mirror of https://github.com/grpc/grpc-java.git
Merge branch 'master' of github.com:carl-mastrangelo/grpc-java
This commit is contained in:
commit
977c3d1c77
|
|
@ -31,6 +31,8 @@
|
||||||
|
|
||||||
package io.grpc.transport;
|
package io.grpc.transport;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
|
|
@ -68,7 +70,7 @@ public abstract class AbstractServerStream<IdT> extends AbstractStream<IdT>
|
||||||
* thread.
|
* thread.
|
||||||
*/
|
*/
|
||||||
public final void setListener(ServerStreamListener listener) {
|
public final void setListener(ServerStreamListener listener) {
|
||||||
this.listener = Preconditions.checkNotNull(listener, "listener");
|
this.listener = checkNotNull(listener);
|
||||||
|
|
||||||
// Now that the stream has actually been initialized, call the listener's onReady callback if
|
// Now that the stream has actually been initialized, call the listener's onReady callback if
|
||||||
// appropriate.
|
// appropriate.
|
||||||
|
|
@ -77,7 +79,7 @@ public abstract class AbstractServerStream<IdT> extends AbstractStream<IdT>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ServerStreamListener listener() {
|
protected ServerStreamListener listener() {
|
||||||
return this.listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,11 @@
|
||||||
|
|
||||||
package io.grpc.transport;
|
package io.grpc.transport;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
|
@ -44,6 +44,8 @@ import javax.annotation.concurrent.GuardedBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for {@link Stream} implementations.
|
* Abstract base class for {@link Stream} implementations.
|
||||||
|
*
|
||||||
|
* @param <IdT> type of the unique identifier of this stream.
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractStream<IdT> implements Stream {
|
public abstract class AbstractStream<IdT> implements Stream {
|
||||||
/**
|
/**
|
||||||
|
|
@ -84,12 +86,6 @@ public abstract class AbstractStream<IdT> implements Stream {
|
||||||
*/
|
*/
|
||||||
private int numSentBytesQueued;
|
private int numSentBytesQueued;
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the listener is currently eligible for notification of
|
|
||||||
* {@link StreamListener#onReady()}.
|
|
||||||
*/
|
|
||||||
@GuardedBy("onReadyLock")
|
|
||||||
private boolean shouldNotifyOnReady = true;
|
|
||||||
/**
|
/**
|
||||||
* Indicates the stream has been created on the connection. This implies that the stream is no
|
* Indicates the stream has been created on the connection. This implies that the stream is no
|
||||||
* longer limited by MAX_CONCURRENT_STREAMS.
|
* longer limited by MAX_CONCURRENT_STREAMS.
|
||||||
|
|
@ -132,6 +128,11 @@ public abstract class AbstractStream<IdT> implements Stream {
|
||||||
deframer = new MessageDeframer(inboundMessageHandler);
|
deframer = new MessageDeframer(inboundMessageHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this method to provide a stream listener.
|
||||||
|
*/
|
||||||
|
protected abstract StreamListener listener();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the internal ID for this stream. Note that ID can be {@code null} for client streams
|
* Returns the internal ID for this stream. Note that ID can be {@code null} for client streams
|
||||||
* as the transport may defer creating the stream to the remote side until it has a payload or
|
* as the transport may defer creating the stream to the remote side until it has a payload or
|
||||||
|
|
@ -145,34 +146,14 @@ public abstract class AbstractStream<IdT> implements Stream {
|
||||||
* will be called. Defaults to {@link #DEFAULT_ONREADY_THRESHOLD}.
|
* will be called. Defaults to {@link #DEFAULT_ONREADY_THRESHOLD}.
|
||||||
*/
|
*/
|
||||||
public int getOnReadyThreshold() {
|
public int getOnReadyThreshold() {
|
||||||
return onReadyThreshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the number of queued bytes for a given stream, below which
|
|
||||||
* {@link StreamListener#onReady()} will be called. If not called, defaults to
|
|
||||||
* {@link #DEFAULT_ONREADY_THRESHOLD}.
|
|
||||||
*
|
|
||||||
* <p>This must be called from the transport thread, since a listener may be called back directly.
|
|
||||||
*/
|
|
||||||
public void setOnReadyThreshold(int onReadyThreshold) {
|
|
||||||
checkArgument(onReadyThreshold > 0, "onReadyThreshold must be > 0");
|
|
||||||
boolean doNotify;
|
|
||||||
synchronized (onReadyLock) {
|
synchronized (onReadyLock) {
|
||||||
if (this.onReadyThreshold <= numSentBytesQueued && onReadyThreshold > numSentBytesQueued) {
|
return onReadyThreshold;
|
||||||
shouldNotifyOnReady = true;
|
|
||||||
}
|
|
||||||
this.onReadyThreshold = onReadyThreshold;
|
|
||||||
doNotify = needToNotifyOnReady();
|
|
||||||
}
|
|
||||||
if (doNotify) {
|
|
||||||
listener().onReady();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeMessage(InputStream message) {
|
public void writeMessage(InputStream message) {
|
||||||
Preconditions.checkNotNull(message, "message");
|
checkNotNull(message);
|
||||||
outboundPhase(Phase.MESSAGE);
|
outboundPhase(Phase.MESSAGE);
|
||||||
if (!framer.isClosed()) {
|
if (!framer.isClosed()) {
|
||||||
framer.writePayload(message);
|
framer.writePayload(message);
|
||||||
|
|
@ -190,9 +171,7 @@ public abstract class AbstractStream<IdT> implements Stream {
|
||||||
public final boolean isReady() {
|
public final boolean isReady() {
|
||||||
if (listener() != null && outboundPhase() != Phase.STATUS) {
|
if (listener() != null && outboundPhase() != Phase.STATUS) {
|
||||||
synchronized (onReadyLock) {
|
synchronized (onReadyLock) {
|
||||||
if (allocated && numSentBytesQueued < onReadyThreshold) {
|
return allocated && numSentBytesQueued < onReadyThreshold;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -221,11 +200,6 @@ public abstract class AbstractStream<IdT> implements Stream {
|
||||||
framer.dispose();
|
framer.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the listener to this stream.
|
|
||||||
*/
|
|
||||||
protected abstract StreamListener listener();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an outbound frame to the remote end point.
|
* Sends an outbound frame to the remote end point.
|
||||||
*
|
*
|
||||||
|
|
@ -312,18 +286,13 @@ public abstract class AbstractStream<IdT> implements Stream {
|
||||||
* StreamListener#onReady()} handler if appropriate. This must be called from the transport
|
* StreamListener#onReady()} handler if appropriate. This must be called from the transport
|
||||||
* thread, since the listener may be called back directly.
|
* thread, since the listener may be called back directly.
|
||||||
*/
|
*/
|
||||||
protected void onStreamAllocated() {
|
protected final void onStreamAllocated() {
|
||||||
boolean doNotify;
|
checkState(listener() != null);
|
||||||
synchronized (onReadyLock) {
|
synchronized (onReadyLock) {
|
||||||
if (allocated) {
|
checkState(!allocated, "Already allocated");
|
||||||
throw new IllegalStateException("Already allocated");
|
|
||||||
}
|
|
||||||
allocated = true;
|
allocated = true;
|
||||||
doNotify = needToNotifyOnReady();
|
|
||||||
}
|
|
||||||
if (doNotify) {
|
|
||||||
listener().onReady();
|
|
||||||
}
|
}
|
||||||
|
notifyIfReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -335,9 +304,6 @@ public abstract class AbstractStream<IdT> implements Stream {
|
||||||
protected final void onSendingBytes(int numBytes) {
|
protected final void onSendingBytes(int numBytes) {
|
||||||
synchronized (onReadyLock) {
|
synchronized (onReadyLock) {
|
||||||
numSentBytesQueued += numBytes;
|
numSentBytesQueued += numBytes;
|
||||||
if (!isReady()) {
|
|
||||||
shouldNotifyOnReady = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,30 +317,27 @@ public abstract class AbstractStream<IdT> implements Stream {
|
||||||
protected final void onSentBytes(int numBytes) {
|
protected final void onSentBytes(int numBytes) {
|
||||||
boolean doNotify;
|
boolean doNotify;
|
||||||
synchronized (onReadyLock) {
|
synchronized (onReadyLock) {
|
||||||
|
boolean belowThresholdBefore = numSentBytesQueued < onReadyThreshold;
|
||||||
numSentBytesQueued -= numBytes;
|
numSentBytesQueued -= numBytes;
|
||||||
doNotify = needToNotifyOnReady();
|
boolean belowThresholdAfter = numSentBytesQueued < onReadyThreshold;
|
||||||
|
doNotify = !belowThresholdBefore && belowThresholdAfter;
|
||||||
|
}
|
||||||
|
if (doNotify) {
|
||||||
|
notifyIfReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
final void notifyIfReady() {
|
||||||
|
boolean doNotify = false;
|
||||||
|
synchronized (onReadyLock) {
|
||||||
|
doNotify = isReady();
|
||||||
}
|
}
|
||||||
if (doNotify) {
|
if (doNotify) {
|
||||||
listener().onReady();
|
listener().onReady();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether or not we need to call the {@link StreamListener#onReady()} handler now.
|
|
||||||
* Calling this method has the side-effect of unsetting {@link #shouldNotifyOnReady} so the
|
|
||||||
* handler should always be invoked immediately after calling this method.
|
|
||||||
*/
|
|
||||||
@GuardedBy("onReadyLock")
|
|
||||||
private boolean needToNotifyOnReady() {
|
|
||||||
if (shouldNotifyOnReady && isReady()) {
|
|
||||||
// Returning true here counts as a call to the onReady callback, so
|
|
||||||
// unset the flag.
|
|
||||||
shouldNotifyOnReady = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Phase inboundPhase() {
|
final Phase inboundPhase() {
|
||||||
return inboundPhase;
|
return inboundPhase;
|
||||||
}
|
}
|
||||||
|
|
@ -405,7 +368,8 @@ public abstract class AbstractStream<IdT> implements Stream {
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Phase verifyNextPhase(Phase currentPhase, Phase nextPhase) {
|
@VisibleForTesting
|
||||||
|
Phase verifyNextPhase(Phase currentPhase, Phase nextPhase) {
|
||||||
if (nextPhase.ordinal() < currentPhase.ordinal()) {
|
if (nextPhase.ordinal() < currentPhase.ordinal()) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
String.format("Cannot transition phase from %s to %s", currentPhase, nextPhase));
|
String.format("Cannot transition phase from %s to %s", currentPhase, nextPhase));
|
||||||
|
|
@ -449,6 +413,5 @@ public abstract class AbstractStream<IdT> implements Stream {
|
||||||
.add("id", id())
|
.add("id", id())
|
||||||
.add("inboundPhase", inboundPhase().name())
|
.add("inboundPhase", inboundPhase().name())
|
||||||
.add("outboundPhase", outboundPhase().name());
|
.add("outboundPhase", outboundPhase().name());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015, Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are
|
||||||
|
* met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following disclaimer
|
||||||
|
* in the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* * Neither the name of Google Inc. nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package io.grpc.transport;
|
||||||
|
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMultimap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
|
import io.grpc.transport.AbstractStream.Phase;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class AbstractStreamTest {
|
||||||
|
@Mock private StreamListener streamListener;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onStreamAllocated_shouldNotifyReady() {
|
||||||
|
AbstractStream<Object> stream = new AbstractStreamBase<Object>(null);
|
||||||
|
|
||||||
|
stream.onStreamAllocated();
|
||||||
|
|
||||||
|
verify(streamListener).onReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validPhaseTransitions() {
|
||||||
|
AbstractStream<Object> stream = new AbstractStreamBase<Object>(null);
|
||||||
|
Multimap<Phase, Phase> validTransitions = ImmutableMultimap.<Phase, Phase>builder()
|
||||||
|
.put(Phase.HEADERS, Phase.HEADERS)
|
||||||
|
.put(Phase.HEADERS, Phase.MESSAGE)
|
||||||
|
.put(Phase.HEADERS, Phase.STATUS)
|
||||||
|
.put(Phase.MESSAGE, Phase.MESSAGE)
|
||||||
|
.put(Phase.MESSAGE, Phase.STATUS)
|
||||||
|
.put(Phase.STATUS, Phase.STATUS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (Phase startPhase : Phase.values()) {
|
||||||
|
for (Phase endPhase : Phase.values()) {
|
||||||
|
if (validTransitions.containsEntry(startPhase, endPhase)) {
|
||||||
|
stream.verifyNextPhase(startPhase, endPhase);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
stream.verifyNextPhase(startPhase, endPhase);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for testing.
|
||||||
|
*/
|
||||||
|
private class AbstractStreamBase<IdT> extends AbstractStream<IdT> {
|
||||||
|
private AbstractStreamBase(WritableBufferAllocator bufferAllocator) {
|
||||||
|
super(bufferAllocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void request(int numMessages) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public IdT id() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StreamListener listener() {
|
||||||
|
return streamListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void internalSendFrame(WritableBuffer frame, boolean endOfStream, boolean flush) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void receiveMessage(InputStream is) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void inboundDeliveryPaused() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void remoteEndClosed() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void returnProcessedBytes(int processedBytes) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deframeFailed(Throwable cause) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -72,6 +72,7 @@ import io.grpc.MethodDescriptor;
|
||||||
import io.grpc.MethodDescriptor.MethodType;
|
import io.grpc.MethodDescriptor.MethodType;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.StatusException;
|
import io.grpc.StatusException;
|
||||||
|
import io.grpc.transport.AbstractStream;
|
||||||
import io.grpc.transport.ClientStreamListener;
|
import io.grpc.transport.ClientStreamListener;
|
||||||
import io.grpc.transport.ClientTransport;
|
import io.grpc.transport.ClientTransport;
|
||||||
import io.grpc.transport.HttpUtil;
|
import io.grpc.transport.HttpUtil;
|
||||||
|
|
@ -926,12 +927,12 @@ public class OkHttpClientTransportTest {
|
||||||
@Test
|
@Test
|
||||||
public void notifyOnReady() throws Exception {
|
public void notifyOnReady() throws Exception {
|
||||||
initTransport();
|
initTransport();
|
||||||
final int messageLength = 15;
|
// exactly one byte below the threshold
|
||||||
|
int messageLength = AbstractStream.DEFAULT_ONREADY_THRESHOLD - HEADER_LENGTH - 1;
|
||||||
setInitialWindowSize(0);
|
setInitialWindowSize(0);
|
||||||
MockStreamListener listener = new MockStreamListener();
|
MockStreamListener listener = new MockStreamListener();
|
||||||
OkHttpClientStream stream = clientTransport.newStream(
|
OkHttpClientStream stream = clientTransport.newStream(
|
||||||
method,new Metadata.Headers(), listener);
|
method, new Metadata.Headers(), listener);
|
||||||
stream.setOnReadyThreshold(HEADER_LENGTH + 20);
|
|
||||||
assertTrue(stream.isReady());
|
assertTrue(stream.isReady());
|
||||||
// Be notified at the beginning.
|
// Be notified at the beginning.
|
||||||
assertTrue(listener.isOnReadyCalled());
|
assertTrue(listener.isOnReadyCalled());
|
||||||
|
|
@ -964,18 +965,6 @@ public class OkHttpClientTransportTest {
|
||||||
assertTrue(stream.isReady());
|
assertTrue(stream.isReady());
|
||||||
assertTrue(listener.isOnReadyCalled());
|
assertTrue(listener.isOnReadyCalled());
|
||||||
|
|
||||||
// Now the first message is still in the queue, and it's size is smaller than the threshold.
|
|
||||||
// Increase the threshold should have no affection.
|
|
||||||
stream.setOnReadyThreshold(messageLength * 10);
|
|
||||||
assertFalse(listener.isOnReadyCalled());
|
|
||||||
// Decrease the threshold should have no affection too.
|
|
||||||
stream.setOnReadyThreshold(HEADER_LENGTH);
|
|
||||||
assertFalse(listener.isOnReadyCalled());
|
|
||||||
// But now increase the threshold to larger than the queued message size, onReady should be
|
|
||||||
// triggered.
|
|
||||||
stream.setOnReadyThreshold(HEADER_LENGTH + messageLength + 1);
|
|
||||||
assertTrue(listener.isOnReadyCalled());
|
|
||||||
|
|
||||||
stream.cancel(Status.CANCELLED);
|
stream.cancel(Status.CANCELLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue