mirror of https://github.com/grpc/grpc-java.git
Handle exceptions thrown by the application
------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=79895899
This commit is contained in:
parent
1f2a1869e5
commit
0f56c47ac8
|
|
@ -5,6 +5,8 @@ import static com.google.net.stubby.transport.StreamState.OPEN;
|
||||||
import static com.google.net.stubby.transport.StreamState.READ_ONLY;
|
import static com.google.net.stubby.transport.StreamState.READ_ONLY;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.net.stubby.Metadata;
|
import com.google.net.stubby.Metadata;
|
||||||
import com.google.net.stubby.Status;
|
import com.google.net.stubby.Status;
|
||||||
|
|
@ -37,6 +39,16 @@ public abstract class AbstractClientStream<IdT> extends AbstractStream<IdT>
|
||||||
// Stored status & trailers to report when deframer completes.
|
// Stored status & trailers to report when deframer completes.
|
||||||
private Status stashedStatus;
|
private Status stashedStatus;
|
||||||
private Metadata.Trailers stashedTrailers;
|
private Metadata.Trailers stashedTrailers;
|
||||||
|
private final FutureCallback<Object> failureCallback = new FutureCallback<Object>() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
log.log(Level.WARNING, "Exception processing message", t);
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object result) {}
|
||||||
|
};
|
||||||
|
|
||||||
protected AbstractClientStream(ClientStreamListener listener,
|
protected AbstractClientStream(ClientStreamListener listener,
|
||||||
@Nullable Decompressor decompressor,
|
@Nullable Decompressor decompressor,
|
||||||
|
|
@ -88,7 +100,10 @@ public abstract class AbstractClientStream<IdT> extends AbstractStream<IdT>
|
||||||
}
|
}
|
||||||
inboundPhase(Phase.MESSAGE);
|
inboundPhase(Phase.MESSAGE);
|
||||||
if (GRPC_V2_PROTOCOL) {
|
if (GRPC_V2_PROTOCOL) {
|
||||||
deframer2.delayProcessing(listener.headersRead(headers));
|
ListenableFuture<?> future = deframer2.delayProcessing(listener.headersRead(headers));
|
||||||
|
if (future != null) {
|
||||||
|
Futures.addCallback(future, failureCallback);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// This is a little broken as it doesn't strictly wait for the last payload handled
|
// This is a little broken as it doesn't strictly wait for the last payload handled
|
||||||
// by the deframer to be processed by the application layer. Not worth fixing as will
|
// by the deframer to be processed by the application layer. Not worth fixing as will
|
||||||
|
|
@ -116,7 +131,10 @@ public abstract class AbstractClientStream<IdT> extends AbstractStream<IdT>
|
||||||
if (!GRPC_V2_PROTOCOL) {
|
if (!GRPC_V2_PROTOCOL) {
|
||||||
deframer.deframe(frame, false);
|
deframer.deframe(frame, false);
|
||||||
} else {
|
} else {
|
||||||
deframer2.deframe(frame, false);
|
ListenableFuture<?> future = deframer2.deframe(frame, false);
|
||||||
|
if (future != null) {
|
||||||
|
Futures.addCallback(future, failureCallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,7 +156,10 @@ public abstract class AbstractClientStream<IdT> extends AbstractStream<IdT>
|
||||||
if (!GRPC_V2_PROTOCOL) {
|
if (!GRPC_V2_PROTOCOL) {
|
||||||
deframer.deframe(Buffers.empty(), true);
|
deframer.deframe(Buffers.empty(), true);
|
||||||
} else {
|
} else {
|
||||||
deframer2.deframe(Buffers.empty(), true);
|
ListenableFuture<?> future = deframer2.deframe(Buffers.empty(), true);
|
||||||
|
if (future != null) {
|
||||||
|
Futures.addCallback(future, failureCallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import static com.google.net.stubby.transport.StreamState.OPEN;
|
||||||
import static com.google.net.stubby.transport.StreamState.WRITE_ONLY;
|
import static com.google.net.stubby.transport.StreamState.WRITE_ONLY;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.net.stubby.Metadata;
|
import com.google.net.stubby.Metadata;
|
||||||
import com.google.net.stubby.Status;
|
import com.google.net.stubby.Status;
|
||||||
|
|
@ -12,6 +14,8 @@ import com.google.net.stubby.Status;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
|
@ -21,6 +25,7 @@ import javax.annotation.concurrent.GuardedBy;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractServerStream<IdT> extends AbstractStream<IdT>
|
public abstract class AbstractServerStream<IdT> extends AbstractStream<IdT>
|
||||||
implements ServerStream {
|
implements ServerStream {
|
||||||
|
private static final Logger log = Logger.getLogger(AbstractServerStream.class.getName());
|
||||||
|
|
||||||
private ServerStreamListener listener;
|
private ServerStreamListener listener;
|
||||||
|
|
||||||
|
|
@ -36,6 +41,16 @@ public abstract class AbstractServerStream<IdT> extends AbstractStream<IdT>
|
||||||
private boolean gracefulClose;
|
private boolean gracefulClose;
|
||||||
/** Saved trailers from close() that need to be sent once the framer has sent all messages. */
|
/** Saved trailers from close() that need to be sent once the framer has sent all messages. */
|
||||||
private Metadata.Trailers stashedTrailers;
|
private Metadata.Trailers stashedTrailers;
|
||||||
|
private final FutureCallback<Object> failureCallback = new FutureCallback<Object>() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
log.log(Level.WARNING, "Exception processing message", t);
|
||||||
|
abortStream(Status.fromThrowable(t), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object result) {}
|
||||||
|
};
|
||||||
|
|
||||||
protected AbstractServerStream(IdT id, @Nullable Decompressor decompressor,
|
protected AbstractServerStream(IdT id, @Nullable Decompressor decompressor,
|
||||||
Executor deframerExecutor) {
|
Executor deframerExecutor) {
|
||||||
|
|
@ -117,7 +132,10 @@ public abstract class AbstractServerStream<IdT> extends AbstractStream<IdT>
|
||||||
if (!GRPC_V2_PROTOCOL) {
|
if (!GRPC_V2_PROTOCOL) {
|
||||||
deframer.deframe(frame, endOfStream);
|
deframer.deframe(frame, endOfStream);
|
||||||
} else {
|
} else {
|
||||||
deframer2.deframe(frame, endOfStream);
|
ListenableFuture<?> future = deframer2.deframe(frame, endOfStream);
|
||||||
|
if (future != null) {
|
||||||
|
Futures.addCallback(future, failureCallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
package com.google.net.stubby.transport;
|
package com.google.net.stubby.transport;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
import com.google.net.stubby.Status;
|
import com.google.net.stubby.Status;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
@ -42,18 +46,11 @@ public class MessageDeframer2 implements Closeable {
|
||||||
private final Sink sink;
|
private final Sink sink;
|
||||||
private final Executor executor;
|
private final Executor executor;
|
||||||
private final Compression compression;
|
private final Compression compression;
|
||||||
private final Runnable deliveryTask = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
deliveryOutstanding = false;
|
|
||||||
deliver();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private State state = State.HEADER;
|
private State state = State.HEADER;
|
||||||
private int requiredLength = HEADER_LENGTH;
|
private int requiredLength = HEADER_LENGTH;
|
||||||
private boolean compressedFlag;
|
private boolean compressedFlag;
|
||||||
private boolean endOfStream;
|
private boolean endOfStream;
|
||||||
private boolean deliveryOutstanding;
|
private SettableFuture<?> deliveryOutstanding;
|
||||||
private CompositeBuffer nextFrame;
|
private CompositeBuffer nextFrame;
|
||||||
private CompositeBuffer unprocessed = new CompositeBuffer();
|
private CompositeBuffer unprocessed = new CompositeBuffer();
|
||||||
|
|
||||||
|
|
@ -86,8 +83,14 @@ public class MessageDeframer2 implements Closeable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the given data to this deframer and attempts delivery to the sink.
|
* Adds the given data to this deframer and attempts delivery to the sink.
|
||||||
|
*
|
||||||
|
* <p>If returned future is not {@code null}, then it completes when no more deliveries are
|
||||||
|
* occuring. Delivering completes if all available deframing input is consumed or if delivery
|
||||||
|
* resulted in an exception, in which case this method may throw the exception or the returned
|
||||||
|
* future will fail with the throwable. The future is guaranteed to complete within the executor
|
||||||
|
* provided during construction.
|
||||||
*/
|
*/
|
||||||
public void deframe(Buffer data, boolean endOfStream) {
|
public ListenableFuture<?> deframe(Buffer data, boolean endOfStream) {
|
||||||
Preconditions.checkNotNull(data, "data");
|
Preconditions.checkNotNull(data, "data");
|
||||||
Preconditions.checkState(this.endOfStream == false, "Past end of stream");
|
Preconditions.checkState(this.endOfStream == false, "Past end of stream");
|
||||||
unprocessed.addBuffer(data);
|
unprocessed.addBuffer(data);
|
||||||
|
|
@ -95,8 +98,11 @@ public class MessageDeframer2 implements Closeable {
|
||||||
// Indicate that all of the data for this stream has been received.
|
// Indicate that all of the data for this stream has been received.
|
||||||
this.endOfStream = endOfStream;
|
this.endOfStream = endOfStream;
|
||||||
|
|
||||||
// Deliver the next message if not already delivering.
|
if (deliveryOutstanding != null) {
|
||||||
deliver();
|
// Only allow one outstanding delivery at a time.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return deliver();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -107,25 +113,62 @@ public class MessageDeframer2 implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delayProcessing(ListenableFuture<Void> future) {
|
/**
|
||||||
Preconditions.checkState(!deliveryOutstanding, "Only one delay allowed concurrently");
|
* Consider {@code future} to be a message currently being processed. Messages will not be
|
||||||
if (future != null) {
|
* delivered until the future completes. The returned future behaves as if it was returned by
|
||||||
deliveryOutstanding = true;
|
* {@link #deframe(Buffer, boolean)}.
|
||||||
// Once future completes, try to deliver the next message.
|
*
|
||||||
future.addListener(deliveryTask, executor);
|
* @throws IllegalStateException if a message is already being processed
|
||||||
|
*/
|
||||||
|
public ListenableFuture<?> delayProcessing(ListenableFuture<?> future) {
|
||||||
|
Preconditions.checkState(deliveryOutstanding == null, "Only one delay allowed concurrently");
|
||||||
|
if (future == null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
return delayProcessingInternal(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is no outstanding delivery, attempts to read and deliver as many messages to the
|
* May only be called when a delivery is known not to be outstanding. If deliveryOutstanding is
|
||||||
* sink as possible. Only one outstanding delivery is allowed at a time.
|
* non-null, then it will be re-used and this method will return {@code null}.
|
||||||
*/
|
*/
|
||||||
private void deliver() {
|
private ListenableFuture<?> delayProcessingInternal(ListenableFuture<?> future) {
|
||||||
if (deliveryOutstanding) {
|
Preconditions.checkNotNull(future, "future");
|
||||||
// Only allow one outstanding delivery at a time.
|
// Return a separate future so that our callback is guaranteed to complete before any
|
||||||
return;
|
// listeners on the returned future.
|
||||||
|
ListenableFuture<?> returnFuture = null;
|
||||||
|
if (deliveryOutstanding == null) {
|
||||||
|
returnFuture = deliveryOutstanding = SettableFuture.create();
|
||||||
}
|
}
|
||||||
|
Futures.addCallback(future, new FutureCallback<Object>() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
SettableFuture<?> previousOutstanding = deliveryOutstanding;
|
||||||
|
deliveryOutstanding = null;
|
||||||
|
previousOutstanding.setException(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object result) {
|
||||||
|
try {
|
||||||
|
deliver();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (deliveryOutstanding == null) {
|
||||||
|
throw Throwables.propagate(t);
|
||||||
|
} else {
|
||||||
|
onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, executor);
|
||||||
|
return returnFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and delivers as many messages to the sink as possible. May only be called when a delivery
|
||||||
|
* is known not to be outstanding.
|
||||||
|
*/
|
||||||
|
private ListenableFuture<?> deliver() {
|
||||||
// Process the uncompressed bytes.
|
// Process the uncompressed bytes.
|
||||||
while (readRequiredBytes()) {
|
while (readRequiredBytes()) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
|
|
@ -134,17 +177,13 @@ public class MessageDeframer2 implements Closeable {
|
||||||
break;
|
break;
|
||||||
case BODY:
|
case BODY:
|
||||||
// Read the body and deliver the message to the sink.
|
// Read the body and deliver the message to the sink.
|
||||||
deliveryOutstanding = true;
|
ListenableFuture<?> processingFuture = processBody();
|
||||||
ListenableFuture<Void> processingFuture = processBody();
|
|
||||||
if (processingFuture != null) {
|
if (processingFuture != null) {
|
||||||
// A future was returned for the completion of processing the delivered
|
// A future was returned for the completion of processing the delivered
|
||||||
// message. Once it's done, try to deliver the next message.
|
// message. Once it's done, try to deliver the next message.
|
||||||
processingFuture.addListener(deliveryTask, executor);
|
return delayProcessingInternal(processingFuture);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No future was returned, so assume processing is complete for the delivery.
|
|
||||||
deliveryOutstanding = false;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new AssertionError("Invalid state: " + state);
|
throw new AssertionError("Invalid state: " + state);
|
||||||
|
|
@ -153,14 +192,19 @@ public class MessageDeframer2 implements Closeable {
|
||||||
|
|
||||||
if (endOfStream) {
|
if (endOfStream) {
|
||||||
if (nextFrame.readableBytes() != 0) {
|
if (nextFrame.readableBytes() != 0) {
|
||||||
// TODO(user): Investigate how this should be propagated, so that stream is aborted and
|
|
||||||
// application is properly notified of abortion.
|
|
||||||
throw Status.INTERNAL
|
throw Status.INTERNAL
|
||||||
.withDescription("Encountered end-of-stream mid-frame")
|
.withDescription("Encountered end-of-stream mid-frame")
|
||||||
.asRuntimeException();
|
.asRuntimeException();
|
||||||
}
|
}
|
||||||
sink.endOfStream();
|
sink.endOfStream();
|
||||||
}
|
}
|
||||||
|
// All available messagesed processed.
|
||||||
|
if (deliveryOutstanding != null) {
|
||||||
|
SettableFuture<?> previousOutstanding = deliveryOutstanding;
|
||||||
|
deliveryOutstanding = null;
|
||||||
|
previousOutstanding.set(null);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -210,8 +254,8 @@ public class MessageDeframer2 implements Closeable {
|
||||||
* Processes the body of the GRPC compression frame. A single compression frame may contain
|
* Processes the body of the GRPC compression frame. A single compression frame may contain
|
||||||
* several GRPC messages within it.
|
* several GRPC messages within it.
|
||||||
*/
|
*/
|
||||||
private ListenableFuture<Void> processBody() {
|
private ListenableFuture<?> processBody() {
|
||||||
ListenableFuture<Void> future;
|
ListenableFuture<?> future;
|
||||||
if (compressedFlag) {
|
if (compressedFlag) {
|
||||||
if (compression == Compression.NONE) {
|
if (compression == Compression.NONE) {
|
||||||
throw Status.INTERNAL
|
throw Status.INTERNAL
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
package com.google.net.stubby.transport;
|
package com.google.net.stubby.transport;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
@ -11,6 +15,7 @@ import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
import com.google.net.stubby.transport.MessageDeframer2.Sink;
|
import com.google.net.stubby.transport.MessageDeframer2.Sink;
|
||||||
|
|
@ -24,6 +29,7 @@ import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -37,7 +43,7 @@ public class MessageDeframer2Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void simplePayload() {
|
public void simplePayload() {
|
||||||
deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 2, 3, 14}), false);
|
assertNull(deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 2, 3, 14}), false));
|
||||||
verify(sink).messageRead(messages.capture(), eq(2));
|
verify(sink).messageRead(messages.capture(), eq(2));
|
||||||
assertEquals(Bytes.asList(new byte[] {3, 14}), bytes(messages));
|
assertEquals(Bytes.asList(new byte[] {3, 14}), bytes(messages));
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
|
|
@ -45,7 +51,8 @@ public class MessageDeframer2Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void smallCombinedPayloads() {
|
public void smallCombinedPayloads() {
|
||||||
deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 2, 14, 15}), false);
|
assertNull(
|
||||||
|
deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 2, 14, 15}), false));
|
||||||
verify(sink).messageRead(messages.capture(), eq(1));
|
verify(sink).messageRead(messages.capture(), eq(1));
|
||||||
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
||||||
verify(sink).messageRead(messages.capture(), eq(2));
|
verify(sink).messageRead(messages.capture(), eq(2));
|
||||||
|
|
@ -55,7 +62,7 @@ public class MessageDeframer2Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void endOfStreamWithPayloadShouldNotifyEndOfStream() {
|
public void endOfStreamWithPayloadShouldNotifyEndOfStream() {
|
||||||
deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 1, 3}), true);
|
assertNull(deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 1, 3}), true));
|
||||||
verify(sink).messageRead(messages.capture(), eq(1));
|
verify(sink).messageRead(messages.capture(), eq(1));
|
||||||
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
||||||
verify(sink).endOfStream();
|
verify(sink).endOfStream();
|
||||||
|
|
@ -64,16 +71,16 @@ public class MessageDeframer2Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void endOfStreamShouldNotifyEndOfStream() {
|
public void endOfStreamShouldNotifyEndOfStream() {
|
||||||
deframer.deframe(buffer(new byte[0]), true);
|
assertNull(deframer.deframe(buffer(new byte[0]), true));
|
||||||
verify(sink).endOfStream();
|
verify(sink).endOfStream();
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void payloadSplitBetweenBuffers() {
|
public void payloadSplitBetweenBuffers() {
|
||||||
deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 7, 3, 14, 1, 5, 9}), false);
|
assertNull(deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 7, 3, 14, 1, 5, 9}), false));
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
deframer.deframe(buffer(new byte[] {2, 6}), false);
|
assertNull(deframer.deframe(buffer(new byte[] {2, 6}), false));
|
||||||
verify(sink).messageRead(messages.capture(), eq(7));
|
verify(sink).messageRead(messages.capture(), eq(7));
|
||||||
assertEquals(Bytes.asList(new byte[] {3, 14, 1, 5, 9, 2, 6}), bytes(messages));
|
assertEquals(Bytes.asList(new byte[] {3, 14, 1, 5, 9, 2, 6}), bytes(messages));
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
|
|
@ -81,9 +88,9 @@ public class MessageDeframer2Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void frameHeaderSplitBetweenBuffers() {
|
public void frameHeaderSplitBetweenBuffers() {
|
||||||
deframer.deframe(buffer(new byte[] {0, 0}), false);
|
assertNull(deframer.deframe(buffer(new byte[] {0, 0}), false));
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
deframer.deframe(buffer(new byte[] {0, 0, 1, 3}), false);
|
assertNull(deframer.deframe(buffer(new byte[] {0, 0, 1, 3}), false));
|
||||||
verify(sink).messageRead(messages.capture(), eq(1));
|
verify(sink).messageRead(messages.capture(), eq(1));
|
||||||
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
|
|
@ -91,7 +98,7 @@ public class MessageDeframer2Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void emptyPayload() {
|
public void emptyPayload() {
|
||||||
deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 0}), false);
|
assertNull(deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 0}), false));
|
||||||
verify(sink).messageRead(messages.capture(), eq(0));
|
verify(sink).messageRead(messages.capture(), eq(0));
|
||||||
assertEquals(Bytes.asList(), bytes(messages));
|
assertEquals(Bytes.asList(), bytes(messages));
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
|
|
@ -99,8 +106,8 @@ public class MessageDeframer2Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void largerFrameSize() {
|
public void largerFrameSize() {
|
||||||
deframer.deframe(
|
assertNull(deframer.deframe(
|
||||||
Buffers.wrap(Bytes.concat(new byte[] {0, 0, 0, 3, (byte) 232}, new byte[1000])), false);
|
Buffers.wrap(Bytes.concat(new byte[] {0, 0, 0, 3, (byte) 232}, new byte[1000])), false));
|
||||||
verify(sink).messageRead(messages.capture(), eq(1000));
|
verify(sink).messageRead(messages.capture(), eq(1000));
|
||||||
assertEquals(Bytes.asList(new byte[1000]), bytes(messages));
|
assertEquals(Bytes.asList(new byte[1000]), bytes(messages));
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
|
|
@ -110,31 +117,91 @@ public class MessageDeframer2Test {
|
||||||
public void payloadCallbackShouldWaitForFutureCompletion() {
|
public void payloadCallbackShouldWaitForFutureCompletion() {
|
||||||
SettableFuture<Void> messageFuture = SettableFuture.create();
|
SettableFuture<Void> messageFuture = SettableFuture.create();
|
||||||
when(sink.messageRead(any(InputStream.class), eq(1))).thenReturn(messageFuture);
|
when(sink.messageRead(any(InputStream.class), eq(1))).thenReturn(messageFuture);
|
||||||
deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 2, 14, 15}), false);
|
ListenableFuture<?> deframeFuture
|
||||||
|
= deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 2, 14, 15}), false);
|
||||||
|
assertNotNull(deframeFuture);
|
||||||
verify(sink).messageRead(messages.capture(), eq(1));
|
verify(sink).messageRead(messages.capture(), eq(1));
|
||||||
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
|
|
||||||
|
SettableFuture<Void> messageFuture2 = SettableFuture.create();
|
||||||
|
when(sink.messageRead(any(InputStream.class), eq(2))).thenReturn(messageFuture2);
|
||||||
messageFuture.set(null);
|
messageFuture.set(null);
|
||||||
|
assertFalse(deframeFuture.isDone());
|
||||||
verify(sink).messageRead(messages.capture(), eq(2));
|
verify(sink).messageRead(messages.capture(), eq(2));
|
||||||
assertEquals(Bytes.asList(new byte[] {14, 15}), bytes(messages));
|
assertEquals(Bytes.asList(new byte[] {14, 15}), bytes(messages));
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
|
|
||||||
|
messageFuture2.set(null);
|
||||||
|
assertTrue(deframeFuture.isDone());
|
||||||
|
verifyNoMoreInteractions(sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void endOfStreamCallbackShouldWaitForFutureCompletion() {
|
public void endOfStreamCallbackShouldWaitForFutureCompletion() {
|
||||||
SettableFuture<Void> messageFuture = SettableFuture.create();
|
SettableFuture<Void> messageFuture = SettableFuture.create();
|
||||||
when(sink.messageRead(any(InputStream.class), eq(1))).thenReturn(messageFuture);
|
when(sink.messageRead(any(InputStream.class), eq(1))).thenReturn(messageFuture);
|
||||||
deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 1, 3}), true);
|
ListenableFuture<?> deframeFuture
|
||||||
|
= deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 1, 3}), true);
|
||||||
|
assertNotNull(deframeFuture);
|
||||||
verify(sink).messageRead(messages.capture(), eq(1));
|
verify(sink).messageRead(messages.capture(), eq(1));
|
||||||
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
|
|
||||||
messageFuture.set(null);
|
messageFuture.set(null);
|
||||||
|
assertTrue(deframeFuture.isDone());
|
||||||
verify(sink).endOfStream();
|
verify(sink).endOfStream();
|
||||||
verifyNoMoreInteractions(sink);
|
verifyNoMoreInteractions(sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void futureShouldPropagateThrownException() throws InterruptedException {
|
||||||
|
SettableFuture<Void> messageFuture = SettableFuture.create();
|
||||||
|
when(sink.messageRead(any(InputStream.class), eq(1))).thenReturn(messageFuture);
|
||||||
|
ListenableFuture<?> deframeFuture
|
||||||
|
= deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 2, 14, 15}), false);
|
||||||
|
assertNotNull(deframeFuture);
|
||||||
|
verify(sink).messageRead(messages.capture(), eq(1));
|
||||||
|
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
||||||
|
verifyNoMoreInteractions(sink);
|
||||||
|
|
||||||
|
RuntimeException thrownEx = new RuntimeException();
|
||||||
|
when(sink.messageRead(any(InputStream.class), eq(2))).thenThrow(thrownEx);
|
||||||
|
messageFuture.set(null);
|
||||||
|
verify(sink).messageRead(messages.capture(), eq(2));
|
||||||
|
assertTrue(deframeFuture.isDone());
|
||||||
|
try {
|
||||||
|
deframeFuture.get();
|
||||||
|
fail("Should have throws ExecutionException");
|
||||||
|
} catch (ExecutionException ex) {
|
||||||
|
assertEquals(thrownEx, ex.getCause());
|
||||||
|
}
|
||||||
|
verifyNoMoreInteractions(sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void futureFailureShouldStopAndPropagateFailure() throws InterruptedException {
|
||||||
|
SettableFuture<Void> messageFuture = SettableFuture.create();
|
||||||
|
when(sink.messageRead(any(InputStream.class), eq(1))).thenReturn(messageFuture);
|
||||||
|
ListenableFuture<?> deframeFuture
|
||||||
|
= deframer.deframe(buffer(new byte[] {0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 2, 14, 15}), false);
|
||||||
|
assertNotNull(deframeFuture);
|
||||||
|
verify(sink).messageRead(messages.capture(), eq(1));
|
||||||
|
assertEquals(Bytes.asList(new byte[] {3}), bytes(messages));
|
||||||
|
verifyNoMoreInteractions(sink);
|
||||||
|
|
||||||
|
RuntimeException thrownEx = new RuntimeException();
|
||||||
|
messageFuture.setException(thrownEx);
|
||||||
|
assertTrue(deframeFuture.isDone());
|
||||||
|
try {
|
||||||
|
deframeFuture.get();
|
||||||
|
fail("Should have throws ExecutionException");
|
||||||
|
} catch (ExecutionException ex) {
|
||||||
|
assertEquals(thrownEx, ex.getCause());
|
||||||
|
}
|
||||||
|
verifyNoMoreInteractions(sink);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compressed() {
|
public void compressed() {
|
||||||
deframer = new MessageDeframer2(
|
deframer = new MessageDeframer2(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue