/**
* Copyright 2014 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rx.internal.util;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import rx.Producer;
import rx.Scheduler;
import rx.exceptions.MissingBackpressureException;
import rx.functions.Action0;
import rx.observers.TestSubscriber;
import rx.schedulers.Schedulers;
public class RxRingBufferSpmcTest extends RxRingBufferBase {
@Override
protected RxRingBuffer createRingBuffer() {
return RxRingBuffer.getSpmcInstance();
}
/**
* Single producer, 2 consumers. The request() ensures it gets scheduled back on the same Producer thread.
*/
@Test
public void testConcurrency() throws InterruptedException {
final RxRingBuffer b = createRingBuffer();
final CountDownLatch emitLatch = new CountDownLatch(255);
final CountDownLatch drainLatch = new CountDownLatch(2);
final Scheduler.Worker w1 = Schedulers.newThread().createWorker();
Scheduler.Worker w2 = Schedulers.newThread().createWorker();
Scheduler.Worker w3 = Schedulers.newThread().createWorker();
final AtomicInteger emit = new AtomicInteger();
final AtomicInteger poll = new AtomicInteger();
final AtomicInteger backpressureExceptions = new AtomicInteger();
final Producer p = new Producer() {
// AtomicInteger c = new AtomicInteger();
@Override
public void request(final long n) {
// System.out.println("request[" + c.incrementAndGet() + "]: " + n + " Thread: " + Thread.currentThread());
w1.schedule(new Action0() {
@Override
public void call() {
if (emitLatch.getCount() == 0) {
return;
}
for (int i = 0; i < n; i++) {
try {
b.onNext("one");
emit.incrementAndGet();
} catch (MissingBackpressureException e) {
System.out.println("BackpressureException => item: " + i + " requested: " + n + " emit: " + emit.get() + " poll: " + poll.get());
backpressureExceptions.incrementAndGet();
}
}
// we'll release after n batches
emitLatch.countDown();
}
});
}
};
final TestSubscriber<String> ts = new TestSubscriber<String>();
w1.schedule(new Action0() {
@Override
public void call() {
ts.requestMore(RxRingBuffer.SIZE);
ts.setProducer(p);
}
});
Action0 drainer = new Action0() {
@Override
public void call() {
int emitted = 0;
int shutdownCount = 0;
while (true) {
Object o = b.poll();
if (o != null) {
emitted++;
poll.incrementAndGet();
} else {
if (emitted > 0) {
ts.requestMore(emitted);
emitted = 0;
} else {
if (emitLatch.getCount() == 0) {
shutdownCount++;
// hack to handle the non-blocking queues
// which can have a race condition between offer and poll
// so poll can return null and then have a value the next loop around
// ... even after emitLatch.getCount() == 0 ... no idea why.
if (shutdownCount > 5) {
drainLatch.countDown();
return;
}
}
}
}
}
}
};
w2.schedule(drainer);
w3.schedule(drainer);
emitLatch.await();
drainLatch.await();
w2.unsubscribe();
w3.unsubscribe();
w1.unsubscribe(); // put this one last as unsubscribing from it can cause Exceptions to be throw in w2/w3
System.out.println("emit: " + emit.get() + " poll: " + poll.get());
assertEquals(0, backpressureExceptions.get());
assertEquals(emit.get(), poll.get());
}
}