protected <ReqT, RespT> RespT callWithRetry(MethodDescriptor<ReqT, RespT> method, Supplier<ReqT> requestFactory, ErrorHandler<RespT> handler) { if (logger.isTraceEnabled()) { logger.trace(String.format("Calling %s...", method.getFullMethodName())); } RetryPolicy.Builder<RespT> builder = new Builder<>(conf.getRetryTimes(), conf.getBackOffClass()); RespT resp = builder.create(handler) .callWithRetry( () -> { BlockingStubT stub = getBlockingStub(); return ClientCalls.blockingUnaryCall( stub.getChannel(), method, stub.getCallOptions(), requestFactory.get()); }, method.getFullMethodName()); if (logger.isTraceEnabled()) { logger.trace(String.format("leaving %s...", method.getFullMethodName())); } return resp; }
protected <ReqT, RespT> void callAsyncWithRetry( MethodDescriptor<ReqT, RespT> method, Supplier<ReqT> requestFactory, StreamObserver<RespT> responseObserver, ErrorHandler<RespT> handler) { logger.debug(String.format("Calling %s...", method.getFullMethodName())); RetryPolicy.Builder<RespT> builder = new Builder<>(conf.getRetryTimes(), conf.getBackOffClass()); builder.create(handler) .callWithRetry( () -> { StubT stub = getAsyncStub(); ClientCalls.asyncUnaryCall( stub.getChannel().newCall(method, stub.getCallOptions()), requestFactory.get(), responseObserver); return null; }, method.getFullMethodName()); logger.debug(String.format("leaving %s...", method.getFullMethodName())); }
@Test public void requestWithNoCacheOptionSkipsCache() { HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT.withOption(SafeMethodCachingInterceptor.NO_CACHE_CALL_OPTION, true), message); HelloReply reply3 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); assertSame(reply1, reply3); }
@Test public void requestWithOnlyIfCachedOption_unavailableIfNotInCache() { try { ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT.withOption( SafeMethodCachingInterceptor.ONLY_IF_CACHED_CALL_OPTION, true), message); fail("Expected call to fail"); } catch (StatusRuntimeException sre) { assertEquals(Status.UNAVAILABLE.getCode(), sre.getStatus().getCode()); assertEquals( "Unsatisfiable Request (only-if-cached set, but value not in cache)", sre.getStatus().getDescription()); } }
@Test public void requestWithNoCacheAndOnlyIfCached_fails() { try { ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT .withOption(SafeMethodCachingInterceptor.NO_CACHE_CALL_OPTION, true) .withOption(SafeMethodCachingInterceptor.ONLY_IF_CACHED_CALL_OPTION, true), message); fail("Expected call to fail"); } catch (StatusRuntimeException sre) { assertEquals(Status.UNAVAILABLE.getCode(), sre.getStatus().getCode()); assertEquals( "Unsatisfiable Request (no-cache and only-if-cached conflict)", sre.getStatus().getDescription()); } }
@Test public void responseNoCacheDirective_notCached() throws Exception { cacheControlDirectives.add("no-cache"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); assertNotEquals(reply1, reply2); Truth.assertThat(cache.internalCache).isEmpty(); Truth.assertThat(cache.removedKeys).isEmpty(); }
@Test public void separateResponseCacheControlDirectives_parsesWithoutError() throws Exception { cacheControlDirectives.add("max-age=1"); cacheControlDirectives.add("no-store , no-cache"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); Truth.assertThat(cache.internalCache).isEmpty(); Truth.assertThat(cache.removedKeys).isEmpty(); }
@Test public void afterResponseMaxAge_cacheEntryInvalidated() throws Exception { cacheControlDirectives.add("max-age=1"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertSame(reply1, reply2); // Wait for cache entry to expire sleepAtLeast(1001); assertNotEquals( reply1, ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message)); Truth.assertThat(cache.removedKeys).hasSize(1); assertEquals( new SafeMethodCachingInterceptor.Key( GreeterGrpc.getSayHelloMethod().getFullMethodName(), message), cache.removedKeys.get(0)); }
@Test public void cacheHit_doesNotResetExpiration() throws Exception { cacheControlDirectives.add("max-age=1"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); sleepAtLeast(1001); HelloReply reply3 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertSame(reply1, reply2); assertNotEquals(reply1, reply3); Truth.assertThat(cache.internalCache).hasSize(1); Truth.assertThat(cache.removedKeys).hasSize(1); }
private ListenableFuture<Void> callBidiStreaming( ImmutableList<DynamicMessage> requests, StreamObserver<DynamicMessage> responseObserver, CallOptions callOptions) { DoneObserver<DynamicMessage> doneObserver = new DoneObserver<>(); StreamObserver<DynamicMessage> requestObserver = ClientCalls.asyncBidiStreamingCall( createCall(callOptions), CompositeStreamObserver.of(responseObserver, doneObserver)); requests.forEach(requestObserver::onNext); requestObserver.onCompleted(); return doneObserver.getCompletionFuture(); }
private ListenableFuture<Void> callClientStreaming( ImmutableList<DynamicMessage> requests, StreamObserver<DynamicMessage> responseObserver, CallOptions callOptions) { DoneObserver<DynamicMessage> doneObserver = new DoneObserver<>(); StreamObserver<DynamicMessage> requestObserver = ClientCalls.asyncClientStreamingCall( createCall(callOptions), CompositeStreamObserver.of(responseObserver, doneObserver)); requests.forEach(requestObserver::onNext); requestObserver.onCompleted(); return doneObserver.getCompletionFuture(); }
private ListenableFuture<Void> callServerStreaming( DynamicMessage request, StreamObserver<DynamicMessage> responseObserver, CallOptions callOptions) { DoneObserver<DynamicMessage> doneObserver = new DoneObserver<>(); ClientCalls.asyncServerStreamingCall( createCall(callOptions), request, CompositeStreamObserver.of(responseObserver, doneObserver)); return doneObserver.getCompletionFuture(); }
private ListenableFuture<Void> callUnary( DynamicMessage request, StreamObserver<DynamicMessage> responseObserver, CallOptions callOptions) { DoneObserver<DynamicMessage> doneObserver = new DoneObserver<>(); ClientCalls.asyncUnaryCall( createCall(callOptions), request, CompositeStreamObserver.of(responseObserver, doneObserver)); return doneObserver.getCompletionFuture(); }
@Test public void safeCallsAreCached() { HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertSame(reply1, reply2); }
@Test public void safeCallsAreCachedWithCopiedMethodDescriptor() { HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod.toBuilder().build(), CallOptions.DEFAULT, message); assertSame(reply1, reply2); }
@Test public void requestWithOnlyIfCachedOption_usesCache() { HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT.withOption( SafeMethodCachingInterceptor.ONLY_IF_CACHED_CALL_OPTION, true), message); assertSame(reply1, reply2); }
@Test public void responseNoStoreDirective_notCached() throws Exception { cacheControlDirectives.add("no-store"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); Truth.assertThat(cache.internalCache).isEmpty(); Truth.assertThat(cache.removedKeys).isEmpty(); }
@Test public void responseNoTransformDirective_notCached() throws Exception { cacheControlDirectives.add("no-transform"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); Truth.assertThat(cache.internalCache).isEmpty(); Truth.assertThat(cache.removedKeys).isEmpty(); }
@Test public void responseMustRevalidateDirective_isIgnored() throws Exception { cacheControlDirectives.add("must-revalidate"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertSame(reply1, reply2); }
@Test public void responseMaxAge_caseInsensitive() throws Exception { cacheControlDirectives.add("MaX-aGe=0"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); Truth.assertThat(cache.internalCache).isEmpty(); Truth.assertThat(cache.removedKeys).isEmpty(); }
@Test public void responseNoCache_caseInsensitive() throws Exception { cacheControlDirectives.add("No-CaCHe"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); Truth.assertThat(cache.internalCache).isEmpty(); Truth.assertThat(cache.removedKeys).isEmpty(); }
@Test public void combinedResponseCacheControlDirectives_parsesWithoutError() throws Exception { cacheControlDirectives.add("max-age=1,no-store , no-cache"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); Truth.assertThat(cache.internalCache).isEmpty(); Truth.assertThat(cache.removedKeys).isEmpty(); }
@Test public void invalidResponseMaxAge_usesDefault() throws Exception { SafeMethodCachingInterceptor interceptorWithCustomMaxAge = SafeMethodCachingInterceptor.newSafeMethodCachingInterceptor(cache, 1); channelToUse = ClientInterceptors.intercept(baseChannel, interceptorWithCustomMaxAge); cacheControlDirectives.add("max-age=-10"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertEquals(reply1, reply2); // Wait for cache entry to expire sleepAtLeast(1001); assertNotEquals( reply1, ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message)); Truth.assertThat(cache.removedKeys).hasSize(1); assertEquals( new SafeMethodCachingInterceptor.Key( GreeterGrpc.getSayHelloMethod().getFullMethodName(), message), cache.removedKeys.get(0)); }
@Test public void responseMaxAgeZero_notAddedToCache() throws Exception { cacheControlDirectives.add("max-age=0"); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); Truth.assertThat(cache.internalCache).isEmpty(); Truth.assertThat(cache.removedKeys).isEmpty(); }
@Test public void afterDefaultMaxAge_cacheEntryInvalidated() throws Exception { SafeMethodCachingInterceptor interceptorWithCustomMaxAge = SafeMethodCachingInterceptor.newSafeMethodCachingInterceptor(cache, 1); channelToUse = ClientInterceptors.intercept(baseChannel, interceptorWithCustomMaxAge); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); assertSame(reply1, reply2); // Wait for cache entry to expire sleepAtLeast(1001); assertNotEquals( reply1, ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message)); Truth.assertThat(cache.removedKeys).hasSize(1); assertEquals( new SafeMethodCachingInterceptor.Key( GreeterGrpc.getSayHelloMethod().getFullMethodName(), message), cache.removedKeys.get(0)); }
@Test public void differentMethodCallsAreNotConflated() { MethodDescriptor<HelloRequest, HelloReply> anotherSafeMethod = GreeterGrpc.getSayAnotherHelloMethod().toBuilder().setSafe(true).build(); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, anotherSafeMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); }
@Test public void differentServiceCallsAreNotConflated() { MethodDescriptor<HelloRequest, HelloReply> anotherSafeMethod = AnotherGreeterGrpc.getSayHelloMethod().toBuilder().setSafe(true).build(); HelloReply reply1 = ClientCalls.blockingUnaryCall( channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message); HelloReply reply2 = ClientCalls.blockingUnaryCall( channelToUse, anotherSafeMethod, CallOptions.DEFAULT, message); assertNotEquals(reply1, reply2); }
/** Sends a cacheable unary rpc using GET. Requires that the server is behind a caching proxy. */ public void cacheableUnary() { // Set safe to true. MethodDescriptor<SimpleRequest, SimpleResponse> safeCacheableUnaryCallMethod = TestServiceGrpc.getCacheableUnaryCallMethod().toBuilder().setSafe(true).build(); // Set fake user IP since some proxies (GFE) won't cache requests from localhost. Metadata.Key<String> userIpKey = Metadata.Key.of("x-user-ip", Metadata.ASCII_STRING_MARSHALLER); Metadata metadata = new Metadata(); metadata.put(userIpKey, "1.2.3.4"); Channel channelWithUserIpKey = ClientInterceptors.intercept(channel, MetadataUtils.newAttachHeadersInterceptor(metadata)); SimpleRequest requests1And2 = SimpleRequest.newBuilder() .setPayload( Payload.newBuilder() .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime())))) .build(); SimpleRequest request3 = SimpleRequest.newBuilder() .setPayload( Payload.newBuilder() .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime())))) .build(); SimpleResponse response1 = ClientCalls.blockingUnaryCall( channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, requests1And2); SimpleResponse response2 = ClientCalls.blockingUnaryCall( channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, requests1And2); SimpleResponse response3 = ClientCalls.blockingUnaryCall( channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, request3); assertEquals(response1, response2); assertNotEquals(response1, response3); }
@Override public void run() { long now; while (!shutdown) { now = System.nanoTime(); ClientCalls.blockingUnaryCall(channel, LoadServer.GENERIC_UNARY_METHOD, CallOptions.DEFAULT, genericRequest.slice()); delay(System.nanoTime() - now); } }
@Override public void run() { while (true) { maxOutstanding.acquireUninterruptibly(); if (shutdown) { maxOutstanding.release(); return; } ClientCalls.asyncUnaryCall( channel.newCall(LoadServer.GENERIC_UNARY_METHOD, CallOptions.DEFAULT), genericRequest.slice(), new StreamObserver<ByteBuf>() { long now = System.nanoTime(); @Override public void onNext(ByteBuf value) { } @Override public void onError(Throwable t) { maxOutstanding.release(); Level level = shutdown ? Level.FINE : Level.INFO; log.log(level, "Error in Generic Async Unary call", t); } @Override public void onCompleted() { delay(System.nanoTime() - now); maxOutstanding.release(); } }); } }
/** * Start a continuously executing set of unary calls that will terminate when * {@code done.get()} is true. Each completed call will increment the counter by the specified * delta which benchmarks can use to measure QPS or bandwidth. */ protected void startUnaryCalls(int callsPerChannel, final AtomicLong counter, final AtomicBoolean done, final long counterDelta) { for (final ManagedChannel channel : channels) { for (int i = 0; i < callsPerChannel; i++) { StreamObserver<ByteBuf> observer = new StreamObserver<ByteBuf>() { @Override public void onNext(ByteBuf value) { counter.addAndGet(counterDelta); } @Override public void onError(Throwable t) { done.set(true); } @Override public void onCompleted() { if (!done.get()) { ByteBuf slice = request.slice(); ClientCalls.asyncUnaryCall( channel.newCall(unaryMethod, CALL_OPTIONS), slice, this); } } }; observer.onCompleted(); } } }
@Override protected String doInBackground(Object... params) { String host = (String) params[0]; String message = (String) params[1]; String portStr = (String) params[2]; boolean useGet = (boolean) params[3]; boolean noCache = (boolean) params[4]; boolean onlyIfCached = (boolean) params[5]; int port = TextUtils.isEmpty(portStr) ? 0 : Integer.valueOf(portStr); try { channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext(true).build(); Channel channelToUse = ClientInterceptors.intercept( channel, SafeMethodCachingInterceptor.newSafeMethodCachingInterceptor(cache)); HelloRequest request = HelloRequest.newBuilder().setName(message).build(); HelloReply reply; if (useGet) { MethodDescriptor<HelloRequest, HelloReply> safeCacheableUnaryCallMethod = GreeterGrpc.getSayHelloMethod().toBuilder().setSafe(true).build(); CallOptions callOptions = CallOptions.DEFAULT; if (noCache) { callOptions = callOptions.withOption(SafeMethodCachingInterceptor.NO_CACHE_CALL_OPTION, true); } if (onlyIfCached) { callOptions = callOptions.withOption( SafeMethodCachingInterceptor.ONLY_IF_CACHED_CALL_OPTION, true); } reply = ClientCalls.blockingUnaryCall( channelToUse, safeCacheableUnaryCallMethod, callOptions, request); } else { GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channelToUse); reply = stub.sayHello(request); } return reply.getMessage(); } catch (Exception e) { Log.e(TAG, "RPC failed", e); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); pw.flush(); return String.format("Failed... : %n%s", sw); } }
/** * Issue a unary call and wait for the response. */ @Benchmark public Object blockingUnary() throws Exception { return ClientCalls.blockingUnaryCall( channels[0].newCall(unaryMethod, CallOptions.DEFAULT), Unpooled.EMPTY_BUFFER); }
/** * Start a continuously executing set of duplex streaming ping-pong calls that will terminate when * {@code done.get()} is true. Each completed call will increment the counter by the specified * delta which benchmarks can use to measure messages per second or bandwidth. */ protected CountDownLatch startStreamingCalls(int callsPerChannel, final AtomicLong counter, final AtomicBoolean record, final AtomicBoolean done, final long counterDelta) { final CountDownLatch latch = new CountDownLatch(callsPerChannel * channels.length); for (final ManagedChannel channel : channels) { for (int i = 0; i < callsPerChannel; i++) { final ClientCall<ByteBuf, ByteBuf> streamingCall = channel.newCall(pingPongMethod, CALL_OPTIONS); final AtomicReference<StreamObserver<ByteBuf>> requestObserverRef = new AtomicReference<StreamObserver<ByteBuf>>(); final AtomicBoolean ignoreMessages = new AtomicBoolean(); StreamObserver<ByteBuf> requestObserver = ClientCalls.asyncBidiStreamingCall( streamingCall, new StreamObserver<ByteBuf>() { @Override public void onNext(ByteBuf value) { if (done.get()) { if (!ignoreMessages.getAndSet(true)) { requestObserverRef.get().onCompleted(); } return; } requestObserverRef.get().onNext(request.slice()); if (record.get()) { counter.addAndGet(counterDelta); } // request is called automatically because the observer implicitly has auto // inbound flow control } @Override public void onError(Throwable t) { logger.log(Level.WARNING, "call error", t); latch.countDown(); } @Override public void onCompleted() { latch.countDown(); } }); requestObserverRef.set(requestObserver); requestObserver.onNext(request.slice()); requestObserver.onNext(request.slice()); } } return latch; }
/** * Start a continuously executing set of duplex streaming ping-pong calls that will terminate when * {@code done.get()} is true. Each completed call will increment the counter by the specified * delta which benchmarks can use to measure messages per second or bandwidth. */ protected CountDownLatch startFlowControlledStreamingCalls(int callsPerChannel, final AtomicLong counter, final AtomicBoolean record, final AtomicBoolean done, final long counterDelta) { final CountDownLatch latch = new CountDownLatch(callsPerChannel * channels.length); for (final ManagedChannel channel : channels) { for (int i = 0; i < callsPerChannel; i++) { final ClientCall<ByteBuf, ByteBuf> streamingCall = channel.newCall(flowControlledStreaming, CALL_OPTIONS); final AtomicReference<StreamObserver<ByteBuf>> requestObserverRef = new AtomicReference<StreamObserver<ByteBuf>>(); final AtomicBoolean ignoreMessages = new AtomicBoolean(); StreamObserver<ByteBuf> requestObserver = ClientCalls.asyncBidiStreamingCall( streamingCall, new StreamObserver<ByteBuf>() { @Override public void onNext(ByteBuf value) { StreamObserver<ByteBuf> obs = requestObserverRef.get(); if (done.get()) { if (!ignoreMessages.getAndSet(true)) { obs.onCompleted(); } return; } if (record.get()) { counter.addAndGet(counterDelta); } // request is called automatically because the observer implicitly has auto // inbound flow control } @Override public void onError(Throwable t) { logger.log(Level.WARNING, "call error", t); latch.countDown(); } @Override public void onCompleted() { latch.countDown(); } }); requestObserverRef.set(requestObserver); // Add some outstanding requests to ensure the server is filling the connection streamingCall.request(5); requestObserver.onNext(request.slice()); } } return latch; }