diff --git a/binder/src/main/java/io/grpc/binder/BinderServerBuilder.java b/binder/src/main/java/io/grpc/binder/BinderServerBuilder.java index af5f9eed75..52122c5e8b 100644 --- a/binder/src/main/java/io/grpc/binder/BinderServerBuilder.java +++ b/binder/src/main/java/io/grpc/binder/BinderServerBuilder.java @@ -166,7 +166,7 @@ public final class BinderServerBuilder ObjectPool extends Executor> executorPool = serverImplBuilder.getExecutorPool(); Executor executor = executorPool.getObject(); BinderTransportSecurity.installAuthInterceptor(this, executor); - internalBuilder.setShutdownListener(() -> executorPool.returnObject(executor)); + internalBuilder.setTerminationListener(() -> executorPool.returnObject(executor)); return super.build(); } } diff --git a/binder/src/main/java/io/grpc/binder/internal/ActiveTransportTracker.java b/binder/src/main/java/io/grpc/binder/internal/ActiveTransportTracker.java new file mode 100644 index 0000000000..ad41018648 --- /dev/null +++ b/binder/src/main/java/io/grpc/binder/internal/ActiveTransportTracker.java @@ -0,0 +1,110 @@ +package io.grpc.binder.internal; + +import static com.google.common.base.Preconditions.checkState; + +import io.grpc.Attributes; +import io.grpc.Metadata; +import io.grpc.internal.ServerListener; +import io.grpc.internal.ServerStream; +import io.grpc.internal.ServerTransport; +import io.grpc.internal.ServerTransportListener; +import javax.annotation.concurrent.GuardedBy; + +/** + * Tracks which {@link BinderTransport.BinderServerTransport} are currently active and allows + * invoking a {@link Runnable} only once all transports are terminated. + */ +final class ActiveTransportTracker implements ServerListener { + private final ServerListener delegate; + private final Runnable terminationListener; + + @GuardedBy("this") + private boolean shutdown = false; + + @GuardedBy("this") + private int activeTransportCount = 0; + + /** + * @param delegate the original server listener that this object decorates. Usually passed to + * {@link BinderServer#start(ServerListener)}. + * @param terminationListener invoked only once the server has started shutdown ({@link + * #serverShutdown()} AND the last active transport is terminated. + */ + ActiveTransportTracker(ServerListener delegate, Runnable terminationListener) { + this.delegate = delegate; + this.terminationListener = terminationListener; + } + + @Override + public ServerTransportListener transportCreated(ServerTransport transport) { + synchronized (this) { + checkState(!shutdown, "Illegal transportCreated() after serverShutdown()"); + activeTransportCount++; + } + ServerTransportListener originalListener = delegate.transportCreated(transport); + return new TrackedTransportListener(originalListener); + } + + private void untrack() { + Runnable maybeTerminationListener; + synchronized (this) { + activeTransportCount--; + maybeTerminationListener = getListenerIfTerminated(); + } + // Prefer running the listener outside of the synchronization lock to release it sooner, since + // we don't know how the callback is implemented nor how long it will take. This should + // minimize the possibility of deadlocks. + if (maybeTerminationListener != null) { + maybeTerminationListener.run(); + } + } + + @Override + public void serverShutdown() { + delegate.serverShutdown(); + Runnable maybeTerminationListener; + synchronized (this) { + shutdown = true; + maybeTerminationListener = getListenerIfTerminated(); + } + // We may be able to shutdown immediately if there are no active transports. + // + // Executed outside of the lock. See "untrack()" above. + if (maybeTerminationListener != null) { + maybeTerminationListener.run(); + } + } + + @GuardedBy("this") + private Runnable getListenerIfTerminated() { + return (shutdown && activeTransportCount == 0) ? terminationListener : null; + } + + /** + * Wraps a {@link ServerTransportListener}, unregistering it from the parent tracker once the + * transport terminates. + */ + private final class TrackedTransportListener implements ServerTransportListener { + private final ServerTransportListener delegate; + + TrackedTransportListener(ServerTransportListener delegate) { + this.delegate = delegate; + } + + @Override + public void streamCreated(ServerStream stream, String method, Metadata headers) { + delegate.streamCreated(stream, method, headers); + } + + @Override + public Attributes transportReady(Attributes attributes) { + return delegate.transportReady(attributes); + } + + @Override + public void transportTerminated() { + delegate.transportTerminated(); + untrack(); + } + } +} diff --git a/binder/src/main/java/io/grpc/binder/internal/BinderServer.java b/binder/src/main/java/io/grpc/binder/internal/BinderServer.java index 260410b75d..d3580dbd13 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderServer.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderServer.java @@ -68,7 +68,7 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder. private final LeakSafeOneWayBinder hostServiceBinder; private final BinderTransportSecurity.ServerPolicyChecker serverPolicyChecker; private final InboundParcelablePolicy inboundParcelablePolicy; - private final BinderTransportSecurity.ShutdownListener transportSecurityShutdownListener; + private final Runnable terminationListener; @GuardedBy("this") private ServerListener listener; @@ -86,7 +86,7 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder. ImmutableList.copyOf(checkNotNull(builder.streamTracerFactories, "streamTracerFactories")); this.serverPolicyChecker = BinderInternal.createPolicyChecker(builder.serverSecurityPolicy); this.inboundParcelablePolicy = builder.inboundParcelablePolicy; - this.transportSecurityShutdownListener = builder.shutdownListener; + this.terminationListener = builder.terminationListener; hostServiceBinder = new LeakSafeOneWayBinder(this); } @@ -97,7 +97,7 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder. @Override public synchronized void start(ServerListener serverListener) throws IOException { - this.listener = serverListener; + listener = new ActiveTransportTracker(serverListener, terminationListener); executorService = executorServicePool.getObject(); } @@ -130,7 +130,6 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder. hostServiceBinder.setHandler(GoAwayHandler.INSTANCE); listener.serverShutdown(); executorService = executorServicePool.returnObject(executorService); - transportSecurityShutdownListener.onServerShutdown(); } } @@ -208,7 +207,7 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder. SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE); ServerSecurityPolicy serverSecurityPolicy = SecurityPolicies.serverInternalOnly(); InboundParcelablePolicy inboundParcelablePolicy = InboundParcelablePolicy.DEFAULT; - BinderTransportSecurity.ShutdownListener shutdownListener = () -> {}; + Runnable terminationListener = () -> {}; public BinderServer build() { return new BinderServer(this); @@ -269,12 +268,13 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder. } /** - * Installs a callback that will be invoked when this server is {@link #shutdown()} + * Installs a callback that will be invoked when this server is {@link #shutdown()} and all of + * its transports are terminated. * *
Optional.
*/
- public Builder setShutdownListener(BinderTransportSecurity.ShutdownListener shutdownListener) {
- this.shutdownListener = checkNotNull(shutdownListener, "shutdownListener");
+ public Builder setTerminationListener(Runnable terminationListener) {
+ this.terminationListener = checkNotNull(terminationListener, "terminationListener");
return this;
}
}
diff --git a/binder/src/main/java/io/grpc/binder/internal/BinderTransportSecurity.java b/binder/src/main/java/io/grpc/binder/internal/BinderTransportSecurity.java
index 56464d58a4..72a02c92ff 100644
--- a/binder/src/main/java/io/grpc/binder/internal/BinderTransportSecurity.java
+++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransportSecurity.java
@@ -238,12 +238,4 @@ public final class BinderTransportSecurity {
*/
ListenableFuture