package test.r2.message.streaming;
import com.linkedin.data.ByteString;
import com.linkedin.r2.message.stream.entitystream.EntityStream;
import com.linkedin.r2.message.stream.entitystream.EntityStreams;
import com.linkedin.r2.message.stream.entitystream.Observer;
import com.linkedin.r2.message.stream.entitystream.ReadHandle;
import com.linkedin.r2.message.stream.entitystream.Reader;
import com.linkedin.r2.message.stream.entitystream.WriteHandle;
import com.linkedin.r2.message.stream.entitystream.Writer;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author Zhenkai Zhu
*/
public class TestEntityStream
{
@Test
public void testEntityStream() throws Exception
{
TestWriter writer = new TestWriter();
ControlReader reader = new ControlReader();
TestObserver ob1 = new TestObserver();
TestObserver ob2 = new TestObserver();
EntityStream stream = EntityStreams.newEntityStream(writer);
stream.addObserver(ob1);
stream.addObserver(ob2);
// write is not possible without a reader
Assert.assertEquals(writer.getWritePossibleCount(), 0);
stream.setReader(reader);
// write is not possible before reader reads
Assert.assertEquals(writer.getWritePossibleCount(), 0);
reader.read(1);
// write become possible
Assert.assertEquals(writer.getWritePossibleCount(), 1);
Assert.assertEquals(writer.remaining(), 1);
writer.write();
Assert.assertEquals(writer.remaining(), 0);
reader.read(10);
// write again become possible
Assert.assertEquals(writer.getWritePossibleCount(), 2);
Assert.assertEquals(writer.remaining(), 10);
while(writer.remaining() > 1)
{
writer.write();
}
Assert.assertEquals(writer.remaining(), 1);
reader.read(10);
// write hasn't become impossible when reader reads again, so onWritePossible should not have been invoked again
Assert.assertEquals(writer.getWritePossibleCount(), 2);
while(writer.remaining() > 0)
{
writer.write();
}
Assert.assertEquals(ob1.getChunkCount(), 21);
Assert.assertEquals(ob2.getChunkCount(), 21);
Assert.assertEquals(reader.getChunkCount(), 21);
try
{
writer.write();
Assert.fail("should fail with IllegalStateException");
}
catch (IllegalStateException ex)
{
// expected
}
}
@Test
public void testNoStackOverflow() throws Exception
{
Writer dumbWriter = new Writer()
{
WriteHandle _wh;
long _count = 0;
final int _total = 1024 * 1024 * 1024;
@Override
public void onInit(WriteHandle wh)
{
_wh = wh;
}
@Override
public void onWritePossible()
{
while(_wh.remaining() > 0 && _count < _total )
{
byte[] bytes = new byte[(int)Math.min(4096, _total - _count)];
_wh.write(ByteString.copy(bytes));
_count += bytes.length;
}
if (_count >= _total )
{
_wh.done();
}
}
@Override
public void onAbort(Throwable ex)
{
// do nothing
}
};
Reader dumbReader = new Reader()
{
ReadHandle _rh;
@Override
public void onInit(ReadHandle rh)
{
_rh = rh;
_rh.request(1);
}
@Override
public void onDataAvailable(ByteString data)
{
_rh.request(1);
}
@Override
public void onDone()
{
}
@Override
public void onError(Throwable e)
{
}
};
EntityStreams.newEntityStream(dumbWriter).setReader(dumbReader);
}
/**
* This test will check the correct behavior in case of a Runtime Exception for the observer.
* Note the Runtime Exception is not the only unchecked exception, and we have to consider also Error
* which is the other unchecked throwable
*/
@Test
public void testObserverThrowRuntimeException()
{
Observer observer = new TestObserver(){
@Override
public void onDone()
{
throw new RuntimeException("broken observer throws");
}
@Override
public void onDataAvailable(ByteString data)
{
throw new RuntimeException("broken observer throws");
}
@Override
public void onError(Throwable ex)
{
throw new RuntimeException("broken observer throws");
}
};
Exception ex = new RuntimeException("writer has problem");
testObserverThrow(observer, ex);
}
@Test
public void testObserversThrowUncheckedError()
{
Observer observer = new TestObserver()
{
@Override
public void onDone()
{
throw new Error("broken observer throws");
}
@Override
public void onDataAvailable(ByteString data)
{
throw new Error("broken observer throws");
}
@Override
public void onError(Throwable ex)
{
throw new Error("broken observer throws");
}
};
Error ex = new Error("writer has problem");
testObserverThrow(observer, ex);
}
public void testObserverThrow(Observer observer, Throwable writeError)
{
TestWriter writer = new TestWriter();
ControlReader reader = new ControlReader();
EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(reader);
reader.read(1);
writer.write();
writer.done();
writer.error(writeError);
Assert.assertEquals(writer.abortedTimes(), 0);
Assert.assertEquals(reader.getChunkCount(), 1);
Assert.assertEquals(reader.doneTimes(), 1);
Assert.assertEquals(reader.errorTimes(), 0);
writer = new TestWriter();
reader = new ControlReader();
es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(reader);
reader.read(1);
writer.write();
writer.error(writeError);
Assert.assertEquals(writer.abortedTimes(), 0);
Assert.assertEquals(reader.getChunkCount(), 1);
Assert.assertEquals(reader.errorTimes(), 1);
}
@Test
public void testReaderThrowRuntimeException()
{
testReaderThrow(new ControlReader()
{
@Override
public void onDataAvailable(ByteString data)
{
super.onDataAvailable(data);
throw new RuntimeException("broken reader throws");
}
}, new ControlReader()
{
@Override
public void onDone()
{
super.onDone();
throw new RuntimeException("broken reader throws");
}
}, new ControlReader()
{
@Override
public void onError(Throwable error)
{
super.onError(error);
throw new RuntimeException("broken reader throws");
}
}, new RuntimeException("writer got problem"));
}
@Test
public void testReaderThrowUncheckedError()
{
testReaderThrow(new ControlReader()
{
@Override
public void onDataAvailable(ByteString data)
{
super.onDataAvailable(data);
throw new Error("broken reader throws");
}
}, new ControlReader()
{
@Override
public void onDone()
{
super.onDone();
throw new Error("broken reader throws");
}
}, new ControlReader()
{
@Override
public void onError(Throwable error)
{
super.onError(error);
throw new Error("broken reader throws");
}
}, new Error("writer got problem"));
}
public void testReaderThrow(ControlReader readerOnData, ControlReader readerOnDone, ControlReader readerOnError,
Throwable writerProblem)
{
TestWriter writer = new TestWriter();
TestObserver observer = new TestObserver();
EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(readerOnData);
readerOnData.read(1);
writer.write();
writer.done();
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertEquals(observer.getChunkCount(), 1);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(observer.doneTimes(), 0);
Assert.assertEquals(readerOnData.getChunkCount(), 1);
Assert.assertEquals(readerOnData.errorTimes(), 1);
Assert.assertEquals(readerOnData.doneTimes(), 0);
writer = new TestWriter();
observer = new TestObserver();
es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(readerOnDone);
readerOnDone.read(1);
writer.write();
writer.done();
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertEquals(observer.getChunkCount(), 1);
Assert.assertEquals(observer.doneTimes(), 1);
Assert.assertEquals(observer.errorTimes(), 0);
Assert.assertEquals(readerOnDone.getChunkCount(), 1);
Assert.assertEquals(readerOnDone.doneTimes(), 1);
Assert.assertEquals(readerOnDone.errorTimes(), 0);
writer = new TestWriter();
observer = new TestObserver();
es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(readerOnError);
readerOnError.read(1);
writer.write();
writer.error(writerProblem);
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertEquals(observer.getChunkCount(), 1);
Assert.assertEquals(observer.doneTimes(), 0);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(readerOnError.getChunkCount(), 1);
Assert.assertEquals(readerOnError.doneTimes(), 0);
Assert.assertEquals(readerOnError.errorTimes(), 1);
}
@Test
public void testWriterThrowRuntimeException()
{
ControlReader reader = new ControlReader() {
@Override
public void onDone()
{
super.onDone();
throw new RuntimeException("broken reader throws");
}
};
TestWriter writer = new TestWriter()
{
@Override
public void onAbort(Throwable ex)
{
super.onAbort(ex);
throw new RuntimeException("broken writer throws");
}
};
TestWriter writerOnWritePossible = new TestWriter()
{
@Override
public void onWritePossible()
{
throw new RuntimeException("broken writer throws");
}
};
testWriterThrow(reader, writer, writerOnWritePossible);
}
@Test
public void testWriterThrowUncheckedError()
{
ControlReader reader = new ControlReader()
{
@Override
public void onDone()
{
super.onDone();
throw new Error("broken reader throws");
}
};
TestWriter writer = new TestWriter()
{
@Override
public void onAbort(Throwable ex)
{
super.onAbort(ex);
throw new Error("broken writer throws");
}
};
TestWriter writerOnWritePossible = new TestWriter()
{
@Override
public void onWritePossible()
{
throw new Error("broken writer throws");
}
};
testWriterThrow(reader, writer, writerOnWritePossible);
}
public void testWriterThrow(ControlReader reader, TestWriter writerOnAbort, TestWriter writerOnWritePossible)
{
TestObserver observer = new TestObserver();
EntityStream es = EntityStreams.newEntityStream(writerOnAbort);
es.addObserver(observer);
es.setReader(reader);
reader.read(1);
writerOnAbort.write();
writerOnAbort.done();
Assert.assertEquals(writerOnAbort.abortedTimes(), 1);
Assert.assertEquals(observer.getChunkCount(), 1);
Assert.assertEquals(observer.doneTimes(), 1);
Assert.assertEquals(observer.errorTimes(), 0);
Assert.assertEquals(reader.getChunkCount(), 1);
Assert.assertEquals(reader.doneTimes(), 1);
Assert.assertEquals(reader.errorTimes(), 0);
reader = new ControlReader();
observer = new TestObserver();
es = EntityStreams.newEntityStream(writerOnWritePossible);
es.addObserver(observer);
es.setReader(reader);
reader.read(1);
writerOnWritePossible.write();
writerOnWritePossible.done();
Assert.assertEquals(writerOnWritePossible.abortedTimes(), 1);
Assert.assertEquals(observer.getChunkCount(), 0);
Assert.assertEquals(observer.doneTimes(), 0);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(reader.getChunkCount(), 0);
Assert.assertEquals(reader.doneTimes(), 0);
Assert.assertEquals(reader.errorTimes(), 1);
}
@Test
public void testCancelSimple() throws Exception
{
TestWriter writer = new TestWriter();
TestObserver observer = new TestObserver();
final ControlReader reader = new ControlReader();
EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(reader);
reader.read(1000);
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.schedule(new Runnable()
{
@Override
public void run()
{
reader.cancel();
}
}, 50, TimeUnit.MILLISECONDS);
while(writer.remaining() > 0)
{
writer.write();
Thread.sleep(1);
}
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertTrue(observer.getChunkCount() < 100);
Assert.assertEquals(observer.doneTimes(), 0);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(observer.getLastEvent(), "onError");
Assert.assertTrue(reader.getChunkCount() < 100);
Assert.assertEquals(reader.doneTimes(), 0);
Assert.assertEquals(reader.errorTimes(), 0);
scheduler.shutdown();
}
@Test
public void testCancelWhenNotWritePossible() throws Exception
{
TestWriter writer = new TestWriter();
TestObserver observer = new TestObserver();
final ControlReader reader = new ControlReader();
EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(reader);
reader.read(10);
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
final CountDownLatch latch = new CountDownLatch(1);
scheduler.schedule(new Runnable()
{
@Override
public void run()
{
reader.cancel();
latch.countDown();
}
}, 50, TimeUnit.MILLISECONDS);
while(writer.remaining() > 0)
{
writer.write();
}
Assert.assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertEquals(observer.getChunkCount(), 10);
Assert.assertEquals(observer.doneTimes(), 0);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(observer.getLastEvent(), "onError");
Assert.assertEquals(reader.getChunkCount(), 10);
Assert.assertEquals(reader.doneTimes(), 0);
Assert.assertEquals(reader.errorTimes(), 0);
scheduler.shutdown();
}
@Test
public void testRaceBetweenWriteAndCancel() throws Exception
{
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 0; i < 1000; i ++)
{
testRaceBetweenWriteAndCancel(executor);
}
executor.shutdown();
}
private void testRaceBetweenWriteAndCancel(ExecutorService executor) throws Exception
{
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch finishLatch = new CountDownLatch(2);
final CountDownLatch prepareLatch = new CountDownLatch(2);
final TestWriter writer = new TestWriter();
TestObserver observer = new TestObserver();
final ControlReader reader = new ControlReader();
EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(reader);
reader.read(1000);
executor.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
while(writer.remaining() > 0)
{
prepareLatch.countDown();
startLatch.await();
writer.write();
}
finishLatch.countDown();
return null;
}
});
executor.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
prepareLatch.countDown();
startLatch.await();
reader.cancel();
finishLatch.countDown();
return null;
}
});
prepareLatch.await();
startLatch.countDown();
Assert.assertTrue(finishLatch.await(1000, TimeUnit.MILLISECONDS));
// in any case, reader shouldn't fail
Assert.assertEquals(reader.errorTimes(), 0);
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertEquals(reader.getChunkCount(), observer.getChunkCount());
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(observer.getLastEvent(), "onError");
}
@Test
public void testRaceBetweenDoneAndCancel() throws Exception
{
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 0; i < 1000; i ++)
{
testRaceBetweenDoneAndCancel(executor);
}
executor.shutdown();
}
@Test
public void testReaderInitError() throws Exception
{
final CountDownLatch latch = new CountDownLatch(1);
final TestWriter writer = new TestWriter();
TestObserver observer = new TestObserver();
final EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
final ControlReader reader = new ControlReader()
{
@Override
public void onInit(ReadHandle rh)
{
throw new RuntimeException();
}
};
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(new Runnable()
{
@Override
public void run()
{
es.setReader(reader);
latch.countDown();
}
});
Assert.assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
Assert.assertEquals(reader.errorTimes(), 1);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(writer.abortedTimes(), 1);
}
@Test
public void testWriterInitError() throws Exception
{
final CountDownLatch latch = new CountDownLatch(1);
final TestWriter writer = new TestWriter() {
@Override
public void onInit(WriteHandle wh)
{
throw new RuntimeException();
}
};
TestObserver observer = new TestObserver();
final EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
final ControlReader reader = new ControlReader();
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(new Runnable()
{
@Override
public void run()
{
es.setReader(reader);
reader.read(5);
latch.countDown();
}
});
Assert.assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
Assert.assertEquals(reader.errorTimes(), 1);
Assert.assertEquals(reader.getChunkCount(), 0);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(reader.getChunkCount(), 0);
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertEquals(writer.getWritePossibleCount(), 0);
}
@Test
public void testWriterAndReaderInitError() throws Exception
{
final CountDownLatch latch = new CountDownLatch(1);
final TestWriter writer = new TestWriter() {
@Override
public void onInit(WriteHandle wh)
{
throw new RuntimeException();
}
};
TestObserver observer = new TestObserver();
final EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
final ControlReader reader = new ControlReader()
{
@Override
public void onInit(ReadHandle rh)
{
throw new RuntimeException();
}
};
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(new Runnable()
{
@Override
public void run()
{
es.setReader(reader);
latch.countDown();
}
});
Assert.assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
Assert.assertEquals(reader.errorTimes(), 1);
Assert.assertEquals(reader.getChunkCount(), 0);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(reader.getChunkCount(), 0);
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertEquals(writer.getWritePossibleCount(), 0);
}
@Test
public void testReaderInitErrorThrowInStreaming() throws Exception
{
final CountDownLatch latch = new CountDownLatch(1);
final TestWriter writer = new TestWriter()
{
@Override
public void onWritePossible()
{
super.onWritePossible();
write();
}
};
TestObserver observer = new TestObserver();
final EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
final ControlReader reader = new ControlReader()
{
@Override
public void onInit(ReadHandle rh)
{
rh.request(1);
}
@Override
public void onDataAvailable(ByteString data)
{
super.onDataAvailable(data);
throw new RuntimeException();
}
};
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(new Runnable()
{
@Override
public void run()
{
es.setReader(reader);
latch.countDown();
}
});
Assert.assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
Assert.assertEquals(reader.errorTimes(), 1);
Assert.assertEquals(reader.getChunkCount(), 1);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(reader.getChunkCount(), 1);
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertEquals(writer.getWritePossibleCount(), 1);
}
private void testRaceBetweenDoneAndCancel(ExecutorService executor) throws Exception
{
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch finishLatch = new CountDownLatch(2);
final CountDownLatch prepareLatch = new CountDownLatch(2);
final TestWriter writer = new TestWriter();
TestObserver observer = new TestObserver();
final ControlReader reader = new ControlReader();
EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(reader);
reader.read(1000);
executor.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
while(writer.remaining() > 100)
{
writer.write();
}
prepareLatch.countDown();
startLatch.await();
writer.done();
finishLatch.countDown();
return null;
}
});
executor.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
prepareLatch.countDown();
startLatch.await();
reader.cancel();
finishLatch.countDown();
return null;
}
});
prepareLatch.await();
startLatch.countDown();
Assert.assertTrue(finishLatch.await(1000, TimeUnit.MILLISECONDS));
// in any case, reader shouldn't fail
Assert.assertEquals(reader.errorTimes(), 0);
// if done wins the race
if (reader.doneTimes() > 0)
{
Assert.assertEquals(reader.doneTimes(), 1);
Assert.assertEquals(observer.doneTimes(), 1);
Assert.assertEquals(observer.errorTimes(), 0);
Assert.assertEquals(writer.abortedTimes(), 0);
}
// if cancel wins the race
else
{
Assert.assertEquals(observer.doneTimes(), 0);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(writer.abortedTimes(), 1);
}
}
@Test
public void testRaceBetweenErrorAndCancel() throws Exception
{
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 0; i < 1000; i ++)
{
testRaceBetweenErrorAndCancel(executor);
}
executor.shutdown();
}
private void testRaceBetweenErrorAndCancel(ExecutorService executor) throws Exception
{
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch finishLatch = new CountDownLatch(2);
final CountDownLatch prepareLatch = new CountDownLatch(2);
final TestWriter writer = new TestWriter();
TestObserver observer = new TestObserver();
final ControlReader reader = new ControlReader();
EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(reader);
reader.read(1000);
executor.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
while(writer.remaining() > 100)
{
writer.write();
}
prepareLatch.countDown();
startLatch.await();
writer.error(new RuntimeException("writer has problem"));
finishLatch.countDown();
return null;
}
});
executor.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
prepareLatch.countDown();
startLatch.await();
reader.cancel();
finishLatch.countDown();
return null;
}
});
prepareLatch.await();
startLatch.countDown();
Assert.assertTrue(finishLatch.await(1000, TimeUnit.MILLISECONDS));
// if error wins the race
if (reader.errorTimes() > 0)
{
Assert.assertEquals(reader.doneTimes(), 0);
Assert.assertEquals(observer.doneTimes(), 0);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(writer.abortedTimes(), 0);
}
// if cancel wins the race
else
{
Assert.assertEquals(observer.doneTimes(), 0);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertEquals(reader.doneTimes(), 0);
Assert.assertEquals(reader.errorTimes(), 0);
}
}
@Test
public void testRaceBetweenAbnormalAbortAndCancel() throws Exception
{
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 0; i < 1000; i ++)
{
testRaceBetweenAbnormalAbortAndCancel(executor);
}
executor.shutdown();
}
private void testRaceBetweenAbnormalAbortAndCancel(ExecutorService executor) throws Exception
{
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch finishLatch = new CountDownLatch(2);
final CountDownLatch prepareLatch = new CountDownLatch(2);
final TestWriter writer = new TestWriter();
TestObserver observer = new TestObserver();
final ControlReader reader = new ControlReader() {
@Override
public void onDataAvailable(ByteString data)
{
try
{
prepareLatch.countDown();
startLatch.await();
}
catch (Exception ex)
{
// ...
}
throw new RuntimeException("broken reader throws");
}
};
EntityStream es = EntityStreams.newEntityStream(writer);
es.addObserver(observer);
es.setReader(reader);
reader.read(1000);
executor.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
while(writer.remaining() > 0)
{
writer.write();
}
finishLatch.countDown();
return null;
}
});
executor.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
prepareLatch.countDown();
startLatch.await();
reader.cancel();
finishLatch.countDown();
return null;
}
});
prepareLatch.await();
startLatch.countDown();
Assert.assertTrue(finishLatch.await(1000, TimeUnit.MILLISECONDS));
// we should always fail because cancel on reader side wouldn't cause cancel action if
// writer is already in the writing process
Assert.assertEquals(writer.abortedTimes(), 1);
Assert.assertEquals(observer.errorTimes(), 1);
Assert.assertEquals(observer.getChunkCount(), 1);
Assert.assertEquals(observer.getLastEvent(), "onError");
Assert.assertEquals(reader.errorTimes(), 1);
Assert.assertEquals(reader.getChunkCount(), 0);
}
private static class ControlReader extends TestObserver implements Reader
{
ReadHandle _rh;
public void read(int n)
{
_rh.request(n);
}
public void cancel()
{
_rh.cancel();
}
@Override
public void onInit(ReadHandle rh)
{
_rh = rh;
}
}
private static class TestWriter implements Writer
{
private WriteHandle _wh;
private volatile int _count = 0;
private AtomicInteger _aborted = new AtomicInteger(0);
public int getWritePossibleCount()
{
return _count;
}
public int remaining()
{
return _wh.remaining();
}
public void write()
{
_wh.write(ByteString.empty());
}
public void done()
{
_wh.done();
}
public void error(Throwable ex)
{
_wh.error(ex);
}
public int abortedTimes()
{
return _aborted.get();
}
@Override
public void onInit(WriteHandle wh)
{
_wh = wh;
}
@Override
public void onWritePossible()
{
_count++;
}
@Override
public void onAbort(Throwable ex)
{
_aborted.incrementAndGet();
}
}
private static class TestObserver implements Observer
{
private volatile int _count = 0;
private AtomicInteger _isDone = new AtomicInteger(0);
private AtomicInteger _error = new AtomicInteger(0);
private AtomicReference<String> _lastEvent = new AtomicReference<String>();
@Override
public void onDataAvailable(ByteString data)
{
_lastEvent.set("onDataAvailable");
_count++;
}
@Override
public void onDone()
{
_lastEvent.set("onDone");
_isDone.incrementAndGet();
}
@Override
public void onError(Throwable e)
{
_lastEvent.set("onError");
_error.incrementAndGet();
}
public int getChunkCount()
{
return _count;
}
public int doneTimes()
{
return _isDone.get();
}
public int errorTimes()
{
return _error.get();
}
public String getLastEvent()
{
return _lastEvent.get();
}
}
}