mirror of https://github.com/tikv/client-java.git
				
				
				
			* cherry pick #481 to release-3.1 Signed-off-by: ti-srebot <ti-srebot@pingcap.com> * resolve confilcts Signed-off-by: marsishandsome <marsishandsome@gmail.com> Co-authored-by: Liangliang Gu <marsishandsome@gmail.com>
This commit is contained in:
		
							parent
							
								
									3bb2eada85
								
							
						
					
					
						commit
						a577795d90
					
				
							
								
								
									
										10
									
								
								pom.xml
								
								
								
								
							
							
						
						
									
										10
									
								
								pom.xml
								
								
								
								
							|  | @ -78,6 +78,16 @@ | |||
|     </properties> | ||||
| 
 | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>io.perfmark</groupId> | ||||
|             <artifactId>perfmark-api</artifactId> | ||||
|             <version>0.24.0</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>io.perfmark</groupId> | ||||
|             <artifactId>perfmark-traceviewer</artifactId> | ||||
|             <version>0.24.0</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.antlr</groupId> | ||||
|             <artifactId>antlr4-runtime</artifactId> | ||||
|  |  | |||
|  | @ -0,0 +1,797 @@ | |||
| /* | ||||
|  * Copyright 2014 The gRPC Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package io.grpc.internal; | ||||
| 
 | ||||
| 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 static com.google.common.util.concurrent.MoreExecutors.directExecutor; | ||||
| import static io.grpc.Contexts.statusFromCancelled; | ||||
| import static io.grpc.Status.DEADLINE_EXCEEDED; | ||||
| import static io.grpc.internal.GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY; | ||||
| import static io.grpc.internal.GrpcUtil.CONTENT_ENCODING_KEY; | ||||
| import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY; | ||||
| import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY; | ||||
| import static java.lang.Math.max; | ||||
| 
 | ||||
| import com.google.common.annotations.VisibleForTesting; | ||||
| import com.google.common.base.MoreObjects; | ||||
| import io.grpc.Attributes; | ||||
| import io.grpc.CallOptions; | ||||
| import io.grpc.ClientCall; | ||||
| import io.grpc.Codec; | ||||
| import io.grpc.Compressor; | ||||
| import io.grpc.CompressorRegistry; | ||||
| import io.grpc.Context; | ||||
| import io.grpc.Context.CancellationListener; | ||||
| import io.grpc.Deadline; | ||||
| import io.grpc.DecompressorRegistry; | ||||
| import io.grpc.InternalConfigSelector; | ||||
| import io.grpc.InternalDecompressorRegistry; | ||||
| import io.grpc.Metadata; | ||||
| import io.grpc.MethodDescriptor; | ||||
| import io.grpc.MethodDescriptor.MethodType; | ||||
| import io.grpc.Status; | ||||
| import io.grpc.internal.ManagedChannelServiceConfig.MethodInfo; | ||||
| import io.perfmark.Link; | ||||
| import io.perfmark.PerfMark; | ||||
| import io.perfmark.Tag; | ||||
| import java.io.InputStream; | ||||
| import java.nio.charset.Charset; | ||||
| import java.util.Locale; | ||||
| import java.util.concurrent.CancellationException; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
| import java.util.concurrent.ScheduledFuture; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.logging.Level; | ||||
| import java.util.logging.Logger; | ||||
| import javax.annotation.Nullable; | ||||
| 
 | ||||
| /** Implementation of {@link ClientCall}. */ | ||||
| final class ClientCallImpl<ReqT, RespT> extends ClientCall<ReqT, RespT> { | ||||
| 
 | ||||
|   private static final Logger log = Logger.getLogger(ClientCallImpl.class.getName()); | ||||
|   private static final byte[] FULL_STREAM_DECOMPRESSION_ENCODINGS = | ||||
|       "gzip".getBytes(Charset.forName("US-ASCII")); | ||||
| 
 | ||||
|   private final MethodDescriptor<ReqT, RespT> method; | ||||
|   private final Tag tag; | ||||
|   private final Executor callExecutor; | ||||
|   private final boolean callExecutorIsDirect; | ||||
|   private final CallTracer channelCallsTracer; | ||||
|   private final Context context; | ||||
|   private volatile ScheduledFuture<?> deadlineCancellationFuture; | ||||
|   private final boolean unaryRequest; | ||||
|   private CallOptions callOptions; | ||||
|   private ClientStream stream; | ||||
|   private volatile boolean cancelListenersShouldBeRemoved; | ||||
|   private boolean cancelCalled; | ||||
|   private boolean halfCloseCalled; | ||||
|   private final ClientStreamProvider clientStreamProvider; | ||||
|   private final ContextCancellationListener cancellationListener = | ||||
|       new ContextCancellationListener(); | ||||
|   private final ScheduledExecutorService deadlineCancellationExecutor; | ||||
|   private boolean fullStreamDecompression; | ||||
|   private DecompressorRegistry decompressorRegistry = DecompressorRegistry.getDefaultInstance(); | ||||
|   private CompressorRegistry compressorRegistry = CompressorRegistry.getDefaultInstance(); | ||||
| 
 | ||||
|   ClientCallImpl( | ||||
|       MethodDescriptor<ReqT, RespT> method, | ||||
|       Executor executor, | ||||
|       CallOptions callOptions, | ||||
|       ClientStreamProvider clientStreamProvider, | ||||
|       ScheduledExecutorService deadlineCancellationExecutor, | ||||
|       CallTracer channelCallsTracer, | ||||
|       // TODO(zdapeng): remove this arg | ||||
|       @Nullable InternalConfigSelector configSelector) { | ||||
|     this.method = method; | ||||
|     // TODO(carl-mastrangelo): consider moving this construction to ManagedChannelImpl. | ||||
|     this.tag = PerfMark.createTag(method.getFullMethodName(), System.identityHashCode(this)); | ||||
|     // If we know that the executor is a direct executor, we don't need to wrap it with a | ||||
|     // SerializingExecutor. This is purely for performance reasons. | ||||
|     // See https://github.com/grpc/grpc-java/issues/368 | ||||
|     if (executor == directExecutor()) { | ||||
|       this.callExecutor = new SerializeReentrantCallsDirectExecutor(); | ||||
|       callExecutorIsDirect = true; | ||||
|     } else { | ||||
|       this.callExecutor = new SerializingExecutor(executor); | ||||
|       callExecutorIsDirect = false; | ||||
|     } | ||||
|     this.channelCallsTracer = channelCallsTracer; | ||||
|     // Propagate the context from the thread which initiated the call to all callbacks. | ||||
|     this.context = Context.current(); | ||||
|     this.unaryRequest = | ||||
|         method.getType() == MethodType.UNARY || method.getType() == MethodType.SERVER_STREAMING; | ||||
|     this.callOptions = callOptions; | ||||
|     this.clientStreamProvider = clientStreamProvider; | ||||
|     this.deadlineCancellationExecutor = deadlineCancellationExecutor; | ||||
|     PerfMark.event("ClientCall.<init>", tag); | ||||
|   } | ||||
| 
 | ||||
|   private final class ContextCancellationListener implements CancellationListener { | ||||
|     @Override | ||||
|     public void cancelled(Context context) { | ||||
|       stream.cancel(statusFromCancelled(context)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** Provider of {@link ClientStream}s. */ | ||||
|   interface ClientStreamProvider { | ||||
|     ClientStream newStream( | ||||
|         MethodDescriptor<?, ?> method, CallOptions callOptions, Metadata headers, Context context); | ||||
|   } | ||||
| 
 | ||||
|   ClientCallImpl<ReqT, RespT> setFullStreamDecompression(boolean fullStreamDecompression) { | ||||
|     this.fullStreamDecompression = fullStreamDecompression; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   ClientCallImpl<ReqT, RespT> setDecompressorRegistry(DecompressorRegistry decompressorRegistry) { | ||||
|     this.decompressorRegistry = decompressorRegistry; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   ClientCallImpl<ReqT, RespT> setCompressorRegistry(CompressorRegistry compressorRegistry) { | ||||
|     this.compressorRegistry = compressorRegistry; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   @VisibleForTesting | ||||
|   static void prepareHeaders( | ||||
|       Metadata headers, | ||||
|       DecompressorRegistry decompressorRegistry, | ||||
|       Compressor compressor, | ||||
|       boolean fullStreamDecompression) { | ||||
|     headers.discardAll(MESSAGE_ENCODING_KEY); | ||||
|     if (compressor != Codec.Identity.NONE) { | ||||
|       headers.put(MESSAGE_ENCODING_KEY, compressor.getMessageEncoding()); | ||||
|     } | ||||
| 
 | ||||
|     headers.discardAll(MESSAGE_ACCEPT_ENCODING_KEY); | ||||
|     byte[] advertisedEncodings = | ||||
|         InternalDecompressorRegistry.getRawAdvertisedMessageEncodings(decompressorRegistry); | ||||
|     if (advertisedEncodings.length != 0) { | ||||
|       headers.put(MESSAGE_ACCEPT_ENCODING_KEY, advertisedEncodings); | ||||
|     } | ||||
| 
 | ||||
|     headers.discardAll(CONTENT_ENCODING_KEY); | ||||
|     headers.discardAll(CONTENT_ACCEPT_ENCODING_KEY); | ||||
|     if (fullStreamDecompression) { | ||||
|       headers.put(CONTENT_ACCEPT_ENCODING_KEY, FULL_STREAM_DECOMPRESSION_ENCODINGS); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void start(Listener<RespT> observer, Metadata headers) { | ||||
|     PerfMark.startTask("ClientCall.start", tag); | ||||
|     try { | ||||
|       startInternal(observer, headers); | ||||
|     } finally { | ||||
|       PerfMark.stopTask("ClientCall.start", tag); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void startInternal(Listener<RespT> observer, Metadata headers) { | ||||
|     checkState(stream == null, "Already started"); | ||||
|     checkState(!cancelCalled, "call was cancelled"); | ||||
|     checkNotNull(observer, "observer"); | ||||
|     checkNotNull(headers, "headers"); | ||||
| 
 | ||||
|     if (context.isCancelled()) { | ||||
|       // Context is already cancelled so no need to create a real stream, just notify the observer | ||||
|       // of cancellation via callback on the executor | ||||
|       stream = NoopClientStream.INSTANCE; | ||||
|       final Listener<RespT> finalObserver = observer; | ||||
|       class ClosedByContext extends ContextRunnable { | ||||
|         ClosedByContext() { | ||||
|           super(context); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void runInContext() { | ||||
|           closeObserver(finalObserver, statusFromCancelled(context), new Metadata()); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       callExecutor.execute(new ClosedByContext()); | ||||
|       return; | ||||
|     } | ||||
|     applyMethodConfig(); | ||||
|     final String compressorName = callOptions.getCompressor(); | ||||
|     Compressor compressor; | ||||
|     if (compressorName != null) { | ||||
|       compressor = compressorRegistry.lookupCompressor(compressorName); | ||||
|       if (compressor == null) { | ||||
|         stream = NoopClientStream.INSTANCE; | ||||
|         final Listener<RespT> finalObserver = observer; | ||||
|         class ClosedByNotFoundCompressor extends ContextRunnable { | ||||
|           ClosedByNotFoundCompressor() { | ||||
|             super(context); | ||||
|           } | ||||
| 
 | ||||
|           @Override | ||||
|           public void runInContext() { | ||||
|             closeObserver( | ||||
|                 finalObserver, | ||||
|                 Status.INTERNAL.withDescription( | ||||
|                     String.format("Unable to find compressor by name %s", compressorName)), | ||||
|                 new Metadata()); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         callExecutor.execute(new ClosedByNotFoundCompressor()); | ||||
|         return; | ||||
|       } | ||||
|     } else { | ||||
|       compressor = Codec.Identity.NONE; | ||||
|     } | ||||
|     prepareHeaders(headers, decompressorRegistry, compressor, fullStreamDecompression); | ||||
| 
 | ||||
|     Deadline effectiveDeadline = effectiveDeadline(); | ||||
|     boolean deadlineExceeded = effectiveDeadline != null && effectiveDeadline.isExpired(); | ||||
|     if (!deadlineExceeded) { | ||||
|       logIfContextNarrowedTimeout( | ||||
|           effectiveDeadline, context.getDeadline(), callOptions.getDeadline()); | ||||
|       stream = clientStreamProvider.newStream(method, callOptions, headers, context); | ||||
|     } else { | ||||
|       stream = | ||||
|           new FailingClientStream( | ||||
|               DEADLINE_EXCEEDED.withDescription( | ||||
|                   "ClientCall started after deadline exceeded: " + effectiveDeadline)); | ||||
|     } | ||||
| 
 | ||||
|     if (callExecutorIsDirect) { | ||||
|       stream.optimizeForDirectExecutor(); | ||||
|     } | ||||
|     if (callOptions.getAuthority() != null) { | ||||
|       stream.setAuthority(callOptions.getAuthority()); | ||||
|     } | ||||
|     if (callOptions.getMaxInboundMessageSize() != null) { | ||||
|       stream.setMaxInboundMessageSize(callOptions.getMaxInboundMessageSize()); | ||||
|     } | ||||
|     if (callOptions.getMaxOutboundMessageSize() != null) { | ||||
|       stream.setMaxOutboundMessageSize(callOptions.getMaxOutboundMessageSize()); | ||||
|     } | ||||
|     if (effectiveDeadline != null) { | ||||
|       stream.setDeadline(effectiveDeadline); | ||||
|     } | ||||
|     stream.setCompressor(compressor); | ||||
|     if (fullStreamDecompression) { | ||||
|       stream.setFullStreamDecompression(fullStreamDecompression); | ||||
|     } | ||||
|     stream.setDecompressorRegistry(decompressorRegistry); | ||||
|     channelCallsTracer.reportCallStarted(); | ||||
|     stream.start(new ClientStreamListenerImpl(observer)); | ||||
| 
 | ||||
|     // Delay any sources of cancellation after start(), because most of the transports are broken if | ||||
|     // they receive cancel before start. Issue #1343 has more details | ||||
| 
 | ||||
|     // Propagate later Context cancellation to the remote side. | ||||
|     context.addListener(cancellationListener, directExecutor()); | ||||
|     if (effectiveDeadline != null | ||||
|         // If the context has the effective deadline, we don't need to schedule an extra task. | ||||
|         && !effectiveDeadline.equals(context.getDeadline()) | ||||
|         // If the channel has been terminated, we don't need to schedule an extra task. | ||||
|         && deadlineCancellationExecutor != null) { | ||||
|       deadlineCancellationFuture = startDeadlineTimer(effectiveDeadline); | ||||
|     } | ||||
|     if (cancelListenersShouldBeRemoved) { | ||||
|       // Race detected! ClientStreamListener.closed may have been called before | ||||
|       // deadlineCancellationFuture was set / context listener added, thereby preventing the future | ||||
|       // and listener from being cancelled. Go ahead and cancel again, just to be sure it | ||||
|       // was cancelled. | ||||
|       removeContextListenerAndCancelDeadlineFuture(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void applyMethodConfig() { | ||||
|     MethodInfo info = callOptions.getOption(MethodInfo.KEY); | ||||
|     if (info == null) { | ||||
|       return; | ||||
|     } | ||||
|     if (info.timeoutNanos != null) { | ||||
|       Deadline newDeadline = Deadline.after(info.timeoutNanos, TimeUnit.NANOSECONDS); | ||||
|       Deadline existingDeadline = callOptions.getDeadline(); | ||||
|       // If the new deadline is sooner than the existing deadline, swap them. | ||||
|       if (existingDeadline == null || newDeadline.compareTo(existingDeadline) < 0) { | ||||
|         callOptions = callOptions.withDeadline(newDeadline); | ||||
|       } | ||||
|     } | ||||
|     if (info.waitForReady != null) { | ||||
|       callOptions = | ||||
|           info.waitForReady ? callOptions.withWaitForReady() : callOptions.withoutWaitForReady(); | ||||
|     } | ||||
|     if (info.maxInboundMessageSize != null) { | ||||
|       Integer existingLimit = callOptions.getMaxInboundMessageSize(); | ||||
|       if (existingLimit != null) { | ||||
|         callOptions = | ||||
|             callOptions.withMaxInboundMessageSize( | ||||
|                 Math.min(existingLimit, info.maxInboundMessageSize)); | ||||
|       } else { | ||||
|         callOptions = callOptions.withMaxInboundMessageSize(info.maxInboundMessageSize); | ||||
|       } | ||||
|     } | ||||
|     if (info.maxOutboundMessageSize != null) { | ||||
|       Integer existingLimit = callOptions.getMaxOutboundMessageSize(); | ||||
|       if (existingLimit != null) { | ||||
|         callOptions = | ||||
|             callOptions.withMaxOutboundMessageSize( | ||||
|                 Math.min(existingLimit, info.maxOutboundMessageSize)); | ||||
|       } else { | ||||
|         callOptions = callOptions.withMaxOutboundMessageSize(info.maxOutboundMessageSize); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static void logIfContextNarrowedTimeout( | ||||
|       Deadline effectiveDeadline, | ||||
|       @Nullable Deadline outerCallDeadline, | ||||
|       @Nullable Deadline callDeadline) { | ||||
|     if (!log.isLoggable(Level.FINE) | ||||
|         || effectiveDeadline == null | ||||
|         || !effectiveDeadline.equals(outerCallDeadline)) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     long effectiveTimeout = max(0, effectiveDeadline.timeRemaining(TimeUnit.NANOSECONDS)); | ||||
|     StringBuilder builder = | ||||
|         new StringBuilder( | ||||
|             String.format( | ||||
|                 "Call timeout set to '%d' ns, due to context deadline.", effectiveTimeout)); | ||||
|     if (callDeadline == null) { | ||||
|       builder.append(" Explicit call timeout was not set."); | ||||
|     } else { | ||||
|       long callTimeout = callDeadline.timeRemaining(TimeUnit.NANOSECONDS); | ||||
|       builder.append(String.format(" Explicit call timeout was '%d' ns.", callTimeout)); | ||||
|     } | ||||
| 
 | ||||
|     log.fine(builder.toString()); | ||||
|   } | ||||
| 
 | ||||
|   private void removeContextListenerAndCancelDeadlineFuture() { | ||||
|     context.removeListener(cancellationListener); | ||||
|     ScheduledFuture<?> f = deadlineCancellationFuture; | ||||
|     if (f != null) { | ||||
|       f.cancel(false); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private class DeadlineTimer implements Runnable { | ||||
|     private final long remainingNanos; | ||||
| 
 | ||||
|     DeadlineTimer(long remainingNanos) { | ||||
|       this.remainingNanos = remainingNanos; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void run() { | ||||
|       InsightBuilder insight = new InsightBuilder(); | ||||
|       stream.appendTimeoutInsight(insight); | ||||
|       // DelayedStream.cancel() is safe to call from a thread that is different from where the | ||||
|       // stream is created. | ||||
|       long seconds = Math.abs(remainingNanos) / TimeUnit.SECONDS.toNanos(1); | ||||
|       long nanos = Math.abs(remainingNanos) % TimeUnit.SECONDS.toNanos(1); | ||||
| 
 | ||||
|       StringBuilder buf = new StringBuilder(); | ||||
|       buf.append("deadline exceeded after "); | ||||
|       if (remainingNanos < 0) { | ||||
|         buf.append('-'); | ||||
|       } | ||||
|       buf.append(seconds); | ||||
|       buf.append(String.format(Locale.US, ".%09d", nanos)); | ||||
|       buf.append("s. "); | ||||
|       buf.append(insight); | ||||
|       stream.cancel(DEADLINE_EXCEEDED.augmentDescription(buf.toString())); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private ScheduledFuture<?> startDeadlineTimer(Deadline deadline) { | ||||
|     long remainingNanos = deadline.timeRemaining(TimeUnit.NANOSECONDS); | ||||
|     return deadlineCancellationExecutor.schedule( | ||||
|         new LogExceptionRunnable(new DeadlineTimer(remainingNanos)), | ||||
|         remainingNanos, | ||||
|         TimeUnit.NANOSECONDS); | ||||
|   } | ||||
| 
 | ||||
|   @Nullable | ||||
|   private Deadline effectiveDeadline() { | ||||
|     // Call options and context are immutable, so we don't need to cache the deadline. | ||||
|     return min(callOptions.getDeadline(), context.getDeadline()); | ||||
|   } | ||||
| 
 | ||||
|   @Nullable | ||||
|   private static Deadline min(@Nullable Deadline deadline0, @Nullable Deadline deadline1) { | ||||
|     if (deadline0 == null) { | ||||
|       return deadline1; | ||||
|     } | ||||
|     if (deadline1 == null) { | ||||
|       return deadline0; | ||||
|     } | ||||
|     return deadline0.minimum(deadline1); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void request(int numMessages) { | ||||
|     PerfMark.startTask("ClientCall.request", tag); | ||||
|     try { | ||||
|       checkState(stream != null, "Not started"); | ||||
|       checkArgument(numMessages >= 0, "Number requested must be non-negative"); | ||||
|       stream.request(numMessages); | ||||
|     } finally { | ||||
|       PerfMark.stopTask("ClientCall.request", tag); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void cancel(@Nullable String message, @Nullable Throwable cause) { | ||||
|     PerfMark.startTask("ClientCall.cancel", tag); | ||||
|     try { | ||||
|       cancelInternal(message, cause); | ||||
|     } finally { | ||||
|       PerfMark.stopTask("ClientCall.cancel", tag); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void cancelInternal(@Nullable String message, @Nullable Throwable cause) { | ||||
|     if (message == null && cause == null) { | ||||
|       cause = new CancellationException("Cancelled without a message or cause"); | ||||
|       log.log(Level.WARNING, "Cancelling without a message or cause is suboptimal", cause); | ||||
|     } | ||||
|     if (cancelCalled) { | ||||
|       return; | ||||
|     } | ||||
|     cancelCalled = true; | ||||
|     try { | ||||
|       // Cancel is called in exception handling cases, so it may be the case that the | ||||
|       // stream was never successfully created or start has never been called. | ||||
|       if (stream != null) { | ||||
|         Status status = Status.CANCELLED; | ||||
|         if (message != null) { | ||||
|           status = status.withDescription(message); | ||||
|         } else { | ||||
|           status = status.withDescription("Call cancelled without message"); | ||||
|         } | ||||
|         if (cause != null) { | ||||
|           status = status.withCause(cause); | ||||
|         } | ||||
|         stream.cancel(status); | ||||
|       } | ||||
|     } finally { | ||||
|       removeContextListenerAndCancelDeadlineFuture(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void halfClose() { | ||||
|     PerfMark.startTask("ClientCall.halfClose", tag); | ||||
|     try { | ||||
|       halfCloseInternal(); | ||||
|     } finally { | ||||
|       PerfMark.stopTask("ClientCall.halfClose", tag); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void halfCloseInternal() { | ||||
|     checkState(stream != null, "Not started"); | ||||
|     checkState(!cancelCalled, "call was cancelled"); | ||||
|     checkState(!halfCloseCalled, "call already half-closed"); | ||||
|     halfCloseCalled = true; | ||||
|     stream.halfClose(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void sendMessage(ReqT message) { | ||||
|     PerfMark.startTask("ClientCall.sendMessage", tag); | ||||
|     try { | ||||
|       sendMessageInternal(message); | ||||
|     } finally { | ||||
|       PerfMark.stopTask("ClientCall.sendMessage", tag); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void sendMessageInternal(ReqT message) { | ||||
|     checkState(stream != null, "Not started"); | ||||
|     checkState(!cancelCalled, "call was cancelled"); | ||||
|     checkState(!halfCloseCalled, "call was half-closed"); | ||||
|     try { | ||||
|       if (stream instanceof RetriableStream) { | ||||
|         @SuppressWarnings("unchecked") | ||||
|         RetriableStream<ReqT> retriableStream = (RetriableStream<ReqT>) stream; | ||||
|         retriableStream.sendMessage(message); | ||||
|       } else { | ||||
|         stream.writeMessage(method.streamRequest(message)); | ||||
|       } | ||||
|     } catch (RuntimeException e) { | ||||
|       stream.cancel(Status.CANCELLED.withCause(e).withDescription("Failed to stream message")); | ||||
|       return; | ||||
|     } catch (Error e) { | ||||
|       stream.cancel(Status.CANCELLED.withDescription("Client sendMessage() failed with Error")); | ||||
|       throw e; | ||||
|     } | ||||
|     // For unary requests, we don't flush since we know that halfClose should be coming soon. This | ||||
|     // allows us to piggy-back the END_STREAM=true on the last message frame without opening the | ||||
|     // possibility of broken applications forgetting to call halfClose without noticing. | ||||
|     if (!unaryRequest) { | ||||
|       stream.flush(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void setMessageCompression(boolean enabled) { | ||||
|     checkState(stream != null, "Not started"); | ||||
|     stream.setMessageCompression(enabled); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean isReady() { | ||||
|     return stream.isReady(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Attributes getAttributes() { | ||||
|     if (stream != null) { | ||||
|       return stream.getAttributes(); | ||||
|     } | ||||
|     return Attributes.EMPTY; | ||||
|   } | ||||
| 
 | ||||
|   private void closeObserver(Listener<RespT> observer, Status status, Metadata trailers) { | ||||
|     observer.onClose(status, trailers); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return MoreObjects.toStringHelper(this).add("method", method).toString(); | ||||
|   } | ||||
| 
 | ||||
|   private class ClientStreamListenerImpl implements ClientStreamListener { | ||||
|     private final Listener<RespT> observer; | ||||
|     private Status exceptionStatus; | ||||
| 
 | ||||
|     public ClientStreamListenerImpl(Listener<RespT> observer) { | ||||
|       this.observer = checkNotNull(observer, "observer"); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Cancels call and schedules onClose() notification. May only be called from the application | ||||
|      * thread. | ||||
|      */ | ||||
|     private void exceptionThrown(Status status) { | ||||
|       // Since each RPC can have its own executor, we can only call onClose() when we are sure there | ||||
|       // will be no further callbacks. We set the status here and overwrite the onClose() details | ||||
|       // when it arrives. | ||||
|       exceptionStatus = status; | ||||
|       stream.cancel(status); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void headersRead(final Metadata headers) { | ||||
|       PerfMark.startTask("ClientStreamListener.headersRead", tag); | ||||
|       final Link link = PerfMark.linkOut(); | ||||
| 
 | ||||
|       final class HeadersRead extends ContextRunnable { | ||||
|         HeadersRead() { | ||||
|           super(context); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void runInContext() { | ||||
|           PerfMark.startTask("ClientCall$Listener.headersRead", tag); | ||||
|           PerfMark.linkIn(link); | ||||
|           try { | ||||
|             runInternal(); | ||||
|           } finally { | ||||
|             PerfMark.stopTask("ClientCall$Listener.headersRead", tag); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         private void runInternal() { | ||||
|           if (exceptionStatus != null) { | ||||
|             return; | ||||
|           } | ||||
|           try { | ||||
|             observer.onHeaders(headers); | ||||
|           } catch (Throwable t) { | ||||
|             exceptionThrown( | ||||
|                 Status.CANCELLED.withCause(t).withDescription("Failed to read headers")); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       try { | ||||
|         callExecutor.execute(new HeadersRead()); | ||||
|       } finally { | ||||
|         PerfMark.stopTask("ClientStreamListener.headersRead", tag); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void messagesAvailable(final MessageProducer producer) { | ||||
|       PerfMark.startTask("ClientStreamListener.messagesAvailable", tag); | ||||
|       final Link link = PerfMark.linkOut(); | ||||
| 
 | ||||
|       final class MessagesAvailable extends ContextRunnable { | ||||
|         MessagesAvailable() { | ||||
|           super(context); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void runInContext() { | ||||
|           PerfMark.startTask("ClientCall$Listener.messagesAvailable", tag); | ||||
|           PerfMark.linkIn(link); | ||||
|           try { | ||||
|             runInternal(); | ||||
|           } finally { | ||||
|             PerfMark.stopTask("ClientCall$Listener.messagesAvailable", tag); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         private void runInternal() { | ||||
|           if (exceptionStatus != null) { | ||||
|             GrpcUtil.closeQuietly(producer); | ||||
|             return; | ||||
|           } | ||||
|           try { | ||||
|             InputStream message; | ||||
|             while ((message = producer.next()) != null) { | ||||
|               try { | ||||
|                 observer.onMessage(method.parseResponse(message)); | ||||
|               } catch (Throwable t) { | ||||
|                 GrpcUtil.closeQuietly(message); | ||||
|                 throw t; | ||||
|               } | ||||
|               message.close(); | ||||
|             } | ||||
|           } catch (Throwable t) { | ||||
|             GrpcUtil.closeQuietly(producer); | ||||
|             exceptionThrown( | ||||
|                 Status.CANCELLED.withCause(t).withDescription("Failed to read message.")); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       try { | ||||
|         callExecutor.execute(new MessagesAvailable()); | ||||
|       } finally { | ||||
|         PerfMark.stopTask("ClientStreamListener.messagesAvailable", tag); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void closed(Status status, Metadata trailers) { | ||||
|       closed(status, RpcProgress.PROCESSED, trailers); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) { | ||||
|       PerfMark.startTask("ClientStreamListener.closed", tag); | ||||
|       try { | ||||
|         closedInternal(status, rpcProgress, trailers); | ||||
|       } finally { | ||||
|         PerfMark.stopTask("ClientStreamListener.closed", tag); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     private void closedInternal( | ||||
|         Status status, @SuppressWarnings("unused") RpcProgress rpcProgress, Metadata trailers) { | ||||
|       Deadline deadline = effectiveDeadline(); | ||||
|       if (status.getCode() == Status.Code.CANCELLED && deadline != null) { | ||||
|         // When the server's deadline expires, it can only reset the stream with CANCEL and no | ||||
|         // description. Since our timer may be delayed in firing, we double-check the deadline and | ||||
|         // turn the failure into the likely more helpful DEADLINE_EXCEEDED status. | ||||
|         if (deadline.isExpired()) { | ||||
|           InsightBuilder insight = new InsightBuilder(); | ||||
|           stream.appendTimeoutInsight(insight); | ||||
|           status = | ||||
|               DEADLINE_EXCEEDED.augmentDescription( | ||||
|                   "ClientCall was cancelled at or after deadline. " + insight); | ||||
|           // Replace trailers to prevent mixing sources of status and trailers. | ||||
|           trailers = new Metadata(); | ||||
|         } | ||||
|       } | ||||
|       final Status savedStatus = status; | ||||
|       final Metadata savedTrailers = trailers; | ||||
|       final Link link = PerfMark.linkOut(); | ||||
|       final class StreamClosed extends ContextRunnable { | ||||
|         StreamClosed() { | ||||
|           super(context); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void runInContext() { | ||||
|           PerfMark.startTask("ClientCall$Listener.onClose", tag); | ||||
|           PerfMark.linkIn(link); | ||||
|           try { | ||||
|             runInternal(); | ||||
|           } finally { | ||||
|             PerfMark.stopTask("ClientCall$Listener.onClose", tag); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         private void runInternal() { | ||||
|           Status status = savedStatus; | ||||
|           Metadata trailers = savedTrailers; | ||||
|           if (exceptionStatus != null) { | ||||
|             // Ideally exceptionStatus == savedStatus, as exceptionStatus was passed to cancel(). | ||||
|             // However the cancel is racy and this closed() may have already been queued when the | ||||
|             // cancellation occurred. Since other calls like onMessage() will throw away data if | ||||
|             // exceptionStatus != null, it is semantically essential that we _not_ use a status | ||||
|             // provided by the server. | ||||
|             status = exceptionStatus; | ||||
|             // Replace trailers to prevent mixing sources of status and trailers. | ||||
|             trailers = new Metadata(); | ||||
|           } | ||||
|           cancelListenersShouldBeRemoved = true; | ||||
|           try { | ||||
|             closeObserver(observer, status, trailers); | ||||
|           } finally { | ||||
|             removeContextListenerAndCancelDeadlineFuture(); | ||||
|             channelCallsTracer.reportCallEnded(status.isOk()); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       callExecutor.execute(new StreamClosed()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onReady() { | ||||
|       if (method.getType().clientSendsOneMessage()) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       PerfMark.startTask("ClientStreamListener.onReady", tag); | ||||
|       final Link link = PerfMark.linkOut(); | ||||
| 
 | ||||
|       final class StreamOnReady extends ContextRunnable { | ||||
|         StreamOnReady() { | ||||
|           super(context); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void runInContext() { | ||||
|           PerfMark.startTask("ClientCall$Listener.onReady", tag); | ||||
|           PerfMark.linkIn(link); | ||||
|           try { | ||||
|             runInternal(); | ||||
|           } finally { | ||||
|             PerfMark.stopTask("ClientCall$Listener.onReady", tag); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         private void runInternal() { | ||||
|           if (exceptionStatus != null) { | ||||
|             return; | ||||
|           } | ||||
|           try { | ||||
|             observer.onReady(); | ||||
|           } catch (Throwable t) { | ||||
|             exceptionThrown( | ||||
|                 Status.CANCELLED.withCause(t).withDescription("Failed to call onReady.")); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       try { | ||||
|         callExecutor.execute(new StreamOnReady()); | ||||
|       } finally { | ||||
|         PerfMark.stopTask("ClientStreamListener.onReady", tag); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,774 @@ | |||
| /* | ||||
|  * Copyright 2014 The gRPC Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package io.grpc.netty; | ||||
| 
 | ||||
| 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 static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS; | ||||
| import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED; | ||||
| 
 | ||||
| import com.google.common.annotations.VisibleForTesting; | ||||
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | ||||
| import io.grpc.Attributes; | ||||
| import io.grpc.CallCredentials; | ||||
| import io.grpc.ChannelCredentials; | ||||
| import io.grpc.ChannelLogger; | ||||
| import io.grpc.EquivalentAddressGroup; | ||||
| import io.grpc.ExperimentalApi; | ||||
| import io.grpc.HttpConnectProxiedSocketAddress; | ||||
| import io.grpc.Internal; | ||||
| import io.grpc.ManagedChannelBuilder; | ||||
| import io.grpc.internal.AbstractManagedChannelImplBuilder; | ||||
| import io.grpc.internal.AtomicBackoff; | ||||
| import io.grpc.internal.ClientTransportFactory; | ||||
| import io.grpc.internal.ConnectionClientTransport; | ||||
| import io.grpc.internal.FixedObjectPool; | ||||
| import io.grpc.internal.GrpcUtil; | ||||
| import io.grpc.internal.KeepAliveManager; | ||||
| import io.grpc.internal.ManagedChannelImplBuilder; | ||||
| import io.grpc.internal.ManagedChannelImplBuilder.ChannelBuilderDefaultPortProvider; | ||||
| import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder; | ||||
| import io.grpc.internal.ObjectPool; | ||||
| import io.grpc.internal.SharedResourcePool; | ||||
| import io.grpc.internal.TransportTracer; | ||||
| import io.grpc.netty.ProtocolNegotiators.FromChannelCredentialsResult; | ||||
| import io.netty.channel.Channel; | ||||
| import io.netty.channel.ChannelFactory; | ||||
| import io.netty.channel.ChannelOption; | ||||
| import io.netty.channel.EventLoopGroup; | ||||
| import io.netty.channel.ReflectiveChannelFactory; | ||||
| import io.netty.channel.socket.nio.NioSocketChannel; | ||||
| import io.netty.handler.ssl.SslContext; | ||||
| import java.net.InetSocketAddress; | ||||
| import java.net.SocketAddress; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import javax.annotation.CheckReturnValue; | ||||
| import javax.annotation.Nullable; | ||||
| import javax.net.ssl.SSLException; | ||||
| 
 | ||||
| /** A builder to help simplify construction of channels using the Netty transport. */ | ||||
| @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784") | ||||
| @CanIgnoreReturnValue | ||||
| public final class NettyChannelBuilder | ||||
|     extends AbstractManagedChannelImplBuilder<NettyChannelBuilder> { | ||||
| 
 | ||||
|   // 1MiB. | ||||
|   public static final int DEFAULT_FLOW_CONTROL_WINDOW = 1024 * 1024; | ||||
|   private static final boolean DEFAULT_AUTO_FLOW_CONTROL; | ||||
| 
 | ||||
|   private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L); | ||||
| 
 | ||||
|   private static final ChannelFactory<? extends Channel> DEFAULT_CHANNEL_FACTORY = | ||||
|       new ReflectiveChannelFactory<>(Utils.DEFAULT_CLIENT_CHANNEL_TYPE); | ||||
|   private static final ObjectPool<? extends EventLoopGroup> DEFAULT_EVENT_LOOP_GROUP_POOL = | ||||
|       SharedResourcePool.forResource(Utils.DEFAULT_WORKER_EVENT_LOOP_GROUP); | ||||
| 
 | ||||
|   static { | ||||
|     String autoFlowControl = System.getenv("GRPC_EXPERIMENTAL_AUTOFLOWCONTROL"); | ||||
|     if (autoFlowControl == null) { | ||||
|       autoFlowControl = "true"; | ||||
|     } | ||||
|     DEFAULT_AUTO_FLOW_CONTROL = Boolean.parseBoolean(autoFlowControl); | ||||
|   } | ||||
| 
 | ||||
|   private final ManagedChannelImplBuilder managedChannelImplBuilder; | ||||
|   private TransportTracer.Factory transportTracerFactory = TransportTracer.getDefaultFactory(); | ||||
|   private final Map<ChannelOption<?>, Object> channelOptions = new HashMap<>(); | ||||
|   private ChannelFactory<? extends Channel> channelFactory = DEFAULT_CHANNEL_FACTORY; | ||||
|   private ObjectPool<? extends EventLoopGroup> eventLoopGroupPool = DEFAULT_EVENT_LOOP_GROUP_POOL; | ||||
|   private boolean autoFlowControl = DEFAULT_AUTO_FLOW_CONTROL; | ||||
|   private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW; | ||||
|   private int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; | ||||
|   private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE; | ||||
|   private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED; | ||||
|   private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS; | ||||
|   private boolean keepAliveWithoutCalls; | ||||
|   private ProtocolNegotiator.ClientFactory protocolNegotiatorFactory = | ||||
|       new DefaultProtocolNegotiator(); | ||||
|   private final boolean freezeProtocolNegotiatorFactory; | ||||
|   private LocalSocketPicker localSocketPicker; | ||||
| 
 | ||||
|   /** | ||||
|    * If true, indicates that the transport may use the GET method for RPCs, and may include the | ||||
|    * request body in the query params. | ||||
|    */ | ||||
|   private final boolean useGetForSafeMethods = false; | ||||
| 
 | ||||
|   /** | ||||
|    * Creates a new builder with the given server address. This factory method is primarily intended | ||||
|    * for using Netty Channel types other than SocketChannel. {@link #forAddress(String, int)} should | ||||
|    * generally be preferred over this method, since that API permits delaying DNS lookups and | ||||
|    * noticing changes to DNS. If an unresolved InetSocketAddress is passed in, then it will remain | ||||
|    * unresolved. | ||||
|    */ | ||||
|   @CheckReturnValue | ||||
|   public static NettyChannelBuilder forAddress(SocketAddress serverAddress) { | ||||
|     return new NettyChannelBuilder(serverAddress); | ||||
|   } | ||||
| 
 | ||||
|   /** Creates a new builder with the given host and port. */ | ||||
|   @CheckReturnValue | ||||
|   public static NettyChannelBuilder forAddress(String host, int port) { | ||||
|     return forTarget(GrpcUtil.authorityFromHostAndPort(host, port)); | ||||
|   } | ||||
| 
 | ||||
|   /** Creates a new builder with the given host and port. */ | ||||
|   @CheckReturnValue | ||||
|   public static NettyChannelBuilder forAddress(String host, int port, ChannelCredentials creds) { | ||||
|     return forTarget(GrpcUtil.authorityFromHostAndPort(host, port), creds); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Creates a new builder with the given target string that will be resolved by {@link | ||||
|    * io.grpc.NameResolver}. | ||||
|    */ | ||||
|   @CheckReturnValue | ||||
|   public static NettyChannelBuilder forTarget(String target) { | ||||
|     return new NettyChannelBuilder(target); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Creates a new builder with the given target string that will be resolved by {@link | ||||
|    * io.grpc.NameResolver}. | ||||
|    */ | ||||
|   @CheckReturnValue | ||||
|   public static NettyChannelBuilder forTarget(String target, ChannelCredentials creds) { | ||||
|     FromChannelCredentialsResult result = ProtocolNegotiators.from(creds); | ||||
|     if (result.error != null) { | ||||
|       throw new IllegalArgumentException(result.error); | ||||
|     } | ||||
|     return new NettyChannelBuilder(target, creds, result.callCredentials, result.negotiator); | ||||
|   } | ||||
| 
 | ||||
|   private final class NettyChannelTransportFactoryBuilder implements ClientTransportFactoryBuilder { | ||||
|     @Override | ||||
|     public ClientTransportFactory buildClientTransportFactory() { | ||||
|       return buildTransportFactory(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private final class NettyChannelDefaultPortProvider implements ChannelBuilderDefaultPortProvider { | ||||
|     @Override | ||||
|     public int getDefaultPort() { | ||||
|       return protocolNegotiatorFactory.getDefaultPort(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @CheckReturnValue | ||||
|   NettyChannelBuilder(String target) { | ||||
|     managedChannelImplBuilder = | ||||
|         new ManagedChannelImplBuilder( | ||||
|             target, | ||||
|             new NettyChannelTransportFactoryBuilder(), | ||||
|             new NettyChannelDefaultPortProvider()); | ||||
|     this.freezeProtocolNegotiatorFactory = false; | ||||
|   } | ||||
| 
 | ||||
|   NettyChannelBuilder( | ||||
|       String target, | ||||
|       ChannelCredentials channelCreds, | ||||
|       CallCredentials callCreds, | ||||
|       ProtocolNegotiator.ClientFactory negotiator) { | ||||
|     managedChannelImplBuilder = | ||||
|         new ManagedChannelImplBuilder( | ||||
|             target, | ||||
|             channelCreds, | ||||
|             callCreds, | ||||
|             new NettyChannelTransportFactoryBuilder(), | ||||
|             new NettyChannelDefaultPortProvider()); | ||||
|     this.protocolNegotiatorFactory = checkNotNull(negotiator, "negotiator"); | ||||
|     this.freezeProtocolNegotiatorFactory = true; | ||||
|   } | ||||
| 
 | ||||
|   @CheckReturnValue | ||||
|   NettyChannelBuilder(SocketAddress address) { | ||||
|     managedChannelImplBuilder = | ||||
|         new ManagedChannelImplBuilder( | ||||
|             address, | ||||
|             getAuthorityFromAddress(address), | ||||
|             new NettyChannelTransportFactoryBuilder(), | ||||
|             new NettyChannelDefaultPortProvider()); | ||||
|     this.freezeProtocolNegotiatorFactory = false; | ||||
|   } | ||||
| 
 | ||||
|   @Internal | ||||
|   @Override | ||||
|   protected ManagedChannelBuilder<?> delegate() { | ||||
|     return managedChannelImplBuilder; | ||||
|   } | ||||
| 
 | ||||
|   @CheckReturnValue | ||||
|   private static String getAuthorityFromAddress(SocketAddress address) { | ||||
|     if (address instanceof InetSocketAddress) { | ||||
|       InetSocketAddress inetAddress = (InetSocketAddress) address; | ||||
|       return GrpcUtil.authorityFromHostAndPort(inetAddress.getHostString(), inetAddress.getPort()); | ||||
|     } else { | ||||
|       return address.toString(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Specifies the channel type to use, by default we use {@code EpollSocketChannel} if available, | ||||
|    * otherwise using {@link NioSocketChannel}. | ||||
|    * | ||||
|    * <p>You either use this or {@link #channelFactory(io.netty.channel.ChannelFactory)} if your | ||||
|    * {@link Channel} implementation has no no-args constructor. | ||||
|    * | ||||
|    * <p>It's an optional parameter. If the user has not provided an Channel type or ChannelFactory | ||||
|    * when the channel is built, the builder will use the default one which is static. | ||||
|    * | ||||
|    * <p>You must also provide corresponding {@link #eventLoopGroup(EventLoopGroup)}. For example, | ||||
|    * {@link NioSocketChannel} must use {@link io.netty.channel.nio.NioEventLoopGroup}, otherwise | ||||
|    * your application won't start. | ||||
|    */ | ||||
|   public NettyChannelBuilder channelType(Class<? extends Channel> channelType) { | ||||
|     checkNotNull(channelType, "channelType"); | ||||
|     return channelFactory(new ReflectiveChannelFactory<>(channelType)); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Specifies the {@link ChannelFactory} to create {@link Channel} instances. This method is | ||||
|    * usually only used if the specific {@code Channel} requires complex logic which requires | ||||
|    * additional information to create the {@code Channel}. Otherwise, recommend to use {@link | ||||
|    * #channelType(Class)}. | ||||
|    * | ||||
|    * <p>It's an optional parameter. If the user has not provided an Channel type or ChannelFactory | ||||
|    * when the channel is built, the builder will use the default one which is static. | ||||
|    * | ||||
|    * <p>You must also provide corresponding {@link #eventLoopGroup(EventLoopGroup)}. For example, | ||||
|    * {@link NioSocketChannel} based {@link ChannelFactory} must use {@link | ||||
|    * io.netty.channel.nio.NioEventLoopGroup}, otherwise your application won't start. | ||||
|    */ | ||||
|   public NettyChannelBuilder channelFactory(ChannelFactory<? extends Channel> channelFactory) { | ||||
|     this.channelFactory = checkNotNull(channelFactory, "channelFactory"); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Specifies a channel option. As the underlying channel as well as network implementation may | ||||
|    * ignore this value applications should consider it a hint. | ||||
|    */ | ||||
|   public <T> NettyChannelBuilder withOption(ChannelOption<T> option, T value) { | ||||
|     channelOptions.put(option, value); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the negotiation type for the HTTP/2 connection. | ||||
|    * | ||||
|    * <p>Default: <code>TLS</code> | ||||
|    */ | ||||
|   public NettyChannelBuilder negotiationType(NegotiationType type) { | ||||
|     checkState( | ||||
|         !freezeProtocolNegotiatorFactory, "Cannot change security when using ChannelCredentials"); | ||||
|     if (!(protocolNegotiatorFactory instanceof DefaultProtocolNegotiator)) { | ||||
|       // Do nothing for compatibility | ||||
|       return this; | ||||
|     } | ||||
|     ((DefaultProtocolNegotiator) protocolNegotiatorFactory).negotiationType = type; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Provides an EventGroupLoop to be used by the netty transport. | ||||
|    * | ||||
|    * <p>It's an optional parameter. If the user has not provided an EventGroupLoop when the channel | ||||
|    * is built, the builder will use the default one which is static. | ||||
|    * | ||||
|    * <p>You must also provide corresponding {@link #channelType(Class)} or {@link | ||||
|    * #channelFactory(ChannelFactory)} corresponding to the given {@code EventLoopGroup}. For | ||||
|    * example, {@link io.netty.channel.nio.NioEventLoopGroup} requires {@link NioSocketChannel} | ||||
|    * | ||||
|    * <p>The channel won't take ownership of the given EventLoopGroup. It's caller's responsibility | ||||
|    * to shut it down when it's desired. | ||||
|    */ | ||||
|   public NettyChannelBuilder eventLoopGroup(@Nullable EventLoopGroup eventLoopGroup) { | ||||
|     if (eventLoopGroup != null) { | ||||
|       return eventLoopGroupPool(new FixedObjectPool<>(eventLoopGroup)); | ||||
|     } | ||||
|     return eventLoopGroupPool(DEFAULT_EVENT_LOOP_GROUP_POOL); | ||||
|   } | ||||
| 
 | ||||
|   NettyChannelBuilder eventLoopGroupPool(ObjectPool<? extends EventLoopGroup> eventLoopGroupPool) { | ||||
|     this.eventLoopGroupPool = checkNotNull(eventLoopGroupPool, "eventLoopGroupPool"); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * SSL/TLS context to use instead of the system default. It must have been configured with {@link | ||||
|    * GrpcSslContexts}, but options could have been overridden. | ||||
|    */ | ||||
|   public NettyChannelBuilder sslContext(SslContext sslContext) { | ||||
|     checkState( | ||||
|         !freezeProtocolNegotiatorFactory, "Cannot change security when using ChannelCredentials"); | ||||
|     if (sslContext != null) { | ||||
|       checkArgument(sslContext.isClient(), "Server SSL context can not be used for client channel"); | ||||
|       GrpcSslContexts.ensureAlpnAndH2Enabled(sslContext.applicationProtocolNegotiator()); | ||||
|     } | ||||
|     if (!(protocolNegotiatorFactory instanceof DefaultProtocolNegotiator)) { | ||||
|       // Do nothing for compatibility | ||||
|       return this; | ||||
|     } | ||||
|     ((DefaultProtocolNegotiator) protocolNegotiatorFactory).sslContext = sslContext; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the initial flow control window in bytes. Setting initial flow control window enables auto | ||||
|    * flow control tuning using bandwidth-delay product algorithm. To disable auto flow control | ||||
|    * tuning, use {@link #flowControlWindow(int)}. By default, auto flow control is enabled with | ||||
|    * initial flow control window size of {@link #DEFAULT_FLOW_CONTROL_WINDOW}. | ||||
|    */ | ||||
|   public NettyChannelBuilder initialFlowControlWindow(int initialFlowControlWindow) { | ||||
|     checkArgument(initialFlowControlWindow > 0, "initialFlowControlWindow must be positive"); | ||||
|     this.flowControlWindow = initialFlowControlWindow; | ||||
|     this.autoFlowControl = true; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the flow control window in bytes. Setting flowControlWindow disables auto flow control | ||||
|    * tuning; use {@link #initialFlowControlWindow(int)} to enable auto flow control tuning. If not | ||||
|    * called, the default value is {@link #DEFAULT_FLOW_CONTROL_WINDOW}) with auto flow control | ||||
|    * tuning. | ||||
|    */ | ||||
|   public NettyChannelBuilder flowControlWindow(int flowControlWindow) { | ||||
|     checkArgument(flowControlWindow > 0, "flowControlWindow must be positive"); | ||||
|     this.flowControlWindow = flowControlWindow; | ||||
|     this.autoFlowControl = false; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the maximum size of header list allowed to be received. This is cumulative size of the | ||||
|    * headers with some overhead, as defined for <a | ||||
|    * href="http://httpwg.org/specs/rfc7540.html#rfc.section.6.5.2">HTTP/2's | ||||
|    * SETTINGS_MAX_HEADER_LIST_SIZE</a>. The default is 8 KiB. | ||||
|    * | ||||
|    * @deprecated Use {@link #maxInboundMetadataSize} instead | ||||
|    */ | ||||
|   @Deprecated | ||||
|   public NettyChannelBuilder maxHeaderListSize(int maxHeaderListSize) { | ||||
|     return maxInboundMetadataSize(maxHeaderListSize); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the maximum size of metadata allowed to be received. This is cumulative size of the | ||||
|    * entries with some overhead, as defined for <a | ||||
|    * href="http://httpwg.org/specs/rfc7540.html#rfc.section.6.5.2">HTTP/2's | ||||
|    * SETTINGS_MAX_HEADER_LIST_SIZE</a>. The default is 8 KiB. | ||||
|    * | ||||
|    * @param bytes the maximum size of received metadata | ||||
|    * @return this | ||||
|    * @throws IllegalArgumentException if bytes is non-positive | ||||
|    * @since 1.17.0 | ||||
|    */ | ||||
|   @Override | ||||
|   public NettyChannelBuilder maxInboundMetadataSize(int bytes) { | ||||
|     checkArgument(bytes > 0, "maxInboundMetadataSize must be > 0"); | ||||
|     this.maxHeaderListSize = bytes; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** Equivalent to using {@link #negotiationType(NegotiationType)} with {@code PLAINTEXT}. */ | ||||
|   @Override | ||||
|   public NettyChannelBuilder usePlaintext() { | ||||
|     negotiationType(NegotiationType.PLAINTEXT); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** Equivalent to using {@link #negotiationType(NegotiationType)} with {@code TLS}. */ | ||||
|   @Override | ||||
|   public NettyChannelBuilder useTransportSecurity() { | ||||
|     negotiationType(NegotiationType.TLS); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    * | ||||
|    * @since 1.3.0 | ||||
|    */ | ||||
|   @Override | ||||
|   public NettyChannelBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) { | ||||
|     checkArgument(keepAliveTime > 0L, "keepalive time must be positive"); | ||||
|     keepAliveTimeNanos = timeUnit.toNanos(keepAliveTime); | ||||
|     keepAliveTimeNanos = KeepAliveManager.clampKeepAliveTimeInNanos(keepAliveTimeNanos); | ||||
|     if (keepAliveTimeNanos >= AS_LARGE_AS_INFINITE) { | ||||
|       // Bump keepalive time to infinite. This disables keepalive. | ||||
|       keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED; | ||||
|     } | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    * | ||||
|    * @since 1.3.0 | ||||
|    */ | ||||
|   @Override | ||||
|   public NettyChannelBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) { | ||||
|     checkArgument(keepAliveTimeout > 0L, "keepalive timeout must be positive"); | ||||
|     keepAliveTimeoutNanos = timeUnit.toNanos(keepAliveTimeout); | ||||
|     keepAliveTimeoutNanos = KeepAliveManager.clampKeepAliveTimeoutInNanos(keepAliveTimeoutNanos); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    * | ||||
|    * @since 1.3.0 | ||||
|    */ | ||||
|   @Override | ||||
|   public NettyChannelBuilder keepAliveWithoutCalls(boolean enable) { | ||||
|     keepAliveWithoutCalls = enable; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** If non-{@code null}, attempts to create connections bound to a local port. */ | ||||
|   public NettyChannelBuilder localSocketPicker(@Nullable LocalSocketPicker localSocketPicker) { | ||||
|     this.localSocketPicker = localSocketPicker; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * This class is meant to be overriden with a custom implementation of {@link | ||||
|    * #createSocketAddress}. The default implementation is a no-op. | ||||
|    * | ||||
|    * @since 1.16.0 | ||||
|    */ | ||||
|   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4917") | ||||
|   public static class LocalSocketPicker { | ||||
| 
 | ||||
|     /** | ||||
|      * Called by gRPC to pick local socket to bind to. This may be called multiple times. Subclasses | ||||
|      * are expected to override this method. | ||||
|      * | ||||
|      * @param remoteAddress the remote address to connect to. | ||||
|      * @param attrs the Attributes present on the {@link io.grpc.EquivalentAddressGroup} associated | ||||
|      *     with the address. | ||||
|      * @return a {@link SocketAddress} suitable for binding, or else {@code null}. | ||||
|      * @since 1.16.0 | ||||
|      */ | ||||
|     @Nullable | ||||
|     public SocketAddress createSocketAddress( | ||||
|         SocketAddress remoteAddress, @EquivalentAddressGroup.Attr Attributes attrs) { | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the maximum message size allowed for a single gRPC frame. If an inbound messages larger | ||||
|    * than this limit is received it will not be processed and the RPC will fail with | ||||
|    * RESOURCE_EXHAUSTED. | ||||
|    */ | ||||
|   @Override | ||||
|   public NettyChannelBuilder maxInboundMessageSize(int max) { | ||||
|     checkArgument(max >= 0, "negative max"); | ||||
|     maxInboundMessageSize = max; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   @CheckReturnValue | ||||
|   ClientTransportFactory buildTransportFactory() { | ||||
|     assertEventLoopAndChannelType(); | ||||
| 
 | ||||
|     ProtocolNegotiator negotiator = protocolNegotiatorFactory.newNegotiator(); | ||||
|     return new NettyTransportFactory( | ||||
|         negotiator, | ||||
|         channelFactory, | ||||
|         channelOptions, | ||||
|         eventLoopGroupPool, | ||||
|         autoFlowControl, | ||||
|         flowControlWindow, | ||||
|         maxInboundMessageSize, | ||||
|         maxHeaderListSize, | ||||
|         keepAliveTimeNanos, | ||||
|         keepAliveTimeoutNanos, | ||||
|         keepAliveWithoutCalls, | ||||
|         transportTracerFactory, | ||||
|         localSocketPicker, | ||||
|         useGetForSafeMethods); | ||||
|   } | ||||
| 
 | ||||
|   @VisibleForTesting | ||||
|   void assertEventLoopAndChannelType() { | ||||
|     boolean bothProvided = | ||||
|         channelFactory != DEFAULT_CHANNEL_FACTORY | ||||
|             && eventLoopGroupPool != DEFAULT_EVENT_LOOP_GROUP_POOL; | ||||
|     boolean nonProvided = | ||||
|         channelFactory == DEFAULT_CHANNEL_FACTORY | ||||
|             && eventLoopGroupPool == DEFAULT_EVENT_LOOP_GROUP_POOL; | ||||
|     checkState( | ||||
|         bothProvided || nonProvided, | ||||
|         "Both EventLoopGroup and ChannelType should be provided or neither should be"); | ||||
|   } | ||||
| 
 | ||||
|   @CheckReturnValue | ||||
|   int getDefaultPort() { | ||||
|     return protocolNegotiatorFactory.getDefaultPort(); | ||||
|   } | ||||
| 
 | ||||
|   @VisibleForTesting | ||||
|   @CheckReturnValue | ||||
|   static ProtocolNegotiator createProtocolNegotiatorByType( | ||||
|       NegotiationType negotiationType, | ||||
|       SslContext sslContext, | ||||
|       ObjectPool<? extends Executor> executorPool) { | ||||
|     switch (negotiationType) { | ||||
|       case PLAINTEXT: | ||||
|         return ProtocolNegotiators.plaintext(); | ||||
|       case PLAINTEXT_UPGRADE: | ||||
|         return ProtocolNegotiators.plaintextUpgrade(); | ||||
|       case TLS: | ||||
|         return ProtocolNegotiators.tls(sslContext, executorPool); | ||||
|       default: | ||||
|         throw new IllegalArgumentException("Unsupported negotiationType: " + negotiationType); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   NettyChannelBuilder disableCheckAuthority() { | ||||
|     this.managedChannelImplBuilder.disableCheckAuthority(); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   NettyChannelBuilder enableCheckAuthority() { | ||||
|     this.managedChannelImplBuilder.enableCheckAuthority(); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   void protocolNegotiatorFactory(ProtocolNegotiator.ClientFactory protocolNegotiatorFactory) { | ||||
|     checkState( | ||||
|         !freezeProtocolNegotiatorFactory, "Cannot change security when using ChannelCredentials"); | ||||
|     this.protocolNegotiatorFactory = | ||||
|         checkNotNull(protocolNegotiatorFactory, "protocolNegotiatorFactory"); | ||||
|   } | ||||
| 
 | ||||
|   void setTracingEnabled(boolean value) { | ||||
|     this.managedChannelImplBuilder.setTracingEnabled(value); | ||||
|   } | ||||
| 
 | ||||
|   void setStatsEnabled(boolean value) { | ||||
|     this.managedChannelImplBuilder.setStatsEnabled(value); | ||||
|   } | ||||
| 
 | ||||
|   void setStatsRecordStartedRpcs(boolean value) { | ||||
|     this.managedChannelImplBuilder.setStatsRecordStartedRpcs(value); | ||||
|   } | ||||
| 
 | ||||
|   void setStatsRecordFinishedRpcs(boolean value) { | ||||
|     this.managedChannelImplBuilder.setStatsRecordFinishedRpcs(value); | ||||
|   } | ||||
| 
 | ||||
|   void setStatsRecordRealTimeMetrics(boolean value) { | ||||
|     this.managedChannelImplBuilder.setStatsRecordRealTimeMetrics(value); | ||||
|   } | ||||
| 
 | ||||
|   @VisibleForTesting | ||||
|   NettyChannelBuilder setTransportTracerFactory(TransportTracer.Factory transportTracerFactory) { | ||||
|     this.transportTracerFactory = transportTracerFactory; | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   private final class DefaultProtocolNegotiator implements ProtocolNegotiator.ClientFactory { | ||||
|     private NegotiationType negotiationType = NegotiationType.TLS; | ||||
|     private SslContext sslContext; | ||||
| 
 | ||||
|     @Override | ||||
|     public ProtocolNegotiator newNegotiator() { | ||||
|       SslContext localSslContext = sslContext; | ||||
|       if (negotiationType == NegotiationType.TLS && localSslContext == null) { | ||||
|         try { | ||||
|           localSslContext = GrpcSslContexts.forClient().build(); | ||||
|         } catch (SSLException ex) { | ||||
|           throw new RuntimeException(ex); | ||||
|         } | ||||
|       } | ||||
|       return createProtocolNegotiatorByType( | ||||
|           negotiationType, localSslContext, managedChannelImplBuilder.getOffloadExecutorPool()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getDefaultPort() { | ||||
|       switch (negotiationType) { | ||||
|         case PLAINTEXT: | ||||
|         case PLAINTEXT_UPGRADE: | ||||
|           return GrpcUtil.DEFAULT_PORT_PLAINTEXT; | ||||
|         case TLS: | ||||
|           return GrpcUtil.DEFAULT_PORT_SSL; | ||||
|         default: | ||||
|           throw new AssertionError(negotiationType + " not handled"); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** Creates Netty transports. Exposed for internal use, as it should be private. */ | ||||
|   @CheckReturnValue | ||||
|   private static final class NettyTransportFactory implements ClientTransportFactory { | ||||
|     private final ProtocolNegotiator protocolNegotiator; | ||||
|     private final ChannelFactory<? extends Channel> channelFactory; | ||||
|     private final Map<ChannelOption<?>, ?> channelOptions; | ||||
|     private final ObjectPool<? extends EventLoopGroup> groupPool; | ||||
|     private final EventLoopGroup group; | ||||
|     private final boolean autoFlowControl; | ||||
|     private final int flowControlWindow; | ||||
|     private final int maxMessageSize; | ||||
|     private final int maxHeaderListSize; | ||||
|     private final long keepAliveTimeNanos; | ||||
|     private final AtomicBackoff keepAliveBackoff; | ||||
|     private final long keepAliveTimeoutNanos; | ||||
|     private final boolean keepAliveWithoutCalls; | ||||
|     private final TransportTracer.Factory transportTracerFactory; | ||||
|     private final LocalSocketPicker localSocketPicker; | ||||
|     private final boolean useGetForSafeMethods; | ||||
| 
 | ||||
|     private boolean closed; | ||||
| 
 | ||||
|     NettyTransportFactory( | ||||
|         ProtocolNegotiator protocolNegotiator, | ||||
|         ChannelFactory<? extends Channel> channelFactory, | ||||
|         Map<ChannelOption<?>, ?> channelOptions, | ||||
|         ObjectPool<? extends EventLoopGroup> groupPool, | ||||
|         boolean autoFlowControl, | ||||
|         int flowControlWindow, | ||||
|         int maxMessageSize, | ||||
|         int maxHeaderListSize, | ||||
|         long keepAliveTimeNanos, | ||||
|         long keepAliveTimeoutNanos, | ||||
|         boolean keepAliveWithoutCalls, | ||||
|         TransportTracer.Factory transportTracerFactory, | ||||
|         LocalSocketPicker localSocketPicker, | ||||
|         boolean useGetForSafeMethods) { | ||||
|       this.protocolNegotiator = checkNotNull(protocolNegotiator, "protocolNegotiator"); | ||||
|       this.channelFactory = channelFactory; | ||||
|       this.channelOptions = new HashMap<ChannelOption<?>, Object>(channelOptions); | ||||
|       this.groupPool = groupPool; | ||||
|       this.group = groupPool.getObject(); | ||||
|       this.autoFlowControl = autoFlowControl; | ||||
|       this.flowControlWindow = flowControlWindow; | ||||
|       this.maxMessageSize = maxMessageSize; | ||||
|       this.maxHeaderListSize = maxHeaderListSize; | ||||
|       this.keepAliveTimeNanos = keepAliveTimeNanos; | ||||
|       this.keepAliveBackoff = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos); | ||||
|       this.keepAliveTimeoutNanos = keepAliveTimeoutNanos; | ||||
|       this.keepAliveWithoutCalls = keepAliveWithoutCalls; | ||||
|       this.transportTracerFactory = transportTracerFactory; | ||||
|       this.localSocketPicker = | ||||
|           localSocketPicker != null ? localSocketPicker : new LocalSocketPicker(); | ||||
|       this.useGetForSafeMethods = useGetForSafeMethods; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ConnectionClientTransport newClientTransport( | ||||
|         SocketAddress serverAddress, ClientTransportOptions options, ChannelLogger channelLogger) { | ||||
|       checkState(!closed, "The transport factory is closed."); | ||||
| 
 | ||||
|       ProtocolNegotiator localNegotiator = protocolNegotiator; | ||||
|       HttpConnectProxiedSocketAddress proxiedAddr = options.getHttpConnectProxiedSocketAddress(); | ||||
|       if (proxiedAddr != null) { | ||||
|         serverAddress = proxiedAddr.getTargetAddress(); | ||||
|         localNegotiator = | ||||
|             ProtocolNegotiators.httpProxy( | ||||
|                 proxiedAddr.getProxyAddress(), | ||||
|                 proxiedAddr.getUsername(), | ||||
|                 proxiedAddr.getPassword(), | ||||
|                 protocolNegotiator); | ||||
|       } | ||||
| 
 | ||||
|       final AtomicBackoff.State keepAliveTimeNanosState = keepAliveBackoff.getState(); | ||||
|       Runnable tooManyPingsRunnable = | ||||
|           new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|               keepAliveTimeNanosState.backoff(); | ||||
|             } | ||||
|           }; | ||||
| 
 | ||||
|       // TODO(carl-mastrangelo): Pass channelLogger in. | ||||
|       NettyClientTransport transport = | ||||
|           new NettyClientTransport( | ||||
|               serverAddress, | ||||
|               channelFactory, | ||||
|               channelOptions, | ||||
|               group, | ||||
|               localNegotiator, | ||||
|               autoFlowControl, | ||||
|               flowControlWindow, | ||||
|               maxMessageSize, | ||||
|               maxHeaderListSize, | ||||
|               keepAliveTimeNanosState.get(), | ||||
|               keepAliveTimeoutNanos, | ||||
|               keepAliveWithoutCalls, | ||||
|               options.getAuthority(), | ||||
|               options.getUserAgent(), | ||||
|               tooManyPingsRunnable, | ||||
|               transportTracerFactory.create(), | ||||
|               options.getEagAttributes(), | ||||
|               localSocketPicker, | ||||
|               channelLogger, | ||||
|               useGetForSafeMethods); | ||||
|       return transport; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ScheduledExecutorService getScheduledExecutorService() { | ||||
|       return group; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public SwapChannelCredentialsResult swapChannelCredentials(ChannelCredentials channelCreds) { | ||||
|       checkNotNull(channelCreds, "channelCreds"); | ||||
|       FromChannelCredentialsResult result = ProtocolNegotiators.from(channelCreds); | ||||
|       if (result.error != null) { | ||||
|         return null; | ||||
|       } | ||||
|       ClientTransportFactory factory = | ||||
|           new NettyTransportFactory( | ||||
|               result.negotiator.newNegotiator(), | ||||
|               channelFactory, | ||||
|               channelOptions, | ||||
|               groupPool, | ||||
|               autoFlowControl, | ||||
|               flowControlWindow, | ||||
|               maxMessageSize, | ||||
|               maxHeaderListSize, | ||||
|               keepAliveTimeNanos, | ||||
|               keepAliveTimeoutNanos, | ||||
|               keepAliveWithoutCalls, | ||||
|               transportTracerFactory, | ||||
|               localSocketPicker, | ||||
|               useGetForSafeMethods); | ||||
|       return new SwapChannelCredentialsResult(factory, result.callCredentials); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void close() { | ||||
|       if (closed) { | ||||
|         return; | ||||
|       } | ||||
|       closed = true; | ||||
| 
 | ||||
|       protocolNegotiator.close(); | ||||
|       groupPool.returnObject(group); | ||||
|     } | ||||
|   } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,353 @@ | |||
| /* | ||||
|  * Copyright 2015 The gRPC Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package io.grpc.netty; | ||||
| 
 | ||||
| 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 static io.netty.buffer.Unpooled.EMPTY_BUFFER; | ||||
| 
 | ||||
| import com.google.common.base.Preconditions; | ||||
| import com.google.common.io.BaseEncoding; | ||||
| import io.grpc.Attributes; | ||||
| import io.grpc.CallOptions; | ||||
| import io.grpc.InternalKnownTransport; | ||||
| import io.grpc.InternalMethodDescriptor; | ||||
| import io.grpc.Metadata; | ||||
| import io.grpc.MethodDescriptor; | ||||
| import io.grpc.Status; | ||||
| import io.grpc.internal.AbstractClientStream; | ||||
| import io.grpc.internal.Http2ClientStreamTransportState; | ||||
| import io.grpc.internal.StatsTraceContext; | ||||
| import io.grpc.internal.TransportTracer; | ||||
| import io.grpc.internal.WritableBuffer; | ||||
| import io.netty.buffer.ByteBuf; | ||||
| import io.netty.channel.Channel; | ||||
| import io.netty.channel.ChannelFuture; | ||||
| import io.netty.channel.ChannelFutureListener; | ||||
| import io.netty.channel.EventLoop; | ||||
| import io.netty.handler.codec.http2.Http2Headers; | ||||
| import io.netty.handler.codec.http2.Http2Stream; | ||||
| import io.netty.util.AsciiString; | ||||
| import io.perfmark.PerfMark; | ||||
| import io.perfmark.Tag; | ||||
| import javax.annotation.Nullable; | ||||
| 
 | ||||
| /** Client stream for a Netty transport. Must only be called from the sending application thread. */ | ||||
| class NettyClientStream extends AbstractClientStream { | ||||
|   private static final InternalMethodDescriptor methodDescriptorAccessor = | ||||
|       new InternalMethodDescriptor( | ||||
|           NettyClientTransport.class.getName().contains("grpc.netty.shaded") | ||||
|               ? InternalKnownTransport.NETTY_SHADED | ||||
|               : InternalKnownTransport.NETTY); | ||||
| 
 | ||||
|   private final Sink sink = new Sink(); | ||||
|   private final TransportState state; | ||||
|   private final WriteQueue writeQueue; | ||||
|   private final MethodDescriptor<?, ?> method; | ||||
|   private AsciiString authority; | ||||
|   private final AsciiString scheme; | ||||
|   private final AsciiString userAgent; | ||||
| 
 | ||||
|   NettyClientStream( | ||||
|       TransportState state, | ||||
|       MethodDescriptor<?, ?> method, | ||||
|       Metadata headers, | ||||
|       Channel channel, | ||||
|       AsciiString authority, | ||||
|       AsciiString scheme, | ||||
|       AsciiString userAgent, | ||||
|       StatsTraceContext statsTraceCtx, | ||||
|       TransportTracer transportTracer, | ||||
|       CallOptions callOptions, | ||||
|       boolean useGetForSafeMethods) { | ||||
|     super( | ||||
|         new NettyWritableBufferAllocator(channel.alloc()), | ||||
|         statsTraceCtx, | ||||
|         transportTracer, | ||||
|         headers, | ||||
|         callOptions, | ||||
|         useGetForSafeMethods && method.isSafe()); | ||||
|     this.state = checkNotNull(state, "transportState"); | ||||
|     this.writeQueue = state.handler.getWriteQueue(); | ||||
|     this.method = checkNotNull(method, "method"); | ||||
|     this.authority = checkNotNull(authority, "authority"); | ||||
|     this.scheme = checkNotNull(scheme, "scheme"); | ||||
|     this.userAgent = userAgent; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected TransportState transportState() { | ||||
|     return state; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected Sink abstractClientStreamSink() { | ||||
|     return sink; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void setAuthority(String authority) { | ||||
|     this.authority = AsciiString.of(checkNotNull(authority, "authority")); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Attributes getAttributes() { | ||||
|     return state.handler.getAttributes(); | ||||
|   } | ||||
| 
 | ||||
|   private class Sink implements AbstractClientStream.Sink { | ||||
| 
 | ||||
|     @Override | ||||
|     public void writeHeaders(Metadata headers, byte[] requestPayload) { | ||||
|       PerfMark.startTask("NettyClientStream$Sink.writeHeaders"); | ||||
|       try { | ||||
|         writeHeadersInternal(headers, requestPayload); | ||||
|       } finally { | ||||
|         PerfMark.stopTask("NettyClientStream$Sink.writeHeaders"); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     private void writeHeadersInternal(Metadata headers, byte[] requestPayload) { | ||||
|       // Convert the headers into Netty HTTP/2 headers. | ||||
|       AsciiString defaultPath = (AsciiString) methodDescriptorAccessor.geRawMethodName(method); | ||||
|       if (defaultPath == null) { | ||||
|         defaultPath = new AsciiString("/" + method.getFullMethodName()); | ||||
|         methodDescriptorAccessor.setRawMethodName(method, defaultPath); | ||||
|       } | ||||
|       boolean get = (requestPayload != null); | ||||
|       AsciiString httpMethod; | ||||
|       if (get) { | ||||
|         // Forge the query string | ||||
|         // TODO(ericgribkoff) Add the key back to the query string | ||||
|         defaultPath = | ||||
|             new AsciiString(defaultPath + "?" + BaseEncoding.base64().encode(requestPayload)); | ||||
|         httpMethod = Utils.HTTP_GET_METHOD; | ||||
|       } else { | ||||
|         httpMethod = Utils.HTTP_METHOD; | ||||
|       } | ||||
|       Http2Headers http2Headers = | ||||
|           Utils.convertClientHeaders( | ||||
|               headers, scheme, defaultPath, authority, httpMethod, userAgent); | ||||
| 
 | ||||
|       ChannelFutureListener failureListener = | ||||
|           new ChannelFutureListener() { | ||||
|             @Override | ||||
|             public void operationComplete(ChannelFuture future) throws Exception { | ||||
|               if (!future.isSuccess()) { | ||||
|                 // Stream creation failed. Close the stream if not already closed. | ||||
|                 // When the channel is shutdown, the lifecycle manager has a better view of the | ||||
|                 // failure, | ||||
|                 // especially before negotiation completes (because the negotiator commonly doesn't | ||||
|                 // receive the execeptionCaught because NettyClientHandler does not propagate it). | ||||
|                 Status s = transportState().handler.getLifecycleManager().getShutdownStatus(); | ||||
|                 if (s == null) { | ||||
|                   s = transportState().statusFromFailedFuture(future); | ||||
|                 } | ||||
|                 transportState().transportReportStatus(s, true, new Metadata()); | ||||
|               } | ||||
|             } | ||||
|           }; | ||||
|       // Write the command requesting the creation of the stream. | ||||
|       writeQueue | ||||
|           .enqueue( | ||||
|               new CreateStreamCommand( | ||||
|                   http2Headers, transportState(), shouldBeCountedForInUse(), get), | ||||
|               !method.getType().clientSendsOneMessage() || get) | ||||
|           .addListener(failureListener); | ||||
|     } | ||||
| 
 | ||||
|     private void writeFrameInternal( | ||||
|         WritableBuffer frame, boolean endOfStream, boolean flush, final int numMessages) { | ||||
|       Preconditions.checkArgument(numMessages >= 0); | ||||
|       ByteBuf bytebuf = | ||||
|           frame == null ? EMPTY_BUFFER : ((NettyWritableBuffer) frame).bytebuf().touch(); | ||||
|       final int numBytes = bytebuf.readableBytes(); | ||||
|       if (numBytes > 0) { | ||||
|         // Add the bytes to outbound flow control. | ||||
|         onSendingBytes(numBytes); | ||||
|         writeQueue | ||||
|             .enqueue(new SendGrpcFrameCommand(transportState(), bytebuf, endOfStream), flush) | ||||
|             .addListener( | ||||
|                 new ChannelFutureListener() { | ||||
|                   @Override | ||||
|                   public void operationComplete(ChannelFuture future) throws Exception { | ||||
|                     // If the future succeeds when http2stream is null, the stream has been | ||||
|                     // cancelled | ||||
|                     // before it began and Netty is purging pending writes from the flow-controller. | ||||
|                     if (future.isSuccess() && transportState().http2Stream() != null) { | ||||
|                       // Remove the bytes from outbound flow control, optionally notifying | ||||
|                       // the client that they can send more bytes. | ||||
|                       transportState().onSentBytes(numBytes); | ||||
|                       NettyClientStream.this.getTransportTracer().reportMessageSent(numMessages); | ||||
|                     } | ||||
|                   } | ||||
|                 }); | ||||
|       } else { | ||||
|         // The frame is empty and will not impact outbound flow control. Just send it. | ||||
|         writeQueue.enqueue(new SendGrpcFrameCommand(transportState(), bytebuf, endOfStream), flush); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void writeFrame( | ||||
|         WritableBuffer frame, boolean endOfStream, boolean flush, int numMessages) { | ||||
|       PerfMark.startTask("NettyClientStream$Sink.writeFrame"); | ||||
|       try { | ||||
|         writeFrameInternal(frame, endOfStream, flush, numMessages); | ||||
|       } finally { | ||||
|         PerfMark.stopTask("NettyClientStream$Sink.writeFrame"); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void cancel(Status status) { | ||||
|       PerfMark.startTask("NettyClientStream$Sink.cancel"); | ||||
|       try { | ||||
|         writeQueue.enqueue(new CancelClientStreamCommand(transportState(), status), true); | ||||
|       } finally { | ||||
|         PerfMark.stopTask("NettyClientStream$Sink.cancel"); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** This should only called from the transport thread. */ | ||||
|   public abstract static class TransportState extends Http2ClientStreamTransportState | ||||
|       implements StreamIdHolder { | ||||
|     private static final int NON_EXISTENT_ID = -1; | ||||
| 
 | ||||
|     private final String methodName; | ||||
|     private final NettyClientHandler handler; | ||||
|     private final EventLoop eventLoop; | ||||
|     private int id; | ||||
|     private Http2Stream http2Stream; | ||||
|     private Tag tag; | ||||
| 
 | ||||
|     protected TransportState( | ||||
|         NettyClientHandler handler, | ||||
|         EventLoop eventLoop, | ||||
|         int maxMessageSize, | ||||
|         StatsTraceContext statsTraceCtx, | ||||
|         TransportTracer transportTracer, | ||||
|         String methodName) { | ||||
|       super(maxMessageSize, statsTraceCtx, transportTracer); | ||||
|       this.methodName = checkNotNull(methodName, "methodName"); | ||||
|       this.handler = checkNotNull(handler, "handler"); | ||||
|       this.eventLoop = checkNotNull(eventLoop, "eventLoop"); | ||||
|       tag = PerfMark.createTag(methodName); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int id() { | ||||
|       // id should be positive | ||||
|       return id; | ||||
|     } | ||||
| 
 | ||||
|     public void setId(int id) { | ||||
|       checkArgument(id > 0, "id must be positive %s", id); | ||||
|       checkState(this.id == 0, "id has been previously set: %s", this.id); | ||||
|       this.id = id; | ||||
|       this.tag = PerfMark.createTag(methodName, id); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Marks the stream state as if it had never existed. This can happen if the stream is cancelled | ||||
|      * after it is created, but before it has been started. | ||||
|      */ | ||||
|     void setNonExistent() { | ||||
|       checkState(this.id == 0, "Id has been previously set: %s", this.id); | ||||
|       this.id = NON_EXISTENT_ID; | ||||
|     } | ||||
| 
 | ||||
|     boolean isNonExistent() { | ||||
|       return this.id == NON_EXISTENT_ID; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the underlying Netty {@link Http2Stream} for this stream. This must be called in the | ||||
|      * context of the transport thread. | ||||
|      */ | ||||
|     public void setHttp2Stream(Http2Stream http2Stream) { | ||||
|       checkNotNull(http2Stream, "http2Stream"); | ||||
|       checkState(this.http2Stream == null, "Can only set http2Stream once"); | ||||
|       this.http2Stream = http2Stream; | ||||
| 
 | ||||
|       // Now that the stream has actually been initialized, call the listener's onReady callback if | ||||
|       // appropriate. | ||||
|       onStreamAllocated(); | ||||
|       getTransportTracer().reportLocalStreamStarted(); | ||||
|     } | ||||
| 
 | ||||
|     /** Gets the underlying Netty {@link Http2Stream} for this stream. */ | ||||
|     @Nullable | ||||
|     public Http2Stream http2Stream() { | ||||
|       return http2Stream; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Intended to be overridden by NettyClientTransport, which has more information about failures. | ||||
|      * May only be called from event loop. | ||||
|      */ | ||||
|     protected abstract Status statusFromFailedFuture(ChannelFuture f); | ||||
| 
 | ||||
|     @Override | ||||
|     protected void http2ProcessingFailed(Status status, boolean stopDelivery, Metadata trailers) { | ||||
|       transportReportStatus(status, stopDelivery, trailers); | ||||
|       handler.getWriteQueue().enqueue(new CancelClientStreamCommand(this, status), true); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void runOnTransportThread(final Runnable r) { | ||||
|       if (eventLoop.inEventLoop()) { | ||||
|         r.run(); | ||||
|       } else { | ||||
|         eventLoop.execute(r); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void bytesRead(int processedBytes) { | ||||
|       handler.returnProcessedBytes(http2Stream, processedBytes); | ||||
|       handler.getWriteQueue().scheduleFlush(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void deframeFailed(Throwable cause) { | ||||
|       http2ProcessingFailed(Status.fromThrowable(cause), true, new Metadata()); | ||||
|     } | ||||
| 
 | ||||
|     void transportHeadersReceived(Http2Headers headers, boolean endOfStream) { | ||||
|       if (endOfStream) { | ||||
|         if (!isOutboundClosed()) { | ||||
|           handler.getWriteQueue().enqueue(new CancelClientStreamCommand(this, null), true); | ||||
|         } | ||||
|         transportTrailersReceived(Utils.convertTrailers(headers)); | ||||
|       } else { | ||||
|         transportHeadersReceived(Utils.convertHeaders(headers)); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     void transportDataReceived(ByteBuf frame, boolean endOfStream) { | ||||
|       transportDataReceived(new NettyReadableBuffer(frame.retain()), endOfStream); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final Tag tag() { | ||||
|       return tag; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,228 @@ | |||
| /* | ||||
|  * Copyright 2015 The gRPC Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package io.grpc.netty; | ||||
| 
 | ||||
| import com.google.common.annotations.VisibleForTesting; | ||||
| import com.google.common.base.Preconditions; | ||||
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | ||||
| import io.netty.channel.Channel; | ||||
| import io.netty.channel.ChannelFuture; | ||||
| import io.netty.channel.ChannelPromise; | ||||
| import io.perfmark.Link; | ||||
| import io.perfmark.PerfMark; | ||||
| import java.util.Queue; | ||||
| import java.util.concurrent.ConcurrentLinkedQueue; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | ||||
| 
 | ||||
| /** A queue of pending writes to a {@link Channel} that is flushed as a single unit. */ | ||||
| class WriteQueue { | ||||
| 
 | ||||
|   // Dequeue in chunks, so we don't have to acquire the queue's log too often. | ||||
|   @VisibleForTesting static final int DEQUE_CHUNK_SIZE = 128; | ||||
| 
 | ||||
|   /** {@link Runnable} used to schedule work onto the tail of the event loop. */ | ||||
|   private final Runnable later = | ||||
|       new Runnable() { | ||||
|         @Override | ||||
|         public void run() { | ||||
|           flush(); | ||||
|         } | ||||
|       }; | ||||
| 
 | ||||
|   private final Channel channel; | ||||
|   private final Queue<QueuedCommand> queue; | ||||
|   private final AtomicBoolean scheduled = new AtomicBoolean(); | ||||
| 
 | ||||
|   public WriteQueue(Channel channel) { | ||||
|     this.channel = Preconditions.checkNotNull(channel, "channel"); | ||||
|     queue = new ConcurrentLinkedQueue<>(); | ||||
|   } | ||||
| 
 | ||||
|   /** Schedule a flush on the channel. */ | ||||
|   void scheduleFlush() { | ||||
|     if (scheduled.compareAndSet(false, true)) { | ||||
|       // Add the queue to the tail of the event loop so writes will be executed immediately | ||||
|       // inside the event loop. Note DO NOT do channel.write outside the event loop as | ||||
|       // it will not wake up immediately without a flush. | ||||
|       channel.eventLoop().execute(later); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Enqueue a write command on the channel. | ||||
|    * | ||||
|    * @param command a write to be executed on the channel. | ||||
|    * @param flush true if a flush of the write should be schedule, false if a later call to enqueue | ||||
|    *     will schedule the flush. | ||||
|    */ | ||||
|   @CanIgnoreReturnValue | ||||
|   ChannelFuture enqueue(QueuedCommand command, boolean flush) { | ||||
|     // Detect erroneous code that tries to reuse command objects. | ||||
|     Preconditions.checkArgument(command.promise() == null, "promise must not be set on command"); | ||||
| 
 | ||||
|     ChannelPromise promise = channel.newPromise(); | ||||
|     command.promise(promise); | ||||
|     queue.add(command); | ||||
|     if (flush) { | ||||
|       scheduleFlush(); | ||||
|     } | ||||
|     return promise; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Enqueue the runnable. It is not safe for another thread to queue an Runnable directly to the | ||||
|    * event loop, because it will be out-of-order with writes. This method allows the Runnable to be | ||||
|    * processed in-order with writes. | ||||
|    */ | ||||
|   void enqueue(Runnable runnable, boolean flush) { | ||||
|     queue.add(new RunnableCommand(runnable)); | ||||
|     if (flush) { | ||||
|       scheduleFlush(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Executes enqueued work directly on the current thread. This can be used to trigger writes | ||||
|    * before performing additional reads. Must be called from the event loop. This method makes no | ||||
|    * guarantee that the work queue is empty when it returns. | ||||
|    */ | ||||
|   void drainNow() { | ||||
|     Preconditions.checkState(channel.eventLoop().inEventLoop(), "must be on the event loop"); | ||||
|     if (queue.peek() == null) { | ||||
|       return; | ||||
|     } | ||||
|     flush(); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Process the queue of commands and dispatch them to the stream. This method is only called in | ||||
|    * the event loop | ||||
|    */ | ||||
|   private void flush() { | ||||
|     PerfMark.startTask("WriteQueue.periodicFlush"); | ||||
|     try { | ||||
|       QueuedCommand cmd; | ||||
|       int i = 0; | ||||
|       boolean flushedOnce = false; | ||||
|       while ((cmd = queue.poll()) != null) { | ||||
|         cmd.run(channel); | ||||
|         if (++i == DEQUE_CHUNK_SIZE) { | ||||
|           i = 0; | ||||
|           // Flush each chunk so we are releasing buffers periodically. In theory this loop | ||||
|           // might never end as new events are continuously added to the queue, if we never | ||||
|           // flushed in that case we would be guaranteed to OOM. | ||||
|           PerfMark.startTask("WriteQueue.flush0"); | ||||
|           try { | ||||
|             channel.flush(); | ||||
|           } finally { | ||||
|             PerfMark.stopTask("WriteQueue.flush0"); | ||||
|           } | ||||
|           flushedOnce = true; | ||||
|         } | ||||
|       } | ||||
|       // Must flush at least once, even if there were no writes. | ||||
|       if (i != 0 || !flushedOnce) { | ||||
|         PerfMark.startTask("WriteQueue.flush1"); | ||||
|         try { | ||||
|           channel.flush(); | ||||
|         } finally { | ||||
|           PerfMark.stopTask("WriteQueue.flush1"); | ||||
|         } | ||||
|       } | ||||
|     } finally { | ||||
|       PerfMark.stopTask("WriteQueue.periodicFlush"); | ||||
|       // Mark the write as done, if the queue is non-empty after marking trigger a new write. | ||||
|       scheduled.set(false); | ||||
|       if (!queue.isEmpty()) { | ||||
|         scheduleFlush(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static class RunnableCommand implements QueuedCommand { | ||||
|     private final Runnable runnable; | ||||
|     private final Link link; | ||||
| 
 | ||||
|     public RunnableCommand(Runnable runnable) { | ||||
|       this.link = PerfMark.linkOut(); | ||||
|       this.runnable = runnable; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void promise(ChannelPromise promise) { | ||||
|       throw new UnsupportedOperationException(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final ChannelPromise promise() { | ||||
|       throw new UnsupportedOperationException(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void run(Channel channel) { | ||||
|       runnable.run(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Link getLink() { | ||||
|       return link; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   abstract static class AbstractQueuedCommand implements QueuedCommand { | ||||
| 
 | ||||
|     private ChannelPromise promise; | ||||
|     private final Link link; | ||||
| 
 | ||||
|     AbstractQueuedCommand() { | ||||
|       this.link = PerfMark.linkOut(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void promise(ChannelPromise promise) { | ||||
|       this.promise = promise; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final ChannelPromise promise() { | ||||
|       return promise; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public final void run(Channel channel) { | ||||
|       channel.write(this, promise); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Link getLink() { | ||||
|       return link; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** Simple wrapper type around a command and its optional completion listener. */ | ||||
|   interface QueuedCommand { | ||||
|     /** Returns the promise beeing notified of the success/failure of the write. */ | ||||
|     ChannelPromise promise(); | ||||
| 
 | ||||
|     /** Sets the promise. */ | ||||
|     void promise(ChannelPromise promise); | ||||
| 
 | ||||
|     void run(Channel channel); | ||||
| 
 | ||||
|     Link getLink(); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,765 @@ | |||
| /* | ||||
|  * Copyright 2014 The gRPC Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package io.grpc.stub; | ||||
| 
 | ||||
| import static com.google.common.base.Preconditions.checkNotNull; | ||||
| import static com.google.common.base.Preconditions.checkState; | ||||
| 
 | ||||
| import com.google.common.base.MoreObjects; | ||||
| import com.google.common.base.Preconditions; | ||||
| import com.google.common.util.concurrent.AbstractFuture; | ||||
| import com.google.common.util.concurrent.ListenableFuture; | ||||
| import io.grpc.CallOptions; | ||||
| import io.grpc.Channel; | ||||
| import io.grpc.ClientCall; | ||||
| import io.grpc.Metadata; | ||||
| import io.grpc.MethodDescriptor; | ||||
| import io.grpc.Status; | ||||
| import io.grpc.StatusException; | ||||
| import io.grpc.StatusRuntimeException; | ||||
| import java.util.Iterator; | ||||
| import java.util.NoSuchElementException; | ||||
| import java.util.concurrent.ArrayBlockingQueue; | ||||
| import java.util.concurrent.BlockingQueue; | ||||
| import java.util.concurrent.ConcurrentLinkedQueue; | ||||
| import java.util.concurrent.ExecutionException; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.concurrent.Future; | ||||
| import java.util.concurrent.locks.LockSupport; | ||||
| import java.util.logging.Level; | ||||
| import java.util.logging.Logger; | ||||
| import javax.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * Utility functions for processing different call idioms. We have one-to-one correspondence between | ||||
|  * utilities in this class and the potential signatures in a generated stub class so that the | ||||
|  * runtime can vary behavior without requiring regeneration of the stub. | ||||
|  */ | ||||
| public final class ClientCalls { | ||||
| 
 | ||||
|   private static final Logger logger = Logger.getLogger(ClientCalls.class.getName()); | ||||
| 
 | ||||
|   // Prevent instantiation | ||||
|   private ClientCalls() {} | ||||
| 
 | ||||
|   /** | ||||
|    * Executes a unary call with a response {@link StreamObserver}. The {@code call} should not be | ||||
|    * already started. After calling this method, {@code call} should no longer be used. | ||||
|    * | ||||
|    * <p>If the provided {@code responseObserver} is an instance of {@link ClientResponseObserver}, | ||||
|    * {@code beforeStart()} will be called. | ||||
|    */ | ||||
|   public static <ReqT, RespT> void asyncUnaryCall( | ||||
|       ClientCall<ReqT, RespT> call, ReqT req, StreamObserver<RespT> responseObserver) { | ||||
|     asyncUnaryRequestCall(call, req, responseObserver, false); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Executes a server-streaming call with a response {@link StreamObserver}. The {@code call} | ||||
|    * should not be already started. After calling this method, {@code call} should no longer be | ||||
|    * used. | ||||
|    * | ||||
|    * <p>If the provided {@code responseObserver} is an instance of {@link ClientResponseObserver}, | ||||
|    * {@code beforeStart()} will be called. | ||||
|    */ | ||||
|   public static <ReqT, RespT> void asyncServerStreamingCall( | ||||
|       ClientCall<ReqT, RespT> call, ReqT req, StreamObserver<RespT> responseObserver) { | ||||
|     asyncUnaryRequestCall(call, req, responseObserver, true); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Executes a client-streaming call returning a {@link StreamObserver} for the request messages. | ||||
|    * The {@code call} should not be already started. After calling this method, {@code call} should | ||||
|    * no longer be used. | ||||
|    * | ||||
|    * <p>If the provided {@code responseObserver} is an instance of {@link ClientResponseObserver}, | ||||
|    * {@code beforeStart()} will be called. | ||||
|    * | ||||
|    * @return request stream observer. It will extend {@link ClientCallStreamObserver} | ||||
|    */ | ||||
|   public static <ReqT, RespT> StreamObserver<ReqT> asyncClientStreamingCall( | ||||
|       ClientCall<ReqT, RespT> call, StreamObserver<RespT> responseObserver) { | ||||
|     return asyncStreamingRequestCall(call, responseObserver, false); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Executes a bidirectional-streaming call. The {@code call} should not be already started. After | ||||
|    * calling this method, {@code call} should no longer be used. | ||||
|    * | ||||
|    * <p>If the provided {@code responseObserver} is an instance of {@link ClientResponseObserver}, | ||||
|    * {@code beforeStart()} will be called. | ||||
|    * | ||||
|    * @return request stream observer. It will extend {@link ClientCallStreamObserver} | ||||
|    */ | ||||
|   public static <ReqT, RespT> StreamObserver<ReqT> asyncBidiStreamingCall( | ||||
|       ClientCall<ReqT, RespT> call, StreamObserver<RespT> responseObserver) { | ||||
|     return asyncStreamingRequestCall(call, responseObserver, true); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Executes a unary call and blocks on the response. The {@code call} should not be already | ||||
|    * started. After calling this method, {@code call} should no longer be used. | ||||
|    * | ||||
|    * @return the single response message. | ||||
|    * @throws StatusRuntimeException on error | ||||
|    */ | ||||
|   public static <ReqT, RespT> RespT blockingUnaryCall(ClientCall<ReqT, RespT> call, ReqT req) { | ||||
|     try { | ||||
|       return getUnchecked(futureUnaryCall(call, req)); | ||||
|     } catch (RuntimeException e) { | ||||
|       throw cancelThrow(call, e); | ||||
|     } catch (Error e) { | ||||
|       throw cancelThrow(call, e); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Executes a unary call and blocks on the response. The {@code call} should not be already | ||||
|    * started. After calling this method, {@code call} should no longer be used. | ||||
|    * | ||||
|    * @return the single response message. | ||||
|    * @throws StatusRuntimeException on error | ||||
|    */ | ||||
|   public static <ReqT, RespT> RespT blockingUnaryCall( | ||||
|       Channel channel, MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, ReqT req) { | ||||
|     ThreadlessExecutor executor = new ThreadlessExecutor(); | ||||
|     boolean interrupt = false; | ||||
|     ClientCall<ReqT, RespT> call = | ||||
|         channel.newCall( | ||||
|             method, | ||||
|             callOptions | ||||
|                 .withOption(ClientCalls.STUB_TYPE_OPTION, StubType.BLOCKING) | ||||
|                 .withExecutor(executor)); | ||||
|     try { | ||||
|       ListenableFuture<RespT> responseFuture = futureUnaryCall(call, req); | ||||
|       while (!responseFuture.isDone()) { | ||||
|         try { | ||||
|           executor.waitAndDrain(); | ||||
|         } catch (InterruptedException e) { | ||||
|           interrupt = true; | ||||
|           call.cancel("Thread interrupted", e); | ||||
|           // Now wait for onClose() to be called, so interceptors can clean up | ||||
|         } | ||||
|       } | ||||
|       return getUnchecked(responseFuture); | ||||
|     } catch (RuntimeException e) { | ||||
|       // Something very bad happened. All bets are off; it may be dangerous to wait for onClose(). | ||||
|       throw cancelThrow(call, e); | ||||
|     } catch (Error e) { | ||||
|       // Something very bad happened. All bets are off; it may be dangerous to wait for onClose(). | ||||
|       throw cancelThrow(call, e); | ||||
|     } finally { | ||||
|       if (interrupt) { | ||||
|         Thread.currentThread().interrupt(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Executes a server-streaming call returning a blocking {@link Iterator} over the response | ||||
|    * stream. The {@code call} should not be already started. After calling this method, {@code call} | ||||
|    * should no longer be used. | ||||
|    * | ||||
|    * <p>The returned iterator may throw {@link StatusRuntimeException} on error. | ||||
|    * | ||||
|    * @return an iterator over the response stream. | ||||
|    */ | ||||
|   // TODO(louiscryan): Not clear if we want to use this idiom for 'simple' stubs. | ||||
|   public static <ReqT, RespT> Iterator<RespT> blockingServerStreamingCall( | ||||
|       ClientCall<ReqT, RespT> call, ReqT req) { | ||||
|     BlockingResponseStream<RespT> result = new BlockingResponseStream<>(call); | ||||
|     asyncUnaryRequestCall(call, req, result.listener()); | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Executes a server-streaming call returning a blocking {@link Iterator} over the response | ||||
|    * stream. The {@code call} should not be already started. After calling this method, {@code call} | ||||
|    * should no longer be used. | ||||
|    * | ||||
|    * <p>The returned iterator may throw {@link StatusRuntimeException} on error. | ||||
|    * | ||||
|    * @return an iterator over the response stream. | ||||
|    */ | ||||
|   // TODO(louiscryan): Not clear if we want to use this idiom for 'simple' stubs. | ||||
|   public static <ReqT, RespT> Iterator<RespT> blockingServerStreamingCall( | ||||
|       Channel channel, MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, ReqT req) { | ||||
|     ThreadlessExecutor executor = new ThreadlessExecutor(); | ||||
|     ClientCall<ReqT, RespT> call = | ||||
|         channel.newCall( | ||||
|             method, | ||||
|             callOptions | ||||
|                 .withOption(ClientCalls.STUB_TYPE_OPTION, StubType.BLOCKING) | ||||
|                 .withExecutor(executor)); | ||||
|     BlockingResponseStream<RespT> result = new BlockingResponseStream<>(call, executor); | ||||
|     asyncUnaryRequestCall(call, req, result.listener()); | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Executes a unary call and returns a {@link ListenableFuture} to the response. The {@code call} | ||||
|    * should not be already started. After calling this method, {@code call} should no longer be | ||||
|    * used. | ||||
|    * | ||||
|    * @return a future for the single response message. | ||||
|    */ | ||||
|   public static <ReqT, RespT> ListenableFuture<RespT> futureUnaryCall( | ||||
|       ClientCall<ReqT, RespT> call, ReqT req) { | ||||
|     GrpcFuture<RespT> responseFuture = new GrpcFuture<>(call); | ||||
|     asyncUnaryRequestCall(call, req, new UnaryStreamToFuture<>(responseFuture)); | ||||
|     return responseFuture; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns the result of calling {@link Future#get()} interruptibly on a task known not to throw a | ||||
|    * checked exception. | ||||
|    * | ||||
|    * <p>If interrupted, the interrupt is restored before throwing an exception.. | ||||
|    * | ||||
|    * @throws java.util.concurrent.CancellationException if {@code get} throws a {@code | ||||
|    *     CancellationException}. | ||||
|    * @throws io.grpc.StatusRuntimeException if {@code get} throws an {@link ExecutionException} or | ||||
|    *     an {@link InterruptedException}. | ||||
|    */ | ||||
|   private static <V> V getUnchecked(Future<V> future) { | ||||
|     try { | ||||
|       return future.get(); | ||||
|     } catch (InterruptedException e) { | ||||
|       Thread.currentThread().interrupt(); | ||||
|       throw Status.CANCELLED | ||||
|           .withDescription("Thread interrupted") | ||||
|           .withCause(e) | ||||
|           .asRuntimeException(); | ||||
|     } catch (ExecutionException e) { | ||||
|       throw toStatusRuntimeException(e.getCause()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Wraps the given {@link Throwable} in a {@link StatusRuntimeException}. If it contains an | ||||
|    * embedded {@link StatusException} or {@link StatusRuntimeException}, the returned exception will | ||||
|    * contain the embedded trailers and status, with the given exception as the cause. Otherwise, an | ||||
|    * exception will be generated from an {@link Status#UNKNOWN} status. | ||||
|    */ | ||||
|   private static StatusRuntimeException toStatusRuntimeException(Throwable t) { | ||||
|     Throwable cause = checkNotNull(t, "t"); | ||||
|     while (cause != null) { | ||||
|       // If we have an embedded status, use it and replace the cause | ||||
|       if (cause instanceof StatusException) { | ||||
|         StatusException se = (StatusException) cause; | ||||
|         return new StatusRuntimeException(se.getStatus(), se.getTrailers()); | ||||
|       } else if (cause instanceof StatusRuntimeException) { | ||||
|         StatusRuntimeException se = (StatusRuntimeException) cause; | ||||
|         return new StatusRuntimeException(se.getStatus(), se.getTrailers()); | ||||
|       } | ||||
|       cause = cause.getCause(); | ||||
|     } | ||||
|     return Status.UNKNOWN.withDescription("unexpected exception").withCause(t).asRuntimeException(); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Cancels a call, and throws the exception. | ||||
|    * | ||||
|    * @param t must be a RuntimeException or Error | ||||
|    */ | ||||
|   private static RuntimeException cancelThrow(ClientCall<?, ?> call, Throwable t) { | ||||
|     try { | ||||
|       call.cancel(null, t); | ||||
|     } catch (Throwable e) { | ||||
|       assert e instanceof RuntimeException || e instanceof Error; | ||||
|       logger.log(Level.SEVERE, "RuntimeException encountered while closing call", e); | ||||
|     } | ||||
|     if (t instanceof RuntimeException) { | ||||
|       throw (RuntimeException) t; | ||||
|     } else if (t instanceof Error) { | ||||
|       throw (Error) t; | ||||
|     } | ||||
|     // should be impossible | ||||
|     throw new AssertionError(t); | ||||
|   } | ||||
| 
 | ||||
|   private static <ReqT, RespT> void asyncUnaryRequestCall( | ||||
|       ClientCall<ReqT, RespT> call, | ||||
|       ReqT req, | ||||
|       StreamObserver<RespT> responseObserver, | ||||
|       boolean streamingResponse) { | ||||
|     asyncUnaryRequestCall( | ||||
|         call, | ||||
|         req, | ||||
|         new StreamObserverToCallListenerAdapter<>( | ||||
|             responseObserver, new CallToStreamObserverAdapter<>(call, streamingResponse))); | ||||
|   } | ||||
| 
 | ||||
|   private static <ReqT, RespT> void asyncUnaryRequestCall( | ||||
|       ClientCall<ReqT, RespT> call, ReqT req, StartableListener<RespT> responseListener) { | ||||
|     startCall(call, responseListener); | ||||
|     try { | ||||
|       call.sendMessage(req); | ||||
|       call.halfClose(); | ||||
|     } catch (RuntimeException e) { | ||||
|       throw cancelThrow(call, e); | ||||
|     } catch (Error e) { | ||||
|       throw cancelThrow(call, e); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static <ReqT, RespT> StreamObserver<ReqT> asyncStreamingRequestCall( | ||||
|       ClientCall<ReqT, RespT> call, | ||||
|       StreamObserver<RespT> responseObserver, | ||||
|       boolean streamingResponse) { | ||||
|     CallToStreamObserverAdapter<ReqT> adapter = | ||||
|         new CallToStreamObserverAdapter<>(call, streamingResponse); | ||||
|     startCall(call, new StreamObserverToCallListenerAdapter<>(responseObserver, adapter)); | ||||
|     return adapter; | ||||
|   } | ||||
| 
 | ||||
|   private static <ReqT, RespT> void startCall( | ||||
|       ClientCall<ReqT, RespT> call, StartableListener<RespT> responseListener) { | ||||
|     call.start(responseListener, new Metadata()); | ||||
|     responseListener.onStart(); | ||||
|   } | ||||
| 
 | ||||
|   private abstract static class StartableListener<T> extends ClientCall.Listener<T> { | ||||
|     abstract void onStart(); | ||||
|   } | ||||
| 
 | ||||
|   private static final class CallToStreamObserverAdapter<T> extends ClientCallStreamObserver<T> { | ||||
|     private boolean frozen; | ||||
|     private final ClientCall<T, ?> call; | ||||
|     private final boolean streamingResponse; | ||||
|     private Runnable onReadyHandler; | ||||
|     private int initialRequest = 1; | ||||
|     private boolean autoRequestEnabled = true; | ||||
|     private boolean aborted = false; | ||||
|     private boolean completed = false; | ||||
| 
 | ||||
|     // Non private to avoid synthetic class | ||||
|     CallToStreamObserverAdapter(ClientCall<T, ?> call, boolean streamingResponse) { | ||||
|       this.call = call; | ||||
|       this.streamingResponse = streamingResponse; | ||||
|     } | ||||
| 
 | ||||
|     private void freeze() { | ||||
|       this.frozen = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onNext(T value) { | ||||
|       checkState(!aborted, "Stream was terminated by error, no further calls are allowed"); | ||||
|       checkState(!completed, "Stream is already completed, no further calls are allowed"); | ||||
|       call.sendMessage(value); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onError(Throwable t) { | ||||
|       call.cancel("Cancelled by client with StreamObserver.onError()", t); | ||||
|       aborted = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCompleted() { | ||||
|       call.halfClose(); | ||||
|       completed = true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean isReady() { | ||||
|       return call.isReady(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setOnReadyHandler(Runnable onReadyHandler) { | ||||
|       if (frozen) { | ||||
|         throw new IllegalStateException( | ||||
|             "Cannot alter onReadyHandler after call started. Use ClientResponseObserver"); | ||||
|       } | ||||
|       this.onReadyHandler = onReadyHandler; | ||||
|     } | ||||
| 
 | ||||
|     @Deprecated | ||||
|     @Override | ||||
|     public void disableAutoInboundFlowControl() { | ||||
|       disableAutoRequestWithInitial(1); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void disableAutoRequestWithInitial(int request) { | ||||
|       if (frozen) { | ||||
|         throw new IllegalStateException( | ||||
|             "Cannot disable auto flow control after call started. Use ClientResponseObserver"); | ||||
|       } | ||||
|       Preconditions.checkArgument(request >= 0, "Initial requests must be non-negative"); | ||||
|       initialRequest = request; | ||||
|       autoRequestEnabled = false; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void request(int count) { | ||||
|       if (!streamingResponse && count == 1) { | ||||
|         // Initially ask for two responses from flow-control so that if a misbehaving server | ||||
|         // sends more than one responses, we can catch it and fail it in the listener. | ||||
|         call.request(2); | ||||
|       } else { | ||||
|         call.request(count); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setMessageCompression(boolean enable) { | ||||
|       call.setMessageCompression(enable); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void cancel(@Nullable String message, @Nullable Throwable cause) { | ||||
|       call.cancel(message, cause); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static final class StreamObserverToCallListenerAdapter<ReqT, RespT> | ||||
|       extends StartableListener<RespT> { | ||||
|     private final StreamObserver<RespT> observer; | ||||
|     private final CallToStreamObserverAdapter<ReqT> adapter; | ||||
|     private boolean firstResponseReceived; | ||||
| 
 | ||||
|     // Non private to avoid synthetic class | ||||
|     StreamObserverToCallListenerAdapter( | ||||
|         StreamObserver<RespT> observer, CallToStreamObserverAdapter<ReqT> adapter) { | ||||
|       this.observer = observer; | ||||
|       this.adapter = adapter; | ||||
|       if (observer instanceof ClientResponseObserver) { | ||||
|         @SuppressWarnings("unchecked") | ||||
|         ClientResponseObserver<ReqT, RespT> clientResponseObserver = | ||||
|             (ClientResponseObserver<ReqT, RespT>) observer; | ||||
|         clientResponseObserver.beforeStart(adapter); | ||||
|       } | ||||
|       adapter.freeze(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onHeaders(Metadata headers) {} | ||||
| 
 | ||||
|     @Override | ||||
|     public void onMessage(RespT message) { | ||||
|       if (firstResponseReceived && !adapter.streamingResponse) { | ||||
|         throw Status.INTERNAL | ||||
|             .withDescription("More than one responses received for unary or client-streaming call") | ||||
|             .asRuntimeException(); | ||||
|       } | ||||
|       firstResponseReceived = true; | ||||
|       observer.onNext(message); | ||||
| 
 | ||||
|       if (adapter.streamingResponse && adapter.autoRequestEnabled) { | ||||
|         // Request delivery of the next inbound message. | ||||
|         adapter.request(1); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onClose(Status status, Metadata trailers) { | ||||
|       if (status.isOk()) { | ||||
|         observer.onCompleted(); | ||||
|       } else { | ||||
|         observer.onError(status.asRuntimeException(trailers)); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onReady() { | ||||
|       if (adapter.onReadyHandler != null) { | ||||
|         adapter.onReadyHandler.run(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void onStart() { | ||||
|       if (adapter.initialRequest > 0) { | ||||
|         adapter.request(adapter.initialRequest); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** Completes a {@link GrpcFuture} using {@link StreamObserver} events. */ | ||||
|   private static final class UnaryStreamToFuture<RespT> extends StartableListener<RespT> { | ||||
|     private final GrpcFuture<RespT> responseFuture; | ||||
|     private RespT value; | ||||
| 
 | ||||
|     // Non private to avoid synthetic class | ||||
|     UnaryStreamToFuture(GrpcFuture<RespT> responseFuture) { | ||||
|       this.responseFuture = responseFuture; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onHeaders(Metadata headers) {} | ||||
| 
 | ||||
|     @Override | ||||
|     public void onMessage(RespT value) { | ||||
|       if (this.value != null) { | ||||
|         throw Status.INTERNAL | ||||
|             .withDescription("More than one value received for unary call") | ||||
|             .asRuntimeException(); | ||||
|       } | ||||
|       this.value = value; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onClose(Status status, Metadata trailers) { | ||||
|       if (status.isOk()) { | ||||
|         if (value == null) { | ||||
|           // No value received so mark the future as an error | ||||
|           responseFuture.setException( | ||||
|               Status.INTERNAL | ||||
|                   .withDescription("No value received for unary call") | ||||
|                   .asRuntimeException(trailers)); | ||||
|         } | ||||
|         responseFuture.set(value); | ||||
|       } else { | ||||
|         responseFuture.setException(status.asRuntimeException(trailers)); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void onStart() { | ||||
|       responseFuture.call.request(2); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static final class GrpcFuture<RespT> extends AbstractFuture<RespT> { | ||||
|     private final ClientCall<?, RespT> call; | ||||
| 
 | ||||
|     // Non private to avoid synthetic class | ||||
|     GrpcFuture(ClientCall<?, RespT> call) { | ||||
|       this.call = call; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void interruptTask() { | ||||
|       call.cancel("GrpcFuture was cancelled", null); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected boolean set(@Nullable RespT resp) { | ||||
|       return super.set(resp); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected boolean setException(Throwable throwable) { | ||||
|       return super.setException(throwable); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("MissingOverride") // Add @Override once Java 6 support is dropped | ||||
|     protected String pendingToString() { | ||||
|       return MoreObjects.toStringHelper(this).add("clientCall", call).toString(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Convert events on a {@link io.grpc.ClientCall.Listener} into a blocking {@link Iterator}. | ||||
|    * | ||||
|    * <p>The class is not thread-safe, but it does permit {@link ClientCall.Listener} calls in a | ||||
|    * separate thread from {@link Iterator} calls. | ||||
|    */ | ||||
|   // TODO(ejona86): determine how to allow ClientCall.cancel() in case of application error. | ||||
|   private static final class BlockingResponseStream<T> implements Iterator<T> { | ||||
|     // Due to flow control, only needs to hold up to 3 items: 2 for value, 1 for close. | ||||
|     // (2 for value, not 1, because of early request() in next()) | ||||
|     private final BlockingQueue<Object> buffer = new ArrayBlockingQueue<>(3); | ||||
|     private final StartableListener<T> listener = new QueuingListener(); | ||||
|     private final ClientCall<?, T> call; | ||||
|     /** May be null. */ | ||||
|     private final ThreadlessExecutor threadless; | ||||
|     // Only accessed when iterating. | ||||
|     private Object last; | ||||
| 
 | ||||
|     // Non private to avoid synthetic class | ||||
|     BlockingResponseStream(ClientCall<?, T> call) { | ||||
|       this(call, null); | ||||
|     } | ||||
| 
 | ||||
|     // Non private to avoid synthetic class | ||||
|     BlockingResponseStream(ClientCall<?, T> call, ThreadlessExecutor threadless) { | ||||
|       this.call = call; | ||||
|       this.threadless = threadless; | ||||
|     } | ||||
| 
 | ||||
|     StartableListener<T> listener() { | ||||
|       return listener; | ||||
|     } | ||||
| 
 | ||||
|     private Object waitForNext() { | ||||
|       boolean interrupt = false; | ||||
|       try { | ||||
|         if (threadless == null) { | ||||
|           while (true) { | ||||
|             try { | ||||
|               return buffer.take(); | ||||
|             } catch (InterruptedException ie) { | ||||
|               interrupt = true; | ||||
|               call.cancel("Thread interrupted", ie); | ||||
|               // Now wait for onClose() to be called, to guarantee BlockingQueue doesn't fill | ||||
|             } | ||||
|           } | ||||
|         } else { | ||||
|           Object next; | ||||
|           while ((next = buffer.poll()) == null) { | ||||
|             try { | ||||
|               threadless.waitAndDrain(); | ||||
|             } catch (InterruptedException ie) { | ||||
|               interrupt = true; | ||||
|               call.cancel("Thread interrupted", ie); | ||||
|               // Now wait for onClose() to be called, so interceptors can clean up | ||||
|             } | ||||
|           } | ||||
|           return next; | ||||
|         } | ||||
|       } finally { | ||||
|         if (interrupt) { | ||||
|           Thread.currentThread().interrupt(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean hasNext() { | ||||
|       while (last == null) { | ||||
|         // Will block here indefinitely waiting for content. RPC timeouts defend against permanent | ||||
|         // hangs here as the call will become closed. | ||||
|         last = waitForNext(); | ||||
|       } | ||||
|       if (last instanceof StatusRuntimeException) { | ||||
|         // Rethrow the exception with a new stacktrace. | ||||
|         StatusRuntimeException e = (StatusRuntimeException) last; | ||||
|         throw e.getStatus().asRuntimeException(e.getTrailers()); | ||||
|       } | ||||
|       return last != this; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public T next() { | ||||
|       // Eagerly call request(1) so it can be processing the next message while we wait for the | ||||
|       // current one, which reduces latency for the next message. With MigratingThreadDeframer and | ||||
|       // if the data has already been recieved, every other message can be delivered instantly. This | ||||
|       // can be run after hasNext(), but just would be slower. | ||||
|       if (!(last instanceof StatusRuntimeException) && last != this) { | ||||
|         call.request(1); | ||||
|       } | ||||
|       if (!hasNext()) { | ||||
|         throw new NoSuchElementException(); | ||||
|       } | ||||
|       @SuppressWarnings("unchecked") | ||||
|       T tmp = (T) last; | ||||
|       last = null; | ||||
|       return tmp; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void remove() { | ||||
|       throw new UnsupportedOperationException(); | ||||
|     } | ||||
| 
 | ||||
|     private final class QueuingListener extends StartableListener<T> { | ||||
|       // Non private to avoid synthetic class | ||||
|       QueuingListener() {} | ||||
| 
 | ||||
|       private boolean done = false; | ||||
| 
 | ||||
|       @Override | ||||
|       public void onHeaders(Metadata headers) {} | ||||
| 
 | ||||
|       @Override | ||||
|       public void onMessage(T value) { | ||||
|         Preconditions.checkState(!done, "ClientCall already closed"); | ||||
|         buffer.add(value); | ||||
|       } | ||||
| 
 | ||||
|       @Override | ||||
|       public void onClose(Status status, Metadata trailers) { | ||||
|         Preconditions.checkState(!done, "ClientCall already closed"); | ||||
|         if (status.isOk()) { | ||||
|           buffer.add(BlockingResponseStream.this); | ||||
|         } else { | ||||
|           buffer.add(status.asRuntimeException(trailers)); | ||||
|         } | ||||
|         done = true; | ||||
|       } | ||||
| 
 | ||||
|       @Override | ||||
|       void onStart() { | ||||
|         call.request(1); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @SuppressWarnings("serial") | ||||
|   private static final class ThreadlessExecutor extends ConcurrentLinkedQueue<Runnable> | ||||
|       implements Executor { | ||||
|     private static final Logger log = Logger.getLogger(ThreadlessExecutor.class.getName()); | ||||
| 
 | ||||
|     private volatile Thread waiter; | ||||
| 
 | ||||
|     // Non private to avoid synthetic class | ||||
|     ThreadlessExecutor() {} | ||||
| 
 | ||||
|     /** | ||||
|      * Waits until there is a Runnable, then executes it and all queued Runnables after it. Must | ||||
|      * only be called by one thread at a time. | ||||
|      */ | ||||
|     public void waitAndDrain() throws InterruptedException { | ||||
|       throwIfInterrupted(); | ||||
|       Runnable runnable = poll(); | ||||
|       if (runnable == null) { | ||||
|         waiter = Thread.currentThread(); | ||||
|         try { | ||||
|           while ((runnable = poll()) == null) { | ||||
|             LockSupport.park(this); | ||||
|             throwIfInterrupted(); | ||||
|           } | ||||
|         } finally { | ||||
|           waiter = null; | ||||
|         } | ||||
|       } | ||||
|       do { | ||||
|         try { | ||||
|           runnable.run(); | ||||
|         } catch (Throwable t) { | ||||
|           log.log(Level.WARNING, "Runnable threw exception", t); | ||||
|         } | ||||
|       } while ((runnable = poll()) != null); | ||||
|     } | ||||
| 
 | ||||
|     private static void throwIfInterrupted() throws InterruptedException { | ||||
|       if (Thread.interrupted()) { | ||||
|         throw new InterruptedException(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void execute(Runnable runnable) { | ||||
|       add(runnable); | ||||
|       LockSupport.unpark(waiter); // no-op if null | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   enum StubType { | ||||
|     BLOCKING, | ||||
|     FUTURE, | ||||
|     ASYNC | ||||
|   } | ||||
| 
 | ||||
|   /** Internal {@link CallOptions.Key} to indicate stub types. */ | ||||
|   static final CallOptions.Key<StubType> STUB_TYPE_OPTION = | ||||
|       CallOptions.Key.create("internal-stub-type"); | ||||
| } | ||||
|  | @ -0,0 +1,546 @@ | |||
| /* | ||||
|  * Copyright 2012 The Netty Project | ||||
|  * | ||||
|  * The Netty Project licenses this file to you under the Apache License, | ||||
|  * version 2.0 (the "License"); you may not use this file except in compliance | ||||
|  * with the License. You may obtain a copy of the License at: | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
| package io.netty.channel.socket.nio; | ||||
| 
 | ||||
| import static io.netty.channel.internal.ChannelUtils.MAX_BYTES_PER_GATHERING_WRITE_ATTEMPTED_LOW_THRESHOLD; | ||||
| 
 | ||||
| import io.netty.buffer.ByteBuf; | ||||
| import io.netty.channel.Channel; | ||||
| import io.netty.channel.ChannelException; | ||||
| import io.netty.channel.ChannelFuture; | ||||
| import io.netty.channel.ChannelFutureListener; | ||||
| import io.netty.channel.ChannelOption; | ||||
| import io.netty.channel.ChannelOutboundBuffer; | ||||
| import io.netty.channel.ChannelPromise; | ||||
| import io.netty.channel.EventLoop; | ||||
| import io.netty.channel.FileRegion; | ||||
| import io.netty.channel.RecvByteBufAllocator; | ||||
| import io.netty.channel.nio.AbstractNioByteChannel; | ||||
| import io.netty.channel.socket.DefaultSocketChannelConfig; | ||||
| import io.netty.channel.socket.ServerSocketChannel; | ||||
| import io.netty.channel.socket.SocketChannelConfig; | ||||
| import io.netty.util.concurrent.GlobalEventExecutor; | ||||
| import io.netty.util.internal.PlatformDependent; | ||||
| import io.netty.util.internal.SocketUtils; | ||||
| import io.netty.util.internal.SuppressJava6Requirement; | ||||
| import io.netty.util.internal.UnstableApi; | ||||
| import io.netty.util.internal.logging.InternalLogger; | ||||
| import io.netty.util.internal.logging.InternalLoggerFactory; | ||||
| import java.io.IOException; | ||||
| import java.net.InetSocketAddress; | ||||
| import java.net.Socket; | ||||
| import java.net.SocketAddress; | ||||
| import java.nio.ByteBuffer; | ||||
| import java.nio.channels.SelectionKey; | ||||
| import java.nio.channels.SocketChannel; | ||||
| import java.nio.channels.spi.SelectorProvider; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.Executor; | ||||
| 
 | ||||
| /** {@link io.netty.channel.socket.SocketChannel} which uses NIO selector based implementation. */ | ||||
| public class NioSocketChannel extends AbstractNioByteChannel | ||||
|     implements io.netty.channel.socket.SocketChannel { | ||||
|   private static final InternalLogger logger = | ||||
|       InternalLoggerFactory.getInstance(NioSocketChannel.class); | ||||
|   private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); | ||||
| 
 | ||||
|   private static SocketChannel newSocket(SelectorProvider provider) { | ||||
|     try { | ||||
|       /** | ||||
|        * Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in | ||||
|        * {@link SelectorProvider#provider()} which is called by each SocketChannel.open() otherwise. | ||||
|        * | ||||
|        * <p>See <a href="https://github.com/netty/netty/issues/2308">#2308</a>. | ||||
|        */ | ||||
|       return provider.openSocketChannel(); | ||||
|     } catch (IOException e) { | ||||
|       throw new ChannelException("Failed to open a socket.", e); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private final SocketChannelConfig config; | ||||
| 
 | ||||
|   /** Create a new instance */ | ||||
|   public NioSocketChannel() { | ||||
|     this(DEFAULT_SELECTOR_PROVIDER); | ||||
|   } | ||||
| 
 | ||||
|   /** Create a new instance using the given {@link SelectorProvider}. */ | ||||
|   public NioSocketChannel(SelectorProvider provider) { | ||||
|     this(newSocket(provider)); | ||||
|   } | ||||
| 
 | ||||
|   /** Create a new instance using the given {@link SocketChannel}. */ | ||||
|   public NioSocketChannel(SocketChannel socket) { | ||||
|     this(null, socket); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Create a new instance | ||||
|    * | ||||
|    * @param parent the {@link Channel} which created this instance or {@code null} if it was created | ||||
|    *     by the user | ||||
|    * @param socket the {@link SocketChannel} which will be used | ||||
|    */ | ||||
|   public NioSocketChannel(Channel parent, SocketChannel socket) { | ||||
|     super(parent, socket); | ||||
|     config = new NioSocketChannelConfig(this, socket.socket()); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ServerSocketChannel parent() { | ||||
|     return (ServerSocketChannel) super.parent(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public SocketChannelConfig config() { | ||||
|     return config; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected SocketChannel javaChannel() { | ||||
|     return (SocketChannel) super.javaChannel(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean isActive() { | ||||
|     SocketChannel ch = javaChannel(); | ||||
|     return ch.isOpen() && ch.isConnected(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean isOutputShutdown() { | ||||
|     return javaChannel().socket().isOutputShutdown() || !isActive(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean isInputShutdown() { | ||||
|     return javaChannel().socket().isInputShutdown() || !isActive(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean isShutdown() { | ||||
|     Socket socket = javaChannel().socket(); | ||||
|     return socket.isInputShutdown() && socket.isOutputShutdown() || !isActive(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public InetSocketAddress localAddress() { | ||||
|     return (InetSocketAddress) super.localAddress(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public InetSocketAddress remoteAddress() { | ||||
|     return (InetSocketAddress) super.remoteAddress(); | ||||
|   } | ||||
| 
 | ||||
|   @SuppressJava6Requirement(reason = "Usage guarded by java version check") | ||||
|   @UnstableApi | ||||
|   @Override | ||||
|   protected final void doShutdownOutput() throws Exception { | ||||
|     if (PlatformDependent.javaVersion() >= 7) { | ||||
|       javaChannel().shutdownOutput(); | ||||
|     } else { | ||||
|       javaChannel().socket().shutdownOutput(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture shutdownOutput() { | ||||
|     return shutdownOutput(newPromise()); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture shutdownOutput(final ChannelPromise promise) { | ||||
|     final EventLoop loop = eventLoop(); | ||||
|     if (loop.inEventLoop()) { | ||||
|       ((AbstractUnsafe) unsafe()).shutdownOutput(promise); | ||||
|     } else { | ||||
|       loop.execute( | ||||
|           new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|               ((AbstractUnsafe) unsafe()).shutdownOutput(promise); | ||||
|             } | ||||
|           }); | ||||
|     } | ||||
|     return promise; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture shutdownInput() { | ||||
|     return shutdownInput(newPromise()); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected boolean isInputShutdown0() { | ||||
|     return isInputShutdown(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture shutdownInput(final ChannelPromise promise) { | ||||
|     EventLoop loop = eventLoop(); | ||||
|     if (loop.inEventLoop()) { | ||||
|       shutdownInput0(promise); | ||||
|     } else { | ||||
|       loop.execute( | ||||
|           new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|               shutdownInput0(promise); | ||||
|             } | ||||
|           }); | ||||
|     } | ||||
|     return promise; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture shutdown() { | ||||
|     return shutdown(newPromise()); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture shutdown(final ChannelPromise promise) { | ||||
|     ChannelFuture shutdownOutputFuture = shutdownOutput(); | ||||
|     if (shutdownOutputFuture.isDone()) { | ||||
|       shutdownOutputDone(shutdownOutputFuture, promise); | ||||
|     } else { | ||||
|       shutdownOutputFuture.addListener( | ||||
|           new ChannelFutureListener() { | ||||
|             @Override | ||||
|             public void operationComplete(final ChannelFuture shutdownOutputFuture) | ||||
|                 throws Exception { | ||||
|               shutdownOutputDone(shutdownOutputFuture, promise); | ||||
|             } | ||||
|           }); | ||||
|     } | ||||
|     return promise; | ||||
|   } | ||||
| 
 | ||||
|   private void shutdownOutputDone( | ||||
|       final ChannelFuture shutdownOutputFuture, final ChannelPromise promise) { | ||||
|     ChannelFuture shutdownInputFuture = shutdownInput(); | ||||
|     if (shutdownInputFuture.isDone()) { | ||||
|       shutdownDone(shutdownOutputFuture, shutdownInputFuture, promise); | ||||
|     } else { | ||||
|       shutdownInputFuture.addListener( | ||||
|           new ChannelFutureListener() { | ||||
|             @Override | ||||
|             public void operationComplete(ChannelFuture shutdownInputFuture) throws Exception { | ||||
|               shutdownDone(shutdownOutputFuture, shutdownInputFuture, promise); | ||||
|             } | ||||
|           }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static void shutdownDone( | ||||
|       ChannelFuture shutdownOutputFuture, | ||||
|       ChannelFuture shutdownInputFuture, | ||||
|       ChannelPromise promise) { | ||||
|     Throwable shutdownOutputCause = shutdownOutputFuture.cause(); | ||||
|     Throwable shutdownInputCause = shutdownInputFuture.cause(); | ||||
|     if (shutdownOutputCause != null) { | ||||
|       if (shutdownInputCause != null) { | ||||
|         logger.debug( | ||||
|             "Exception suppressed because a previous exception occurred.", shutdownInputCause); | ||||
|       } | ||||
|       promise.setFailure(shutdownOutputCause); | ||||
|     } else if (shutdownInputCause != null) { | ||||
|       promise.setFailure(shutdownInputCause); | ||||
|     } else { | ||||
|       promise.setSuccess(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void shutdownInput0(final ChannelPromise promise) { | ||||
|     try { | ||||
|       shutdownInput0(); | ||||
|       promise.setSuccess(); | ||||
|     } catch (Throwable t) { | ||||
|       promise.setFailure(t); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @SuppressJava6Requirement(reason = "Usage guarded by java version check") | ||||
|   private void shutdownInput0() throws Exception { | ||||
|     if (PlatformDependent.javaVersion() >= 7) { | ||||
|       javaChannel().shutdownInput(); | ||||
|     } else { | ||||
|       javaChannel().socket().shutdownInput(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected SocketAddress localAddress0() { | ||||
|     return javaChannel().socket().getLocalSocketAddress(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected SocketAddress remoteAddress0() { | ||||
|     return javaChannel().socket().getRemoteSocketAddress(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected void doBind(SocketAddress localAddress) throws Exception { | ||||
|     doBind0(localAddress); | ||||
|   } | ||||
| 
 | ||||
|   private void doBind0(SocketAddress localAddress) throws Exception { | ||||
|     if (PlatformDependent.javaVersion() >= 7) { | ||||
|       SocketUtils.bind(javaChannel(), localAddress); | ||||
|     } else { | ||||
|       SocketUtils.bind(javaChannel().socket(), localAddress); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) | ||||
|       throws Exception { | ||||
|     if (localAddress != null) { | ||||
|       doBind0(localAddress); | ||||
|     } | ||||
| 
 | ||||
|     boolean success = false; | ||||
|     try { | ||||
|       boolean connected = SocketUtils.connect(javaChannel(), remoteAddress); | ||||
|       if (!connected) { | ||||
|         selectionKey().interestOps(SelectionKey.OP_CONNECT); | ||||
|       } | ||||
|       success = true; | ||||
|       return connected; | ||||
|     } finally { | ||||
|       if (!success) { | ||||
|         doClose(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected void doFinishConnect() throws Exception { | ||||
|     if (!javaChannel().finishConnect()) { | ||||
|       throw new Error(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected void doDisconnect() throws Exception { | ||||
|     doClose(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected void doClose() throws Exception { | ||||
|     super.doClose(); | ||||
|     javaChannel().close(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected int doReadBytes(ByteBuf byteBuf) throws Exception { | ||||
|     final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); | ||||
|     allocHandle.attemptedBytesRead(byteBuf.writableBytes()); | ||||
|     return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead()); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected int doWriteBytes(ByteBuf buf) throws Exception { | ||||
|     final int expectedWrittenBytes = buf.readableBytes(); | ||||
|     return buf.readBytes(javaChannel(), expectedWrittenBytes); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected long doWriteFileRegion(FileRegion region) throws Exception { | ||||
|     final long position = region.transferred(); | ||||
|     return region.transferTo(javaChannel(), position); | ||||
|   } | ||||
| 
 | ||||
|   private void adjustMaxBytesPerGatheringWrite( | ||||
|       int attempted, int written, int oldMaxBytesPerGatheringWrite) { | ||||
|     // By default we track the SO_SNDBUF when ever it is explicitly set. However some OSes may | ||||
|     // dynamically change | ||||
|     // SO_SNDBUF (and other characteristics that determine how much data can be written at once) so | ||||
|     // we should try | ||||
|     // make a best effort to adjust as OS behavior changes. | ||||
|     if (attempted == written) { | ||||
|       if (attempted << 1 > oldMaxBytesPerGatheringWrite) { | ||||
|         ((NioSocketChannelConfig) config).setMaxBytesPerGatheringWrite(attempted << 1); | ||||
|       } | ||||
|     } else if (attempted > MAX_BYTES_PER_GATHERING_WRITE_ATTEMPTED_LOW_THRESHOLD | ||||
|         && written < attempted >>> 1) { | ||||
|       ((NioSocketChannelConfig) config).setMaxBytesPerGatheringWrite(attempted >>> 1); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected void doWrite(ChannelOutboundBuffer in) throws Exception { | ||||
|     SocketChannel ch = javaChannel(); | ||||
|     int writeSpinCount = config().getWriteSpinCount(); | ||||
|     do { | ||||
|       if (in.isEmpty()) { | ||||
|         // All written so clear OP_WRITE | ||||
|         clearOpWrite(); | ||||
|         // Directly return here so incompleteWrite(...) is not called. | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       // Ensure the pending writes are made of ByteBufs only. | ||||
|       int maxBytesPerGatheringWrite = | ||||
|           ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite(); | ||||
|       ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite); | ||||
|       int nioBufferCnt = in.nioBufferCount(); | ||||
| 
 | ||||
|       // Always use nioBuffers() to workaround data-corruption. | ||||
|       // See https://github.com/netty/netty/issues/2761 | ||||
|       switch (nioBufferCnt) { | ||||
|         case 0: | ||||
|           // We have something else beside ByteBuffers to write so fallback to normal writes. | ||||
|           writeSpinCount -= doWrite0(in); | ||||
|           break; | ||||
|         case 1: | ||||
|           { | ||||
|             // Only one ByteBuf so use non-gathering write | ||||
|             // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is | ||||
|             // no need | ||||
|             // to check if the total size of all the buffers is non-zero. | ||||
|             ByteBuffer buffer = nioBuffers[0]; | ||||
|             int attemptedBytes = buffer.remaining(); | ||||
|             final int localWrittenBytes = ch.write(buffer); | ||||
|             if (localWrittenBytes <= 0) { | ||||
|               incompleteWrite(true); | ||||
|               return; | ||||
|             } | ||||
|             adjustMaxBytesPerGatheringWrite( | ||||
|                 attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite); | ||||
|             in.removeBytes(localWrittenBytes); | ||||
|             --writeSpinCount; | ||||
|             break; | ||||
|           } | ||||
|         default: | ||||
|           { | ||||
|             // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is | ||||
|             // no need | ||||
|             // to check if the total size of all the buffers is non-zero. | ||||
|             // We limit the max amount to int above so cast is safe | ||||
|             long attemptedBytes = in.nioBufferSize(); | ||||
|             final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt); | ||||
|             if (localWrittenBytes <= 0) { | ||||
|               incompleteWrite(true); | ||||
|               return; | ||||
|             } | ||||
|             // Casting to int is safe because we limit the total amount of data in the nioBuffers to | ||||
|             // int above. | ||||
|             adjustMaxBytesPerGatheringWrite( | ||||
|                 (int) attemptedBytes, (int) localWrittenBytes, maxBytesPerGatheringWrite); | ||||
|             in.removeBytes(localWrittenBytes); | ||||
|             --writeSpinCount; | ||||
|             break; | ||||
|           } | ||||
|       } | ||||
|     } while (writeSpinCount > 0); | ||||
| 
 | ||||
|     incompleteWrite(writeSpinCount < 0); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected AbstractNioUnsafe newUnsafe() { | ||||
|     return new NioSocketChannelUnsafe(); | ||||
|   } | ||||
| 
 | ||||
|   private final class NioSocketChannelUnsafe extends NioByteUnsafe { | ||||
|     @Override | ||||
|     protected Executor prepareToClose() { | ||||
|       try { | ||||
|         if (javaChannel().isOpen() && config().getSoLinger() > 0) { | ||||
|           // We need to cancel this key of the channel so we may not end up in a eventloop spin | ||||
|           // because we try to read or write until the actual close happens which may be later due | ||||
|           // SO_LINGER handling. | ||||
|           // See https://github.com/netty/netty/issues/4449 | ||||
|           doDeregister(); | ||||
|           return GlobalEventExecutor.INSTANCE; | ||||
|         } | ||||
|       } catch (Throwable ignore) { | ||||
|         // Ignore the error as the underlying channel may be closed in the meantime and so | ||||
|         // getSoLinger() may produce an exception. In this case we just return null. | ||||
|         // See https://github.com/netty/netty/issues/4449 | ||||
|       } | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private final class NioSocketChannelConfig extends DefaultSocketChannelConfig { | ||||
|     private volatile int maxBytesPerGatheringWrite = Integer.MAX_VALUE; | ||||
| 
 | ||||
|     private NioSocketChannelConfig(NioSocketChannel channel, Socket javaSocket) { | ||||
|       super(channel, javaSocket); | ||||
|       calculateMaxBytesPerGatheringWrite(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void autoReadCleared() { | ||||
|       clearReadPending(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public NioSocketChannelConfig setSendBufferSize(int sendBufferSize) { | ||||
|       super.setSendBufferSize(sendBufferSize); | ||||
|       calculateMaxBytesPerGatheringWrite(); | ||||
|       return this; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public <T> boolean setOption(ChannelOption<T> option, T value) { | ||||
|       if (PlatformDependent.javaVersion() >= 7 && option instanceof NioChannelOption) { | ||||
|         return NioChannelOption.setOption(jdkChannel(), (NioChannelOption<T>) option, value); | ||||
|       } | ||||
|       return super.setOption(option, value); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public <T> T getOption(ChannelOption<T> option) { | ||||
|       if (PlatformDependent.javaVersion() >= 7 && option instanceof NioChannelOption) { | ||||
|         return NioChannelOption.getOption(jdkChannel(), (NioChannelOption<T>) option); | ||||
|       } | ||||
|       return super.getOption(option); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Map<ChannelOption<?>, Object> getOptions() { | ||||
|       if (PlatformDependent.javaVersion() >= 7) { | ||||
|         return getOptions(super.getOptions(), NioChannelOption.getOptions(jdkChannel())); | ||||
|       } | ||||
|       return super.getOptions(); | ||||
|     } | ||||
| 
 | ||||
|     void setMaxBytesPerGatheringWrite(int maxBytesPerGatheringWrite) { | ||||
|       this.maxBytesPerGatheringWrite = maxBytesPerGatheringWrite; | ||||
|     } | ||||
| 
 | ||||
|     int getMaxBytesPerGatheringWrite() { | ||||
|       return maxBytesPerGatheringWrite; | ||||
|     } | ||||
| 
 | ||||
|     private void calculateMaxBytesPerGatheringWrite() { | ||||
|       // Multiply by 2 to give some extra space in case the OS can process write data faster than we | ||||
|       // can provide. | ||||
|       int newSendBufferSize = getSendBufferSize() << 1; | ||||
|       if (newSendBufferSize > 0) { | ||||
|         setMaxBytesPerGatheringWrite(newSendBufferSize); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     private SocketChannel jdkChannel() { | ||||
|       return ((NioSocketChannel) channel).javaChannel(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,708 @@ | |||
| /* | ||||
|  * Copyright 2014 The Netty Project | ||||
|  * | ||||
|  * The Netty Project licenses this file to you under the Apache License, version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. You may obtain a | ||||
|  * copy of the License at: | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software distributed under the License | ||||
|  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||||
|  * or implied. See the License for the specific language governing permissions and limitations under | ||||
|  * the License. | ||||
|  */ | ||||
| 
 | ||||
| package io.netty.handler.codec.http2; | ||||
| 
 | ||||
| import static io.netty.buffer.Unpooled.directBuffer; | ||||
| import static io.netty.buffer.Unpooled.unreleasableBuffer; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.CONTINUATION_FRAME_HEADER_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.DATA_FRAME_HEADER_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.GO_AWAY_FRAME_HEADER_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.HEADERS_FRAME_HEADER_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.PING_FRAME_PAYLOAD_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_FRAME_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.PUSH_PROMISE_FRAME_HEADER_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.RST_STREAM_FRAME_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.WINDOW_UPDATE_FRAME_LENGTH; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.verifyPadding; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeaderInternal; | ||||
| import static io.netty.handler.codec.http2.Http2Error.FRAME_SIZE_ERROR; | ||||
| import static io.netty.handler.codec.http2.Http2Exception.connectionError; | ||||
| import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION; | ||||
| import static io.netty.handler.codec.http2.Http2FrameTypes.DATA; | ||||
| import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY; | ||||
| import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS; | ||||
| import static io.netty.handler.codec.http2.Http2FrameTypes.PING; | ||||
| import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY; | ||||
| import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE; | ||||
| import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM; | ||||
| import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS; | ||||
| import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE; | ||||
| import static io.netty.util.internal.ObjectUtil.checkNotNull; | ||||
| import static io.netty.util.internal.ObjectUtil.checkPositive; | ||||
| import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; | ||||
| import static java.lang.Math.max; | ||||
| import static java.lang.Math.min; | ||||
| 
 | ||||
| import io.netty.buffer.ByteBuf; | ||||
| import io.netty.channel.ChannelFuture; | ||||
| import io.netty.channel.ChannelHandlerContext; | ||||
| import io.netty.channel.ChannelPromise; | ||||
| import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator; | ||||
| import io.netty.handler.codec.http2.Http2FrameWriter.Configuration; | ||||
| import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector; | ||||
| import io.netty.util.internal.PlatformDependent; | ||||
| import io.netty.util.internal.UnstableApi; | ||||
| 
 | ||||
| /** A {@link Http2FrameWriter} that supports all frame types defined by the HTTP/2 specification. */ | ||||
| @UnstableApi | ||||
| public class DefaultHttp2FrameWriter | ||||
|     implements Http2FrameWriter, Http2FrameSizePolicy, Configuration { | ||||
|   private static final String STREAM_ID = "Stream ID"; | ||||
|   private static final String STREAM_DEPENDENCY = "Stream Dependency"; | ||||
|   /** | ||||
|    * This buffer is allocated to the maximum size of the padding field, and filled with zeros. When | ||||
|    * padding is needed it can be taken as a slice of this buffer. Users should call {@link | ||||
|    * ByteBuf#retain()} before using their slice. | ||||
|    */ | ||||
|   private static final ByteBuf ZERO_BUFFER = | ||||
|       unreleasableBuffer(directBuffer(MAX_UNSIGNED_BYTE).writeZero(MAX_UNSIGNED_BYTE)).asReadOnly(); | ||||
| 
 | ||||
|   private final Http2HeadersEncoder headersEncoder; | ||||
|   private int maxFrameSize; | ||||
| 
 | ||||
|   public DefaultHttp2FrameWriter() { | ||||
|     this(new DefaultHttp2HeadersEncoder()); | ||||
|   } | ||||
| 
 | ||||
|   public DefaultHttp2FrameWriter(SensitivityDetector headersSensitivityDetector) { | ||||
|     this(new DefaultHttp2HeadersEncoder(headersSensitivityDetector)); | ||||
|   } | ||||
| 
 | ||||
|   public DefaultHttp2FrameWriter( | ||||
|       SensitivityDetector headersSensitivityDetector, boolean ignoreMaxHeaderListSize) { | ||||
|     this(new DefaultHttp2HeadersEncoder(headersSensitivityDetector, ignoreMaxHeaderListSize)); | ||||
|   } | ||||
| 
 | ||||
|   public DefaultHttp2FrameWriter(Http2HeadersEncoder headersEncoder) { | ||||
|     this.headersEncoder = headersEncoder; | ||||
|     maxFrameSize = DEFAULT_MAX_FRAME_SIZE; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Configuration configuration() { | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Http2HeadersEncoder.Configuration headersConfiguration() { | ||||
|     return headersEncoder.configuration(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Http2FrameSizePolicy frameSizePolicy() { | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void maxFrameSize(int max) throws Http2Exception { | ||||
|     if (!isMaxFrameSizeValid(max)) { | ||||
|       throw connectionError( | ||||
|           FRAME_SIZE_ERROR, "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max); | ||||
|     } | ||||
|     maxFrameSize = max; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public int maxFrameSize() { | ||||
|     return maxFrameSize; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void close() {} | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeData( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       ByteBuf data, | ||||
|       int padding, | ||||
|       boolean endStream, | ||||
|       ChannelPromise promise) { | ||||
|     final SimpleChannelPromiseAggregator promiseAggregator = | ||||
|         new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); | ||||
|     ByteBuf frameHeader = null; | ||||
|     try { | ||||
|       verifyStreamId(streamId, STREAM_ID); | ||||
|       verifyPadding(padding); | ||||
| 
 | ||||
|       int remainingData = data.readableBytes(); | ||||
|       Http2Flags flags = new Http2Flags(); | ||||
|       flags.endOfStream(false); | ||||
|       flags.paddingPresent(false); | ||||
|       // Fast path to write frames of payload size maxFrameSize first. | ||||
|       if (remainingData > maxFrameSize) { | ||||
|         frameHeader = ctx.alloc().buffer(FRAME_HEADER_LENGTH); | ||||
|         writeFrameHeaderInternal(frameHeader, maxFrameSize, DATA, flags, streamId); | ||||
|         do { | ||||
|           // Write the header. | ||||
|           ctx.write(frameHeader.retainedSlice(), promiseAggregator.newPromise()); | ||||
| 
 | ||||
|           // Write the payload. | ||||
|           ctx.write(data.readRetainedSlice(maxFrameSize), promiseAggregator.newPromise()); | ||||
| 
 | ||||
|           remainingData -= maxFrameSize; | ||||
|           // Stop iterating if remainingData == maxFrameSize so we can take care of reference counts | ||||
|           // below. | ||||
|         } while (remainingData > maxFrameSize); | ||||
|       } | ||||
| 
 | ||||
|       if (padding == 0) { | ||||
|         // Write the header. | ||||
|         if (frameHeader != null) { | ||||
|           frameHeader.release(); | ||||
|           frameHeader = null; | ||||
|         } | ||||
|         ByteBuf frameHeader2 = ctx.alloc().buffer(FRAME_HEADER_LENGTH); | ||||
|         flags.endOfStream(endStream); | ||||
|         writeFrameHeaderInternal(frameHeader2, remainingData, DATA, flags, streamId); | ||||
|         ctx.write(frameHeader2, promiseAggregator.newPromise()); | ||||
| 
 | ||||
|         // Write the payload. | ||||
|         ByteBuf lastFrame = data.readSlice(remainingData); | ||||
|         data = null; | ||||
|         ctx.write(lastFrame, promiseAggregator.newPromise()); | ||||
|       } else { | ||||
|         if (remainingData != maxFrameSize) { | ||||
|           if (frameHeader != null) { | ||||
|             frameHeader.release(); | ||||
|             frameHeader = null; | ||||
|           } | ||||
|         } else { | ||||
|           remainingData -= maxFrameSize; | ||||
|           // Write the header. | ||||
|           ByteBuf lastFrame; | ||||
|           if (frameHeader == null) { | ||||
|             lastFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH); | ||||
|             writeFrameHeaderInternal(lastFrame, maxFrameSize, DATA, flags, streamId); | ||||
|           } else { | ||||
|             lastFrame = frameHeader.slice(); | ||||
|             frameHeader = null; | ||||
|           } | ||||
|           ctx.write(lastFrame, promiseAggregator.newPromise()); | ||||
| 
 | ||||
|           // Write the payload. | ||||
|           lastFrame = data.readableBytes() != maxFrameSize ? data.readSlice(maxFrameSize) : data; | ||||
|           data = null; | ||||
|           ctx.write(lastFrame, promiseAggregator.newPromise()); | ||||
|         } | ||||
| 
 | ||||
|         do { | ||||
|           int frameDataBytes = min(remainingData, maxFrameSize); | ||||
|           int framePaddingBytes = min(padding, max(0, (maxFrameSize - 1) - frameDataBytes)); | ||||
| 
 | ||||
|           // Decrement the remaining counters. | ||||
|           padding -= framePaddingBytes; | ||||
|           remainingData -= frameDataBytes; | ||||
| 
 | ||||
|           // Write the header. | ||||
|           ByteBuf frameHeader2 = ctx.alloc().buffer(DATA_FRAME_HEADER_LENGTH); | ||||
|           flags.endOfStream(endStream && remainingData == 0 && padding == 0); | ||||
|           flags.paddingPresent(framePaddingBytes > 0); | ||||
|           writeFrameHeaderInternal( | ||||
|               frameHeader2, framePaddingBytes + frameDataBytes, DATA, flags, streamId); | ||||
|           writePaddingLength(frameHeader2, framePaddingBytes); | ||||
|           ctx.write(frameHeader2, promiseAggregator.newPromise()); | ||||
| 
 | ||||
|           // Write the payload. | ||||
|           if (frameDataBytes != 0) { | ||||
|             if (remainingData == 0) { | ||||
|               ByteBuf lastFrame = data.readSlice(frameDataBytes); | ||||
|               data = null; | ||||
|               ctx.write(lastFrame, promiseAggregator.newPromise()); | ||||
|             } else { | ||||
|               ctx.write(data.readRetainedSlice(frameDataBytes), promiseAggregator.newPromise()); | ||||
|             } | ||||
|           } | ||||
|           // Write the frame padding. | ||||
|           if (paddingBytes(framePaddingBytes) > 0) { | ||||
|             ctx.write( | ||||
|                 ZERO_BUFFER.slice(0, paddingBytes(framePaddingBytes)), | ||||
|                 promiseAggregator.newPromise()); | ||||
|           } | ||||
|         } while (remainingData != 0 || padding != 0); | ||||
|       } | ||||
|     } catch (Throwable cause) { | ||||
|       if (frameHeader != null) { | ||||
|         frameHeader.release(); | ||||
|       } | ||||
|       // Use a try/finally here in case the data has been released before calling this method. This | ||||
|       // is not | ||||
|       // necessary above because we internally allocate frameHeader. | ||||
|       try { | ||||
|         if (data != null) { | ||||
|           data.release(); | ||||
|         } | ||||
|       } finally { | ||||
|         promiseAggregator.setFailure(cause); | ||||
|         promiseAggregator.doneAllocatingPromises(); | ||||
|       } | ||||
|       return promiseAggregator; | ||||
|     } | ||||
|     return promiseAggregator.doneAllocatingPromises(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeHeaders( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       Http2Headers headers, | ||||
|       int padding, | ||||
|       boolean endStream, | ||||
|       ChannelPromise promise) { | ||||
|     return writeHeadersInternal( | ||||
|         ctx, streamId, headers, padding, endStream, false, 0, (short) 0, false, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeHeaders( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       Http2Headers headers, | ||||
|       int streamDependency, | ||||
|       short weight, | ||||
|       boolean exclusive, | ||||
|       int padding, | ||||
|       boolean endStream, | ||||
|       ChannelPromise promise) { | ||||
|     return writeHeadersInternal( | ||||
|         ctx, | ||||
|         streamId, | ||||
|         headers, | ||||
|         padding, | ||||
|         endStream, | ||||
|         true, | ||||
|         streamDependency, | ||||
|         weight, | ||||
|         exclusive, | ||||
|         promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writePriority( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       int streamDependency, | ||||
|       short weight, | ||||
|       boolean exclusive, | ||||
|       ChannelPromise promise) { | ||||
|     try { | ||||
|       verifyStreamId(streamId, STREAM_ID); | ||||
|       verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY); | ||||
|       verifyWeight(weight); | ||||
| 
 | ||||
|       ByteBuf buf = ctx.alloc().buffer(PRIORITY_FRAME_LENGTH); | ||||
|       writeFrameHeaderInternal(buf, PRIORITY_ENTRY_LENGTH, PRIORITY, new Http2Flags(), streamId); | ||||
|       buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency); | ||||
|       // Adjust the weight so that it fits into a single byte on the wire. | ||||
|       buf.writeByte(weight - 1); | ||||
|       return ctx.write(buf, promise); | ||||
|     } catch (Throwable t) { | ||||
|       return promise.setFailure(t); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeRstStream( | ||||
|       ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) { | ||||
|     try { | ||||
|       verifyStreamId(streamId, STREAM_ID); | ||||
|       verifyErrorCode(errorCode); | ||||
| 
 | ||||
|       ByteBuf buf = ctx.alloc().buffer(RST_STREAM_FRAME_LENGTH); | ||||
|       writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(), streamId); | ||||
|       buf.writeInt((int) errorCode); | ||||
|       return ctx.write(buf, promise); | ||||
|     } catch (Throwable t) { | ||||
|       return promise.setFailure(t); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeSettings( | ||||
|       ChannelHandlerContext ctx, Http2Settings settings, ChannelPromise promise) { | ||||
|     try { | ||||
|       checkNotNull(settings, "settings"); | ||||
|       int payloadLength = SETTING_ENTRY_LENGTH * settings.size(); | ||||
|       ByteBuf buf = | ||||
|           ctx.alloc().buffer(FRAME_HEADER_LENGTH + settings.size() * SETTING_ENTRY_LENGTH); | ||||
|       writeFrameHeaderInternal(buf, payloadLength, SETTINGS, new Http2Flags(), 0); | ||||
|       for (Http2Settings.PrimitiveEntry<Long> entry : settings.entries()) { | ||||
|         buf.writeChar(entry.key()); | ||||
|         buf.writeInt(entry.value().intValue()); | ||||
|       } | ||||
|       return ctx.write(buf, promise); | ||||
|     } catch (Throwable t) { | ||||
|       return promise.setFailure(t); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) { | ||||
|     try { | ||||
|       ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH); | ||||
|       writeFrameHeaderInternal(buf, 0, SETTINGS, new Http2Flags().ack(true), 0); | ||||
|       return ctx.write(buf, promise); | ||||
|     } catch (Throwable t) { | ||||
|       return promise.setFailure(t); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writePing( | ||||
|       ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise) { | ||||
|     Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags(); | ||||
|     ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PING_FRAME_PAYLOAD_LENGTH); | ||||
|     // Assume nothing below will throw until buf is written. That way we don't have to take care of | ||||
|     // ownership | ||||
|     // in the catch block. | ||||
|     writeFrameHeaderInternal(buf, PING_FRAME_PAYLOAD_LENGTH, PING, flags, 0); | ||||
|     buf.writeLong(data); | ||||
|     return ctx.write(buf, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writePushPromise( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       int promisedStreamId, | ||||
|       Http2Headers headers, | ||||
|       int padding, | ||||
|       ChannelPromise promise) { | ||||
|     ByteBuf headerBlock = null; | ||||
|     SimpleChannelPromiseAggregator promiseAggregator = | ||||
|         new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); | ||||
|     try { | ||||
|       verifyStreamId(streamId, STREAM_ID); | ||||
|       verifyStreamId(promisedStreamId, "Promised Stream ID"); | ||||
|       verifyPadding(padding); | ||||
| 
 | ||||
|       // Encode the entire header block into an intermediate buffer. | ||||
|       headerBlock = ctx.alloc().buffer(); | ||||
|       headersEncoder.encodeHeaders(streamId, headers, headerBlock); | ||||
| 
 | ||||
|       // Read the first fragment (possibly everything). | ||||
|       Http2Flags flags = new Http2Flags().paddingPresent(padding > 0); | ||||
|       // INT_FIELD_LENGTH is for the length of the promisedStreamId | ||||
|       int nonFragmentLength = INT_FIELD_LENGTH + padding; | ||||
|       int maxFragmentLength = maxFrameSize - nonFragmentLength; | ||||
|       ByteBuf fragment = | ||||
|           headerBlock.readRetainedSlice(min(headerBlock.readableBytes(), maxFragmentLength)); | ||||
| 
 | ||||
|       flags.endOfHeaders(!headerBlock.isReadable()); | ||||
| 
 | ||||
|       int payloadLength = fragment.readableBytes() + nonFragmentLength; | ||||
|       ByteBuf buf = ctx.alloc().buffer(PUSH_PROMISE_FRAME_HEADER_LENGTH); | ||||
|       writeFrameHeaderInternal(buf, payloadLength, PUSH_PROMISE, flags, streamId); | ||||
|       writePaddingLength(buf, padding); | ||||
| 
 | ||||
|       // Write out the promised stream ID. | ||||
|       buf.writeInt(promisedStreamId); | ||||
|       ctx.write(buf, promiseAggregator.newPromise()); | ||||
| 
 | ||||
|       // Write the first fragment. | ||||
|       ctx.write(fragment, promiseAggregator.newPromise()); | ||||
| 
 | ||||
|       // Write out the padding, if any. | ||||
|       if (paddingBytes(padding) > 0) { | ||||
|         ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise()); | ||||
|       } | ||||
| 
 | ||||
|       if (!flags.endOfHeaders()) { | ||||
|         writeContinuationFrames(ctx, streamId, headerBlock, promiseAggregator); | ||||
|       } | ||||
|     } catch (Http2Exception e) { | ||||
|       promiseAggregator.setFailure(e); | ||||
|     } catch (Throwable t) { | ||||
|       promiseAggregator.setFailure(t); | ||||
|       promiseAggregator.doneAllocatingPromises(); | ||||
|       PlatformDependent.throwException(t); | ||||
|     } finally { | ||||
|       if (headerBlock != null) { | ||||
|         headerBlock.release(); | ||||
|       } | ||||
|     } | ||||
|     return promiseAggregator.doneAllocatingPromises(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeGoAway( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int lastStreamId, | ||||
|       long errorCode, | ||||
|       ByteBuf debugData, | ||||
|       ChannelPromise promise) { | ||||
|     SimpleChannelPromiseAggregator promiseAggregator = | ||||
|         new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); | ||||
|     try { | ||||
|       verifyStreamOrConnectionId(lastStreamId, "Last Stream ID"); | ||||
|       verifyErrorCode(errorCode); | ||||
| 
 | ||||
|       int payloadLength = 8 + debugData.readableBytes(); | ||||
|       ByteBuf buf = ctx.alloc().buffer(GO_AWAY_FRAME_HEADER_LENGTH); | ||||
|       // Assume nothing below will throw until buf is written. That way we don't have to take care | ||||
|       // of ownership | ||||
|       // in the catch block. | ||||
|       writeFrameHeaderInternal(buf, payloadLength, GO_AWAY, new Http2Flags(), 0); | ||||
|       buf.writeInt(lastStreamId); | ||||
|       buf.writeInt((int) errorCode); | ||||
|       ctx.write(buf, promiseAggregator.newPromise()); | ||||
|     } catch (Throwable t) { | ||||
|       try { | ||||
|         debugData.release(); | ||||
|       } finally { | ||||
|         promiseAggregator.setFailure(t); | ||||
|         promiseAggregator.doneAllocatingPromises(); | ||||
|       } | ||||
|       return promiseAggregator; | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       ctx.write(debugData, promiseAggregator.newPromise()); | ||||
|     } catch (Throwable t) { | ||||
|       promiseAggregator.setFailure(t); | ||||
|     } | ||||
|     return promiseAggregator.doneAllocatingPromises(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeWindowUpdate( | ||||
|       ChannelHandlerContext ctx, int streamId, int windowSizeIncrement, ChannelPromise promise) { | ||||
|     try { | ||||
|       verifyStreamOrConnectionId(streamId, STREAM_ID); | ||||
|       verifyWindowSizeIncrement(windowSizeIncrement); | ||||
| 
 | ||||
|       ByteBuf buf = ctx.alloc().buffer(WINDOW_UPDATE_FRAME_LENGTH); | ||||
|       writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, WINDOW_UPDATE, new Http2Flags(), streamId); | ||||
|       buf.writeInt(windowSizeIncrement); | ||||
|       return ctx.write(buf, promise); | ||||
|     } catch (Throwable t) { | ||||
|       return promise.setFailure(t); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeFrame( | ||||
|       ChannelHandlerContext ctx, | ||||
|       byte frameType, | ||||
|       int streamId, | ||||
|       Http2Flags flags, | ||||
|       ByteBuf payload, | ||||
|       ChannelPromise promise) { | ||||
|     SimpleChannelPromiseAggregator promiseAggregator = | ||||
|         new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); | ||||
|     try { | ||||
|       verifyStreamOrConnectionId(streamId, STREAM_ID); | ||||
|       ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH); | ||||
|       // Assume nothing below will throw until buf is written. That way we don't have to take care | ||||
|       // of ownership | ||||
|       // in the catch block. | ||||
|       writeFrameHeaderInternal(buf, payload.readableBytes(), frameType, flags, streamId); | ||||
|       ctx.write(buf, promiseAggregator.newPromise()); | ||||
|     } catch (Throwable t) { | ||||
|       try { | ||||
|         payload.release(); | ||||
|       } finally { | ||||
|         promiseAggregator.setFailure(t); | ||||
|         promiseAggregator.doneAllocatingPromises(); | ||||
|       } | ||||
|       return promiseAggregator; | ||||
|     } | ||||
|     try { | ||||
|       ctx.write(payload, promiseAggregator.newPromise()); | ||||
|     } catch (Throwable t) { | ||||
|       promiseAggregator.setFailure(t); | ||||
|     } | ||||
|     return promiseAggregator.doneAllocatingPromises(); | ||||
|   } | ||||
| 
 | ||||
|   private ChannelFuture writeHeadersInternal( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       Http2Headers headers, | ||||
|       int padding, | ||||
|       boolean endStream, | ||||
|       boolean hasPriority, | ||||
|       int streamDependency, | ||||
|       short weight, | ||||
|       boolean exclusive, | ||||
|       ChannelPromise promise) { | ||||
|     ByteBuf headerBlock = null; | ||||
|     SimpleChannelPromiseAggregator promiseAggregator = | ||||
|         new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); | ||||
|     try { | ||||
|       verifyStreamId(streamId, STREAM_ID); | ||||
|       if (hasPriority) { | ||||
|         verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY); | ||||
|         verifyPadding(padding); | ||||
|         verifyWeight(weight); | ||||
|       } | ||||
| 
 | ||||
|       // Encode the entire header block. | ||||
|       headerBlock = ctx.alloc().buffer(); | ||||
|       headersEncoder.encodeHeaders(streamId, headers, headerBlock); | ||||
| 
 | ||||
|       Http2Flags flags = | ||||
|           new Http2Flags() | ||||
|               .endOfStream(endStream) | ||||
|               .priorityPresent(hasPriority) | ||||
|               .paddingPresent(padding > 0); | ||||
| 
 | ||||
|       // Read the first fragment (possibly everything). | ||||
|       int nonFragmentBytes = padding + flags.getNumPriorityBytes(); | ||||
|       int maxFragmentLength = maxFrameSize - nonFragmentBytes; | ||||
|       ByteBuf fragment = | ||||
|           headerBlock.readRetainedSlice(min(headerBlock.readableBytes(), maxFragmentLength)); | ||||
| 
 | ||||
|       // Set the end of headers flag for the first frame. | ||||
|       flags.endOfHeaders(!headerBlock.isReadable()); | ||||
| 
 | ||||
|       int payloadLength = fragment.readableBytes() + nonFragmentBytes; | ||||
|       ByteBuf buf = ctx.alloc().buffer(HEADERS_FRAME_HEADER_LENGTH); | ||||
|       writeFrameHeaderInternal(buf, payloadLength, HEADERS, flags, streamId); | ||||
|       writePaddingLength(buf, padding); | ||||
| 
 | ||||
|       if (hasPriority) { | ||||
|         buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency); | ||||
| 
 | ||||
|         // Adjust the weight so that it fits into a single byte on the wire. | ||||
|         buf.writeByte(weight - 1); | ||||
|       } | ||||
|       ctx.write(buf, promiseAggregator.newPromise()); | ||||
| 
 | ||||
|       // Write the first fragment. | ||||
|       ctx.write(fragment, promiseAggregator.newPromise()); | ||||
| 
 | ||||
|       // Write out the padding, if any. | ||||
|       if (paddingBytes(padding) > 0) { | ||||
|         ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise()); | ||||
|       } | ||||
| 
 | ||||
|       if (!flags.endOfHeaders()) { | ||||
|         writeContinuationFrames(ctx, streamId, headerBlock, promiseAggregator); | ||||
|       } | ||||
|     } catch (Http2Exception e) { | ||||
|       promiseAggregator.setFailure(e); | ||||
|     } catch (Throwable t) { | ||||
|       promiseAggregator.setFailure(t); | ||||
|       promiseAggregator.doneAllocatingPromises(); | ||||
|       PlatformDependent.throwException(t); | ||||
|     } finally { | ||||
|       if (headerBlock != null) { | ||||
|         headerBlock.release(); | ||||
|       } | ||||
|     } | ||||
|     return promiseAggregator.doneAllocatingPromises(); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Writes as many continuation frames as needed until {@code padding} and {@code headerBlock} are | ||||
|    * consumed. | ||||
|    */ | ||||
|   private ChannelFuture writeContinuationFrames( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       ByteBuf headerBlock, | ||||
|       SimpleChannelPromiseAggregator promiseAggregator) { | ||||
|     Http2Flags flags = new Http2Flags(); | ||||
| 
 | ||||
|     if (headerBlock.isReadable()) { | ||||
|       // The frame header (and padding) only changes on the last frame, so allocate it once and | ||||
|       // re-use | ||||
|       int fragmentReadableBytes = min(headerBlock.readableBytes(), maxFrameSize); | ||||
|       ByteBuf buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH); | ||||
|       writeFrameHeaderInternal(buf, fragmentReadableBytes, CONTINUATION, flags, streamId); | ||||
| 
 | ||||
|       do { | ||||
|         fragmentReadableBytes = min(headerBlock.readableBytes(), maxFrameSize); | ||||
|         ByteBuf fragment = headerBlock.readRetainedSlice(fragmentReadableBytes); | ||||
| 
 | ||||
|         if (headerBlock.isReadable()) { | ||||
|           ctx.write(buf.retain(), promiseAggregator.newPromise()); | ||||
|         } else { | ||||
|           // The frame header is different for the last frame, so re-allocate and release the old | ||||
|           // buffer | ||||
|           flags = flags.endOfHeaders(true); | ||||
|           buf.release(); | ||||
|           buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH); | ||||
|           writeFrameHeaderInternal(buf, fragmentReadableBytes, CONTINUATION, flags, streamId); | ||||
|           ctx.write(buf, promiseAggregator.newPromise()); | ||||
|         } | ||||
| 
 | ||||
|         ctx.write(fragment, promiseAggregator.newPromise()); | ||||
| 
 | ||||
|       } while (headerBlock.isReadable()); | ||||
|     } | ||||
|     return promiseAggregator; | ||||
|   } | ||||
| 
 | ||||
|   /** Returns the number of padding bytes that should be appended to the end of a frame. */ | ||||
|   private static int paddingBytes(int padding) { | ||||
|     // The padding parameter contains the 1 byte pad length field as well as the trailing padding | ||||
|     // bytes. | ||||
|     // Subtract 1, so to only get the number of padding bytes that need to be appended to the end of | ||||
|     // a frame. | ||||
|     return padding - 1; | ||||
|   } | ||||
| 
 | ||||
|   private static void writePaddingLength(ByteBuf buf, int padding) { | ||||
|     if (padding > 0) { | ||||
|       // It is assumed that the padding length has been bounds checked before this | ||||
|       // Minus 1, as the pad length field is included in the padding parameter and is 1 byte wide. | ||||
|       buf.writeByte(padding - 1); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static void verifyStreamId(int streamId, String argumentName) { | ||||
|     checkPositive(streamId, argumentName); | ||||
|   } | ||||
| 
 | ||||
|   private static void verifyStreamOrConnectionId(int streamId, String argumentName) { | ||||
|     checkPositiveOrZero(streamId, argumentName); | ||||
|   } | ||||
| 
 | ||||
|   private static void verifyWeight(short weight) { | ||||
|     if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) { | ||||
|       throw new IllegalArgumentException("Invalid weight: " + weight); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static void verifyErrorCode(long errorCode) { | ||||
|     if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) { | ||||
|       throw new IllegalArgumentException("Invalid errorCode: " + errorCode); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static void verifyWindowSizeIncrement(int windowSizeIncrement) { | ||||
|     checkPositiveOrZero(windowSizeIncrement, "windowSizeIncrement"); | ||||
|   } | ||||
| 
 | ||||
|   private static void verifyPingPayload(ByteBuf data) { | ||||
|     if (data == null || data.readableBytes() != PING_FRAME_PAYLOAD_LENGTH) { | ||||
|       throw new IllegalArgumentException( | ||||
|           "Opaque data must be " + PING_FRAME_PAYLOAD_LENGTH + " bytes"); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,779 @@ | |||
| /* | ||||
|  * Copyright 2014 The Netty Project | ||||
|  * | ||||
|  * The Netty Project licenses this file to you under the Apache License, version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. You may obtain a | ||||
|  * copy of the License at: | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software distributed under the License | ||||
|  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||||
|  * or implied. See the License for the specific language governing permissions and limitations under | ||||
|  * the License. | ||||
|  */ | ||||
| package io.netty.handler.codec.http2; | ||||
| 
 | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT; | ||||
| import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT; | ||||
| import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR; | ||||
| import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; | ||||
| import static io.netty.handler.codec.http2.Http2Error.STREAM_CLOSED; | ||||
| import static io.netty.handler.codec.http2.Http2Exception.streamError; | ||||
| import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_LOCAL; | ||||
| import static io.netty.util.internal.ObjectUtil.checkNotNull; | ||||
| import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; | ||||
| import static java.lang.Math.max; | ||||
| import static java.lang.Math.min; | ||||
| 
 | ||||
| import io.netty.channel.ChannelHandlerContext; | ||||
| import io.netty.util.internal.UnstableApi; | ||||
| import io.netty.util.internal.logging.InternalLogger; | ||||
| import io.netty.util.internal.logging.InternalLoggerFactory; | ||||
| import java.util.ArrayDeque; | ||||
| import java.util.Deque; | ||||
| 
 | ||||
| /** | ||||
|  * Basic implementation of {@link Http2RemoteFlowController}. | ||||
|  * | ||||
|  * <p>This class is <strong>NOT</strong> thread safe. The assumption is all methods must be invoked | ||||
|  * from a single thread. Typically this thread is the event loop thread for the {@link | ||||
|  * ChannelHandlerContext} managed by this class. | ||||
|  */ | ||||
| @UnstableApi | ||||
| public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowController { | ||||
|   private static final InternalLogger logger = | ||||
|       InternalLoggerFactory.getInstance(DefaultHttp2RemoteFlowController.class); | ||||
|   private static final int MIN_WRITABLE_CHUNK = 32 * 1024; | ||||
|   private final Http2Connection connection; | ||||
|   private final Http2Connection.PropertyKey stateKey; | ||||
|   private final StreamByteDistributor streamByteDistributor; | ||||
|   private final FlowState connectionState; | ||||
|   private int initialWindowSize = DEFAULT_WINDOW_SIZE; | ||||
|   private WritabilityMonitor monitor; | ||||
|   private ChannelHandlerContext ctx; | ||||
| 
 | ||||
|   public DefaultHttp2RemoteFlowController(Http2Connection connection) { | ||||
|     this(connection, (Listener) null); | ||||
|   } | ||||
| 
 | ||||
|   public DefaultHttp2RemoteFlowController( | ||||
|       Http2Connection connection, StreamByteDistributor streamByteDistributor) { | ||||
|     this(connection, streamByteDistributor, null); | ||||
|   } | ||||
| 
 | ||||
|   public DefaultHttp2RemoteFlowController(Http2Connection connection, final Listener listener) { | ||||
|     this(connection, new WeightedFairQueueByteDistributor(connection), listener); | ||||
|   } | ||||
| 
 | ||||
|   public DefaultHttp2RemoteFlowController( | ||||
|       Http2Connection connection, | ||||
|       StreamByteDistributor streamByteDistributor, | ||||
|       final Listener listener) { | ||||
|     this.connection = checkNotNull(connection, "connection"); | ||||
|     this.streamByteDistributor = checkNotNull(streamByteDistributor, "streamWriteDistributor"); | ||||
| 
 | ||||
|     // Add a flow state for the connection. | ||||
|     stateKey = connection.newKey(); | ||||
|     connectionState = new FlowState(connection.connectionStream()); | ||||
|     connection.connectionStream().setProperty(stateKey, connectionState); | ||||
| 
 | ||||
|     // Monitor may depend upon connectionState, and so initialize after connectionState | ||||
|     listener(listener); | ||||
|     monitor.windowSize(connectionState, initialWindowSize); | ||||
| 
 | ||||
|     // Register for notification of new streams. | ||||
|     connection.addListener( | ||||
|         new Http2ConnectionAdapter() { | ||||
|           @Override | ||||
|           public void onStreamAdded(Http2Stream stream) { | ||||
|             // If the stream state is not open then the stream is not yet eligible for flow | ||||
|             // controlled frames and | ||||
|             // only requires the ReducedFlowState. Otherwise the full amount of memory is required. | ||||
|             stream.setProperty(stateKey, new FlowState(stream)); | ||||
|           } | ||||
| 
 | ||||
|           @Override | ||||
|           public void onStreamActive(Http2Stream stream) { | ||||
|             // If the object was previously created, but later activated then we have to ensure the | ||||
|             // proper | ||||
|             // initialWindowSize is used. | ||||
|             monitor.windowSize(state(stream), initialWindowSize); | ||||
|           } | ||||
| 
 | ||||
|           @Override | ||||
|           public void onStreamClosed(Http2Stream stream) { | ||||
|             // Any pending frames can never be written, cancel and | ||||
|             // write errors for any pending frames. | ||||
|             state(stream).cancel(STREAM_CLOSED, null); | ||||
|           } | ||||
| 
 | ||||
|           @Override | ||||
|           public void onStreamHalfClosed(Http2Stream stream) { | ||||
|             if (HALF_CLOSED_LOCAL == stream.state()) { | ||||
|               /** | ||||
|                * When this method is called there should not be any pending frames left if the API | ||||
|                * is used correctly. However, it is possible that a erroneous application can sneak | ||||
|                * in a frame even after having already written a frame with the END_STREAM flag set, | ||||
|                * as the stream state might not transition immediately to HALF_CLOSED_LOCAL / CLOSED | ||||
|                * due to flow control delaying the write. | ||||
|                * | ||||
|                * <p>This is to cancel any such illegal writes. | ||||
|                */ | ||||
|               state(stream).cancel(STREAM_CLOSED, null); | ||||
|             } | ||||
|           } | ||||
|         }); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * {@inheritDoc} | ||||
|    * | ||||
|    * <p>Any queued {@link FlowControlled} objects will be sent. | ||||
|    */ | ||||
|   @Override | ||||
|   public void channelHandlerContext(ChannelHandlerContext ctx) throws Http2Exception { | ||||
|     this.ctx = checkNotNull(ctx, "ctx"); | ||||
| 
 | ||||
|     // Writing the pending bytes will not check writability change and instead a writability change | ||||
|     // notification | ||||
|     // to be provided by an explicit call. | ||||
|     channelWritabilityChanged(); | ||||
| 
 | ||||
|     // Don't worry about cleaning up queued frames here if ctx is null. It is expected that all | ||||
|     // streams will be | ||||
|     // closed and the queue cleanup will occur when the stream state transitions occur. | ||||
| 
 | ||||
|     // If any frames have been queued up, we should send them now that we have a channel context. | ||||
|     if (isChannelWritable()) { | ||||
|       writePendingBytes(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelHandlerContext channelHandlerContext() { | ||||
|     return ctx; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void initialWindowSize(int newWindowSize) throws Http2Exception { | ||||
|     assert ctx == null || ctx.executor().inEventLoop(); | ||||
|     monitor.initialWindowSize(newWindowSize); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public int initialWindowSize() { | ||||
|     return initialWindowSize; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public int windowSize(Http2Stream stream) { | ||||
|     return state(stream).windowSize(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean isWritable(Http2Stream stream) { | ||||
|     return monitor.isWritable(state(stream)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void channelWritabilityChanged() throws Http2Exception { | ||||
|     monitor.channelWritabilityChange(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void updateDependencyTree( | ||||
|       int childStreamId, int parentStreamId, short weight, boolean exclusive) { | ||||
|     // It is assumed there are all validated at a higher level. For example in the Http2FrameReader. | ||||
|     assert weight >= MIN_WEIGHT && weight <= MAX_WEIGHT : "Invalid weight"; | ||||
|     assert childStreamId != parentStreamId : "A stream cannot depend on itself"; | ||||
|     assert childStreamId > 0 && parentStreamId >= 0 | ||||
|         : "childStreamId must be > 0. parentStreamId must be >= 0."; | ||||
| 
 | ||||
|     streamByteDistributor.updateDependencyTree(childStreamId, parentStreamId, weight, exclusive); | ||||
|   } | ||||
| 
 | ||||
|   private boolean isChannelWritable() { | ||||
|     return ctx != null && isChannelWritable0(); | ||||
|   } | ||||
| 
 | ||||
|   private boolean isChannelWritable0() { | ||||
|     return ctx.channel().isWritable(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void listener(Listener listener) { | ||||
|     monitor = | ||||
|         listener == null ? new WritabilityMonitor() : new ListenerWritabilityMonitor(listener); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void incrementWindowSize(Http2Stream stream, int delta) throws Http2Exception { | ||||
|     assert ctx == null || ctx.executor().inEventLoop(); | ||||
|     monitor.incrementWindowSize(state(stream), delta); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void addFlowControlled(Http2Stream stream, FlowControlled frame) { | ||||
|     // The context can be null assuming the frame will be queued and send later when the context is | ||||
|     // set. | ||||
|     assert ctx == null || ctx.executor().inEventLoop(); | ||||
|     checkNotNull(frame, "frame"); | ||||
|     try { | ||||
|       monitor.enqueueFrame(state(stream), frame); | ||||
|     } catch (Throwable t) { | ||||
|       frame.error(ctx, t); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean hasFlowControlled(Http2Stream stream) { | ||||
|     return state(stream).hasFrame(); | ||||
|   } | ||||
| 
 | ||||
|   private FlowState state(Http2Stream stream) { | ||||
|     return (FlowState) stream.getProperty(stateKey); | ||||
|   } | ||||
| 
 | ||||
|   /** Returns the flow control window for the entire connection. */ | ||||
|   private int connectionWindowSize() { | ||||
|     return connectionState.windowSize(); | ||||
|   } | ||||
| 
 | ||||
|   private int minUsableChannelBytes() { | ||||
|     // The current allocation algorithm values "fairness" and doesn't give any consideration to | ||||
|     // "goodput". It | ||||
|     // is possible that 1 byte will be allocated to many streams. In an effort to try to make | ||||
|     // "goodput" | ||||
|     // reasonable with the current allocation algorithm we have this "cheap" check up front to | ||||
|     // ensure there is | ||||
|     // an "adequate" amount of connection window before allocation is attempted. This is not | ||||
|     // foolproof as if the | ||||
|     // number of streams is >= this minimal number then we may still have the issue, but the idea is | ||||
|     // to narrow the | ||||
|     // circumstances in which this can happen without rewriting the allocation algorithm. | ||||
|     return max(ctx.channel().config().getWriteBufferLowWaterMark(), MIN_WRITABLE_CHUNK); | ||||
|   } | ||||
| 
 | ||||
|   private int maxUsableChannelBytes() { | ||||
|     // If the channel isWritable, allow at least minUsableChannelBytes. | ||||
|     int channelWritableBytes = (int) min(Integer.MAX_VALUE, ctx.channel().bytesBeforeUnwritable()); | ||||
|     int usableBytes = | ||||
|         channelWritableBytes > 0 ? max(channelWritableBytes, minUsableChannelBytes()) : 0; | ||||
| 
 | ||||
|     // Clip the usable bytes by the connection window. | ||||
|     return min(connectionState.windowSize(), usableBytes); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * The amount of bytes that can be supported by underlying {@link io.netty.channel.Channel} | ||||
|    * without queuing "too-much". | ||||
|    */ | ||||
|   private int writableBytes() { | ||||
|     return min(connectionWindowSize(), maxUsableChannelBytes()); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void writePendingBytes() throws Http2Exception { | ||||
|     monitor.writePendingBytes(); | ||||
|   } | ||||
| 
 | ||||
|   /** The remote flow control state for a single stream. */ | ||||
|   private final class FlowState implements StreamByteDistributor.StreamState { | ||||
|     private final Http2Stream stream; | ||||
|     private final Deque<FlowControlled> pendingWriteQueue; | ||||
|     private int window; | ||||
|     private long pendingBytes; | ||||
|     private boolean markedWritable; | ||||
| 
 | ||||
|     /** Set to true while a frame is being written, false otherwise. */ | ||||
|     private boolean writing; | ||||
|     /** Set to true if cancel() was called. */ | ||||
|     private boolean cancelled; | ||||
| 
 | ||||
|     FlowState(Http2Stream stream) { | ||||
|       this.stream = stream; | ||||
|       pendingWriteQueue = new ArrayDeque<FlowControlled>(2); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Determine if the stream associated with this object is writable. | ||||
|      * | ||||
|      * @return {@code true} if the stream associated with this object is writable. | ||||
|      */ | ||||
|     boolean isWritable() { | ||||
|       return windowSize() > pendingBytes() && !cancelled; | ||||
|     } | ||||
| 
 | ||||
|     /** The stream this state is associated with. */ | ||||
|     @Override | ||||
|     public Http2Stream stream() { | ||||
|       return stream; | ||||
|     } | ||||
| 
 | ||||
|     /** Returns the parameter from the last call to {@link #markedWritability(boolean)}. */ | ||||
|     boolean markedWritability() { | ||||
|       return markedWritable; | ||||
|     } | ||||
| 
 | ||||
|     /** Save the state of writability. */ | ||||
|     void markedWritability(boolean isWritable) { | ||||
|       this.markedWritable = isWritable; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int windowSize() { | ||||
|       return window; | ||||
|     } | ||||
| 
 | ||||
|     /** Reset the window size for this stream. */ | ||||
|     void windowSize(int initialWindowSize) { | ||||
|       window = initialWindowSize; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Write the allocated bytes for this stream. | ||||
|      * | ||||
|      * @return the number of bytes written for a stream or {@code -1} if no write occurred. | ||||
|      */ | ||||
|     int writeAllocatedBytes(int allocated) { | ||||
|       final int initialAllocated = allocated; | ||||
|       int writtenBytes; | ||||
|       // In case an exception is thrown we want to remember it and pass it to cancel(Throwable). | ||||
|       Throwable cause = null; | ||||
|       FlowControlled frame; | ||||
|       try { | ||||
|         assert !writing; | ||||
|         writing = true; | ||||
| 
 | ||||
|         // Write the remainder of frames that we are allowed to | ||||
|         boolean writeOccurred = false; | ||||
|         while (!cancelled && (frame = peek()) != null) { | ||||
|           int maxBytes = min(allocated, writableWindow()); | ||||
|           if (maxBytes <= 0 && frame.size() > 0) { | ||||
|             // The frame still has data, but the amount of allocated bytes has been exhausted. | ||||
|             // Don't write needless empty frames. | ||||
|             break; | ||||
|           } | ||||
|           writeOccurred = true; | ||||
|           int initialFrameSize = frame.size(); | ||||
|           try { | ||||
|             frame.write(ctx, max(0, maxBytes)); | ||||
|             if (frame.size() == 0) { | ||||
|               // This frame has been fully written, remove this frame and notify it. | ||||
|               // Since we remove this frame first, we're guaranteed that its error | ||||
|               // method will not be called when we call cancel. | ||||
|               pendingWriteQueue.remove(); | ||||
|               frame.writeComplete(); | ||||
|             } | ||||
|           } finally { | ||||
|             // Decrement allocated by how much was actually written. | ||||
|             allocated -= initialFrameSize - frame.size(); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         if (!writeOccurred) { | ||||
|           // Either there was no frame, or the amount of allocated bytes has been exhausted. | ||||
|           return -1; | ||||
|         } | ||||
| 
 | ||||
|       } catch (Throwable t) { | ||||
|         // Mark the state as cancelled, we'll clear the pending queue via cancel() below. | ||||
|         cancelled = true; | ||||
|         cause = t; | ||||
|       } finally { | ||||
|         writing = false; | ||||
|         // Make sure we always decrement the flow control windows | ||||
|         // by the bytes written. | ||||
|         writtenBytes = initialAllocated - allocated; | ||||
| 
 | ||||
|         decrementPendingBytes(writtenBytes, false); | ||||
|         decrementFlowControlWindow(writtenBytes); | ||||
| 
 | ||||
|         // If a cancellation occurred while writing, call cancel again to | ||||
|         // clear and error all of the pending writes. | ||||
|         if (cancelled) { | ||||
|           cancel(INTERNAL_ERROR, cause); | ||||
|         } | ||||
|       } | ||||
|       return writtenBytes; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Increments the flow control window for this stream by the given delta and returns the new | ||||
|      * value. | ||||
|      */ | ||||
|     int incrementStreamWindow(int delta) throws Http2Exception { | ||||
|       if (delta > 0 && Integer.MAX_VALUE - delta < window) { | ||||
|         throw streamError( | ||||
|             stream.id(), FLOW_CONTROL_ERROR, "Window size overflow for stream: %d", stream.id()); | ||||
|       } | ||||
|       window += delta; | ||||
| 
 | ||||
|       streamByteDistributor.updateStreamableBytes(this); | ||||
|       return window; | ||||
|     } | ||||
| 
 | ||||
|     /** Returns the maximum writable window (minimum of the stream and connection windows). */ | ||||
|     private int writableWindow() { | ||||
|       return min(window, connectionWindowSize()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public long pendingBytes() { | ||||
|       return pendingBytes; | ||||
|     } | ||||
| 
 | ||||
|     /** Adds the {@code frame} to the pending queue and increments the pending byte count. */ | ||||
|     void enqueueFrame(FlowControlled frame) { | ||||
|       FlowControlled last = pendingWriteQueue.peekLast(); | ||||
|       if (last == null) { | ||||
|         enqueueFrameWithoutMerge(frame); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       int lastSize = last.size(); | ||||
|       if (last.merge(ctx, frame)) { | ||||
|         incrementPendingBytes(last.size() - lastSize, true); | ||||
|         return; | ||||
|       } | ||||
|       enqueueFrameWithoutMerge(frame); | ||||
|     } | ||||
| 
 | ||||
|     private void enqueueFrameWithoutMerge(FlowControlled frame) { | ||||
|       pendingWriteQueue.offer(frame); | ||||
|       // This must be called after adding to the queue in order so that hasFrame() is | ||||
|       // updated before updating the stream state. | ||||
|       incrementPendingBytes(frame.size(), true); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean hasFrame() { | ||||
|       return !pendingWriteQueue.isEmpty(); | ||||
|     } | ||||
| 
 | ||||
|     /** Returns the head of the pending queue, or {@code null} if empty. */ | ||||
|     private FlowControlled peek() { | ||||
|       return pendingWriteQueue.peek(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Clears the pending queue and writes errors for each remaining frame. | ||||
|      * | ||||
|      * @param error the {@link Http2Error} to use. | ||||
|      * @param cause the {@link Throwable} that caused this method to be invoked. | ||||
|      */ | ||||
|     void cancel(Http2Error error, Throwable cause) { | ||||
|       cancelled = true; | ||||
|       // Ensure that the queue can't be modified while we are writing. | ||||
|       if (writing) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       FlowControlled frame = pendingWriteQueue.poll(); | ||||
|       if (frame != null) { | ||||
|         // Only create exception once and reuse to reduce overhead of filling in the stacktrace. | ||||
|         final Http2Exception exception = | ||||
|             streamError(stream.id(), error, cause, "Stream closed before write could take place"); | ||||
|         do { | ||||
|           writeError(frame, exception); | ||||
|           frame = pendingWriteQueue.poll(); | ||||
|         } while (frame != null); | ||||
|       } | ||||
| 
 | ||||
|       streamByteDistributor.updateStreamableBytes(this); | ||||
| 
 | ||||
|       monitor.stateCancelled(this); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Increments the number of pending bytes for this node and optionally updates the {@link | ||||
|      * StreamByteDistributor}. | ||||
|      */ | ||||
|     private void incrementPendingBytes(int numBytes, boolean updateStreamableBytes) { | ||||
|       pendingBytes += numBytes; | ||||
|       monitor.incrementPendingBytes(numBytes); | ||||
|       if (updateStreamableBytes) { | ||||
|         streamByteDistributor.updateStreamableBytes(this); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * If this frame is in the pending queue, decrements the number of pending bytes for the stream. | ||||
|      */ | ||||
|     private void decrementPendingBytes(int bytes, boolean updateStreamableBytes) { | ||||
|       incrementPendingBytes(-bytes, updateStreamableBytes); | ||||
|     } | ||||
| 
 | ||||
|     /** Decrement the per stream and connection flow control window by {@code bytes}. */ | ||||
|     private void decrementFlowControlWindow(int bytes) { | ||||
|       try { | ||||
|         int negativeBytes = -bytes; | ||||
|         connectionState.incrementStreamWindow(negativeBytes); | ||||
|         incrementStreamWindow(negativeBytes); | ||||
|       } catch (Http2Exception e) { | ||||
|         // Should never get here since we're decrementing. | ||||
|         throw new IllegalStateException( | ||||
|             "Invalid window state when writing frame: " + e.getMessage(), e); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Discards this {@link FlowControlled}, writing an error. If this frame is in the pending | ||||
|      * queue, the unwritten bytes are removed from this branch of the priority tree. | ||||
|      */ | ||||
|     private void writeError(FlowControlled frame, Http2Exception cause) { | ||||
|       assert ctx != null; | ||||
|       decrementPendingBytes(frame.size(), true); | ||||
|       frame.error(ctx, cause); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** Abstract class which provides common functionality for writability monitor implementations. */ | ||||
|   private class WritabilityMonitor implements StreamByteDistributor.Writer { | ||||
|     private boolean inWritePendingBytes; | ||||
|     private long totalPendingBytes; | ||||
| 
 | ||||
|     @Override | ||||
|     public final void write(Http2Stream stream, int numBytes) { | ||||
|       state(stream).writeAllocatedBytes(numBytes); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called when the writability of the underlying channel changes. | ||||
|      * | ||||
|      * @throws Http2Exception If a write occurs and an exception happens in the write operation. | ||||
|      */ | ||||
|     void channelWritabilityChange() throws Http2Exception {} | ||||
| 
 | ||||
|     /** | ||||
|      * Called when the state is cancelled. | ||||
|      * | ||||
|      * @param state the state that was cancelled. | ||||
|      */ | ||||
|     void stateCancelled(FlowState state) {} | ||||
| 
 | ||||
|     /** | ||||
|      * Set the initial window size for {@code state}. | ||||
|      * | ||||
|      * @param state the state to change the initial window size for. | ||||
|      * @param initialWindowSize the size of the window in bytes. | ||||
|      */ | ||||
|     void windowSize(FlowState state, int initialWindowSize) { | ||||
|       state.windowSize(initialWindowSize); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Increment the window size for a particular stream. | ||||
|      * | ||||
|      * @param state the state associated with the stream whose window is being incremented. | ||||
|      * @param delta The amount to increment by. | ||||
|      * @throws Http2Exception If this operation overflows the window for {@code state}. | ||||
|      */ | ||||
|     void incrementWindowSize(FlowState state, int delta) throws Http2Exception { | ||||
|       state.incrementStreamWindow(delta); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Add a frame to be sent via flow control. | ||||
|      * | ||||
|      * @param state The state associated with the stream which the {@code frame} is associated with. | ||||
|      * @param frame the frame to enqueue. | ||||
|      * @throws Http2Exception If a writability error occurs. | ||||
|      */ | ||||
|     void enqueueFrame(FlowState state, FlowControlled frame) throws Http2Exception { | ||||
|       state.enqueueFrame(frame); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Increment the total amount of pending bytes for all streams. When any stream's pending bytes | ||||
|      * changes method should be called. | ||||
|      * | ||||
|      * @param delta The amount to increment by. | ||||
|      */ | ||||
|     final void incrementPendingBytes(int delta) { | ||||
|       totalPendingBytes += delta; | ||||
| 
 | ||||
|       // Notification of writibilty change should be delayed until the end of the top level event. | ||||
|       // This is to ensure the flow controller is more consistent state before calling external | ||||
|       // listener methods. | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Determine if the stream associated with {@code state} is writable. | ||||
|      * | ||||
|      * @param state The state which is associated with the stream to test writability for. | ||||
|      * @return {@code true} if {@link FlowState#stream()} is writable. {@code false} otherwise. | ||||
|      */ | ||||
|     final boolean isWritable(FlowState state) { | ||||
|       return isWritableConnection() && state.isWritable(); | ||||
|     } | ||||
| 
 | ||||
|     final void writePendingBytes() throws Http2Exception { | ||||
|       // Reentry is not permitted during the byte distribution process. It may lead to undesirable | ||||
|       // distribution of | ||||
|       // bytes and even infinite loops. We protect against reentry and make sure each call has an | ||||
|       // opportunity to | ||||
|       // cause a distribution to occur. This may be useful for example if the channel's writability | ||||
|       // changes from | ||||
|       // Writable -> Not Writable (because we are writing) -> Writable (because the user flushed to | ||||
|       // make more room | ||||
|       // in the channel outbound buffer). | ||||
|       if (inWritePendingBytes) { | ||||
|         return; | ||||
|       } | ||||
|       inWritePendingBytes = true; | ||||
|       try { | ||||
|         int bytesToWrite = writableBytes(); | ||||
|         // Make sure we always write at least once, regardless if we have bytesToWrite or not. | ||||
|         // This ensures that zero-length frames will always be written. | ||||
|         for (; ; ) { | ||||
|           if (!streamByteDistributor.distribute(bytesToWrite, this) | ||||
|               || (bytesToWrite = writableBytes()) <= 0 | ||||
|               || !isChannelWritable0()) { | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|       } finally { | ||||
|         inWritePendingBytes = false; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     void initialWindowSize(int newWindowSize) throws Http2Exception { | ||||
|       checkPositiveOrZero(newWindowSize, "newWindowSize"); | ||||
| 
 | ||||
|       final int delta = newWindowSize - initialWindowSize; | ||||
|       initialWindowSize = newWindowSize; | ||||
|       connection.forEachActiveStream( | ||||
|           new Http2StreamVisitor() { | ||||
|             @Override | ||||
|             public boolean visit(Http2Stream stream) throws Http2Exception { | ||||
|               state(stream).incrementStreamWindow(delta); | ||||
|               return true; | ||||
|             } | ||||
|           }); | ||||
| 
 | ||||
|       if (delta > 0 && isChannelWritable()) { | ||||
|         // The window size increased, send any pending frames for all streams. | ||||
|         writePendingBytes(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     final boolean isWritableConnection() { | ||||
|       return connectionState.windowSize() - totalPendingBytes > 0 && isChannelWritable(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Writability of a {@code stream} is calculated using the following: | ||||
|    * | ||||
|    * <pre> | ||||
|    * Connection Window - Total Queued Bytes > 0 && | ||||
|    * Stream Window - Bytes Queued for Stream > 0 && | ||||
|    * isChannelWritable() | ||||
|    * </pre> | ||||
|    */ | ||||
|   private final class ListenerWritabilityMonitor extends WritabilityMonitor | ||||
|       implements Http2StreamVisitor { | ||||
|     private final Listener listener; | ||||
| 
 | ||||
|     ListenerWritabilityMonitor(Listener listener) { | ||||
|       this.listener = listener; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean visit(Http2Stream stream) throws Http2Exception { | ||||
|       FlowState state = state(stream); | ||||
|       if (isWritable(state) != state.markedWritability()) { | ||||
|         notifyWritabilityChanged(state); | ||||
|       } | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void windowSize(FlowState state, int initialWindowSize) { | ||||
|       super.windowSize(state, initialWindowSize); | ||||
|       try { | ||||
|         checkStateWritability(state); | ||||
|       } catch (Http2Exception e) { | ||||
|         throw new RuntimeException("Caught unexpected exception from window", e); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void incrementWindowSize(FlowState state, int delta) throws Http2Exception { | ||||
|       super.incrementWindowSize(state, delta); | ||||
|       checkStateWritability(state); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void initialWindowSize(int newWindowSize) throws Http2Exception { | ||||
|       super.initialWindowSize(newWindowSize); | ||||
|       if (isWritableConnection()) { | ||||
|         // If the write operation does not occur we still need to check all streams because they | ||||
|         // may have transitioned from writable to not writable. | ||||
|         checkAllWritabilityChanged(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void enqueueFrame(FlowState state, FlowControlled frame) throws Http2Exception { | ||||
|       super.enqueueFrame(state, frame); | ||||
|       checkConnectionThenStreamWritabilityChanged(state); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void stateCancelled(FlowState state) { | ||||
|       try { | ||||
|         checkConnectionThenStreamWritabilityChanged(state); | ||||
|       } catch (Http2Exception e) { | ||||
|         throw new RuntimeException( | ||||
|             "Caught unexpected exception from checkAllWritabilityChanged", e); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     void channelWritabilityChange() throws Http2Exception { | ||||
|       if (connectionState.markedWritability() != isChannelWritable()) { | ||||
|         checkAllWritabilityChanged(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     private void checkStateWritability(FlowState state) throws Http2Exception { | ||||
|       if (isWritable(state) != state.markedWritability()) { | ||||
|         if (state == connectionState) { | ||||
|           checkAllWritabilityChanged(); | ||||
|         } else { | ||||
|           notifyWritabilityChanged(state); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     private void notifyWritabilityChanged(FlowState state) { | ||||
|       state.markedWritability(!state.markedWritability()); | ||||
|       try { | ||||
|         listener.writabilityChanged(state.stream); | ||||
|       } catch (Throwable cause) { | ||||
|         logger.error("Caught Throwable from listener.writabilityChanged", cause); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     private void checkConnectionThenStreamWritabilityChanged(FlowState state) | ||||
|         throws Http2Exception { | ||||
|       // It is possible that the connection window and/or the individual stream writability could | ||||
|       // change. | ||||
|       if (isWritableConnection() != connectionState.markedWritability()) { | ||||
|         checkAllWritabilityChanged(); | ||||
|       } else if (isWritable(state) != state.markedWritability()) { | ||||
|         notifyWritabilityChanged(state); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     private void checkAllWritabilityChanged() throws Http2Exception { | ||||
|       // Make sure we mark that we have notified as a result of this change. | ||||
|       connectionState.markedWritability(isWritableConnection()); | ||||
|       connection.forEachActiveStream(this); | ||||
|     } | ||||
|   } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,176 @@ | |||
| /* | ||||
|  * Copyright 2014 The Netty Project | ||||
|  * | ||||
|  * The Netty Project licenses this file to you under the Apache License, | ||||
|  * version 2.0 (the "License"); you may not use this file except in compliance | ||||
|  * with the License. You may obtain a copy of the License at: | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
| package io.netty.handler.codec.http2; | ||||
| 
 | ||||
| import static io.netty.handler.codec.http2.Http2FrameLogger.Direction.OUTBOUND; | ||||
| import static io.netty.util.internal.ObjectUtil.checkNotNull; | ||||
| 
 | ||||
| import io.netty.buffer.ByteBuf; | ||||
| import io.netty.channel.ChannelFuture; | ||||
| import io.netty.channel.ChannelHandlerContext; | ||||
| import io.netty.channel.ChannelPromise; | ||||
| import io.netty.util.internal.UnstableApi; | ||||
| 
 | ||||
| /** | ||||
|  * Decorator around a {@link Http2FrameWriter} that logs all outbound frames before calling the | ||||
|  * writer. | ||||
|  */ | ||||
| @UnstableApi | ||||
| public class Http2OutboundFrameLogger implements Http2FrameWriter { | ||||
|   private final Http2FrameWriter writer; | ||||
|   private final Http2FrameLogger logger; | ||||
| 
 | ||||
|   public Http2OutboundFrameLogger(Http2FrameWriter writer, Http2FrameLogger logger) { | ||||
|     this.writer = checkNotNull(writer, "writer"); | ||||
|     this.logger = checkNotNull(logger, "logger"); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeData( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       ByteBuf data, | ||||
|       int padding, | ||||
|       boolean endStream, | ||||
|       ChannelPromise promise) { | ||||
|     logger.logData(OUTBOUND, ctx, streamId, data, padding, endStream); | ||||
|     return writer.writeData(ctx, streamId, data, padding, endStream, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeHeaders( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       Http2Headers headers, | ||||
|       int padding, | ||||
|       boolean endStream, | ||||
|       ChannelPromise promise) { | ||||
|     logger.logHeaders(OUTBOUND, ctx, streamId, headers, padding, endStream); | ||||
|     return writer.writeHeaders(ctx, streamId, headers, padding, endStream, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeHeaders( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       Http2Headers headers, | ||||
|       int streamDependency, | ||||
|       short weight, | ||||
|       boolean exclusive, | ||||
|       int padding, | ||||
|       boolean endStream, | ||||
|       ChannelPromise promise) { | ||||
|     logger.logHeaders( | ||||
|         OUTBOUND, ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream); | ||||
|     return writer.writeHeaders( | ||||
|         ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writePriority( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       int streamDependency, | ||||
|       short weight, | ||||
|       boolean exclusive, | ||||
|       ChannelPromise promise) { | ||||
|     logger.logPriority(OUTBOUND, ctx, streamId, streamDependency, weight, exclusive); | ||||
|     return writer.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeRstStream( | ||||
|       ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) { | ||||
|     logger.logRstStream(OUTBOUND, ctx, streamId, errorCode); | ||||
|     return writer.writeRstStream(ctx, streamId, errorCode, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeSettings( | ||||
|       ChannelHandlerContext ctx, Http2Settings settings, ChannelPromise promise) { | ||||
|     logger.logSettings(OUTBOUND, ctx, settings); | ||||
|     return writer.writeSettings(ctx, settings, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) { | ||||
|     logger.logSettingsAck(OUTBOUND, ctx); | ||||
|     return writer.writeSettingsAck(ctx, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writePing( | ||||
|       ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise) { | ||||
|     if (ack) { | ||||
|       logger.logPingAck(OUTBOUND, ctx, data); | ||||
|     } else { | ||||
|       logger.logPing(OUTBOUND, ctx, data); | ||||
|     } | ||||
|     return writer.writePing(ctx, ack, data, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writePushPromise( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int streamId, | ||||
|       int promisedStreamId, | ||||
|       Http2Headers headers, | ||||
|       int padding, | ||||
|       ChannelPromise promise) { | ||||
|     logger.logPushPromise(OUTBOUND, ctx, streamId, promisedStreamId, headers, padding); | ||||
|     return writer.writePushPromise(ctx, streamId, promisedStreamId, headers, padding, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeGoAway( | ||||
|       ChannelHandlerContext ctx, | ||||
|       int lastStreamId, | ||||
|       long errorCode, | ||||
|       ByteBuf debugData, | ||||
|       ChannelPromise promise) { | ||||
|     logger.logGoAway(OUTBOUND, ctx, lastStreamId, errorCode, debugData); | ||||
|     return writer.writeGoAway(ctx, lastStreamId, errorCode, debugData, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeWindowUpdate( | ||||
|       ChannelHandlerContext ctx, int streamId, int windowSizeIncrement, ChannelPromise promise) { | ||||
|     logger.logWindowsUpdate(OUTBOUND, ctx, streamId, windowSizeIncrement); | ||||
|     return writer.writeWindowUpdate(ctx, streamId, windowSizeIncrement, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public ChannelFuture writeFrame( | ||||
|       ChannelHandlerContext ctx, | ||||
|       byte frameType, | ||||
|       int streamId, | ||||
|       Http2Flags flags, | ||||
|       ByteBuf payload, | ||||
|       ChannelPromise promise) { | ||||
|     logger.logUnknownFrame(OUTBOUND, ctx, frameType, streamId, flags, payload); | ||||
|     return writer.writeFrame(ctx, frameType, streamId, flags, payload, promise); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void close() { | ||||
|     writer.close(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Configuration configuration() { | ||||
|     return writer.configuration(); | ||||
|   } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue