diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index eac167e6fd..ba06be52a4 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -39,6 +39,7 @@ import com.google.common.base.Splitter; import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.grpc.Metadata; @@ -53,6 +54,7 @@ import java.util.Map.Entry; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -62,6 +64,12 @@ import javax.annotation.Nullable; */ public final class GrpcUtil { + // Certain production AppEngine runtimes have constraints on threading and socket handling + // that need to be accommodated. + public static final boolean IS_RESTRICTED_APPENGINE = + "Production".equals(System.getProperty("com.google.appengine.runtime.environment")) + && "1.7".equals(System.getProperty("java.specification.version")); + /** * {@link io.grpc.Metadata.Key} for the timeout header. */ @@ -374,10 +382,7 @@ public final class GrpcUtil { private static final String name = "grpc-default-executor"; @Override public ExecutorService create() { - return Executors.newCachedThreadPool(new ThreadFactoryBuilder() - .setDaemon(true) - .setNameFormat(name + "-%d") - .build()); + return Executors.newCachedThreadPool(getThreadFactory(name + "-%d", true)); } @Override @@ -402,10 +407,8 @@ public final class GrpcUtil { // ScheduledThreadPoolExecutor. ScheduledExecutorService service = Executors.newScheduledThreadPool( 1, - new ThreadFactoryBuilder() - .setDaemon(true) - .setNameFormat("grpc-timer-%d") - .build()); + getThreadFactory("grpc-timer-%d", true)); + // If there are long timeouts that are cancelled, they will not actually be removed from // the executors queue. This forces immediate removal upon cancellation to avoid a // memory leak. Reflection is used because we cannot use methods added in Java 1.7. If @@ -431,6 +434,27 @@ public final class GrpcUtil { } }; + + /** + * Get a {@link ThreadFactory} suitable for use in the current environment. + * @param nameFormat to apply to threads created by the factory. + * @param daemon {@code true} if the threads the factory creates are daemon threads, {@code false} + * otherwise. + * @return a {@link ThreadFactory}. + */ + public static ThreadFactory getThreadFactory(String nameFormat, boolean daemon) { + ThreadFactory threadFactory = MoreExecutors.platformThreadFactory(); + if (IS_RESTRICTED_APPENGINE) { + return threadFactory; + } else { + return new ThreadFactoryBuilder() + .setThreadFactory(threadFactory) + .setDaemon(daemon) + .setNameFormat(nameFormat) + .build(); + } + } + /** * The factory of default Stopwatches. */ diff --git a/core/src/main/java/io/grpc/internal/SharedResourceHolder.java b/core/src/main/java/io/grpc/internal/SharedResourceHolder.java index 18a2f87ee5..1907354de1 100644 --- a/core/src/main/java/io/grpc/internal/SharedResourceHolder.java +++ b/core/src/main/java/io/grpc/internal/SharedResourceHolder.java @@ -32,7 +32,6 @@ package io.grpc.internal; import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.IdentityHashMap; import java.util.concurrent.Executors; @@ -65,10 +64,8 @@ public final class SharedResourceHolder { new ScheduledExecutorFactory() { @Override public ScheduledExecutorService createScheduledExecutor() { - return Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder() - .setDaemon(true) - .setNameFormat("grpc-shared-destroyer-%d") - .build()); + return Executors.newSingleThreadScheduledExecutor( + GrpcUtil.getThreadFactory("grpc-shared-destroyer-%d", true)); } }); diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java index 5f1f9dbfd3..afd992e354 100644 --- a/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java +++ b/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java @@ -45,6 +45,7 @@ import io.grpc.internal.GrpcUtil; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyServerBuilder; import io.grpc.okhttp.OkHttpChannelBuilder; +import io.grpc.okhttp.internal.Platform; import io.grpc.stub.StreamObserver; import io.grpc.testing.StreamRecorder; import io.grpc.testing.TestUtils; @@ -60,6 +61,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.FileInputStream; import java.io.IOException; import javax.net.ssl.SSLPeerUnverifiedException; @@ -109,7 +111,8 @@ public class Http2OkHttpTest extends AbstractInteropTest { .overrideAuthority(GrpcUtil.authorityFromHostAndPort( TestUtils.TEST_SERVER_HOST, getPort())); try { - builder.sslSocketFactory(TestUtils.newSslSocketFactoryForCa(TestUtils.loadCert("ca.pem"))); + builder.sslSocketFactory(TestUtils.newSslSocketFactoryForCa(Platform.get().getProvider(), + new FileInputStream(TestUtils.loadCert("ca.pem")))); } catch (Exception e) { throw new RuntimeException(e); } @@ -149,7 +152,8 @@ public class Http2OkHttpTest extends AbstractInteropTest { .overrideAuthority(GrpcUtil.authorityFromHostAndPort( "I.am.a.bad.hostname", getPort())); ManagedChannel channel = builder.sslSocketFactory( - TestUtils.newSslSocketFactoryForCa(TestUtils.loadCert("ca.pem"))).build(); + TestUtils.newSslSocketFactoryForCa(Platform.get().getProvider(), + new FileInputStream(TestUtils.loadCert("ca.pem")))).build(); TestServiceGrpc.TestServiceBlockingStub blockingStub = TestServiceGrpc.newBlockingStub(channel); diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java index 676ed2ea65..80c09b683d 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java @@ -38,7 +38,6 @@ import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.squareup.okhttp.CipherSuite; import com.squareup.okhttp.ConnectionSpec; @@ -54,15 +53,18 @@ import io.grpc.internal.ConnectionClientTransport; import io.grpc.internal.GrpcUtil; import io.grpc.internal.SharedResourceHolder; import io.grpc.internal.SharedResourceHolder.Resource; +import io.grpc.okhttp.internal.Platform; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.security.GeneralSecurityException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; /** Convenience class for building channels with the OkHttp transport. */ @@ -90,10 +92,7 @@ public class OkHttpChannelBuilder extends new Resource() { @Override public ExecutorService create() { - return Executors.newCachedThreadPool(new ThreadFactoryBuilder() - .setDaemon(true) - .setNameFormat("grpc-okhttp-%d") - .build()); + return Executors.newCachedThreadPool(GrpcUtil.getThreadFactory("grpc-okhttp-%d", true)); } @Override @@ -147,6 +146,11 @@ public class OkHttpChannelBuilder extends /** * Sets the negotiation type for the HTTP/2 connection. * + *

If TLS is enabled a default {@link SSLSocketFactory} is created using the best + * {@link java.security.Provider} available and is NOT based on + * {@link SSLSocketFactory#getDefault}. To more precisely control the TLS configuration call + * {@link #sslSocketFactory} to override the socket factory used. + * *

Default: TLS */ public final OkHttpChannelBuilder negotiationType(NegotiationType type) { @@ -180,7 +184,8 @@ public class OkHttpChannelBuilder extends } /** - * Provides a SSLSocketFactory to replace the default SSLSocketFactory used for TLS. + * Override the default {@link SSLSocketFactory} and enable {@link NegotiationType#TLS} + * negotiation. * *

By default, when TLS is enabled, SSLSocketFactory.getDefault() will be used. * @@ -264,8 +269,16 @@ public class OkHttpChannelBuilder extends SSLSocketFactory createSocketFactory() { switch (negotiationType) { case TLS: - return sslSocketFactory == null - ? (SSLSocketFactory) SSLSocketFactory.getDefault() : sslSocketFactory; + try { + if (sslSocketFactory == null) { + SSLContext sslContext = SSLContext.getInstance("TLS", Platform.get().getProvider()); + sslContext.init(null, null, null); + sslSocketFactory = sslContext.getSocketFactory(); + } + return sslSocketFactory; + } catch (GeneralSecurityException gse) { + throw new RuntimeException("TLS Provider failure", gse); + } case PLAINTEXT: return null; default: diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java index d713712c0e..f98780fc48 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java @@ -33,10 +33,14 @@ package io.grpc.okhttp; import io.grpc.Internal; import io.grpc.ManagedChannelProvider; +import io.grpc.internal.GrpcUtil; -/** Provider for {@link OkHttpChannelBuilder} instances. */ +/** + * Provider for {@link OkHttpChannelBuilder} instances. + */ @Internal public final class OkHttpChannelProvider extends ManagedChannelProvider { + @Override public boolean isAvailable() { return true; @@ -44,7 +48,7 @@ public final class OkHttpChannelProvider extends ManagedChannelProvider { @Override public int priority() { - return isAndroid() ? 8 : 3; + return (GrpcUtil.IS_RESTRICTED_APPENGINE || isAndroid()) ? 8 : 3; } @Override diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 92e8a0d45f..7e147301f3 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -735,7 +735,9 @@ class OkHttpClientTransport implements ConnectionClientTransport { @Override public void run() { String threadName = Thread.currentThread().getName(); - Thread.currentThread().setName("OkHttpClientTransport"); + if (!GrpcUtil.IS_RESTRICTED_APPENGINE) { + Thread.currentThread().setName("OkHttpClientTransport"); + } try { // Read until the underlying socket closes. while (frameReader.nextFrame(this)) { @@ -758,8 +760,10 @@ class OkHttpClientTransport implements ConnectionClientTransport { log.log(Level.INFO, "Exception closing frame reader", ex); } listener.transportTerminated(); - // Restore the original thread name. - Thread.currentThread().setName(threadName); + if (!GrpcUtil.IS_RESTRICTED_APPENGINE) { + // Restore the original thread name. + Thread.currentThread().setName(threadName); + } } } diff --git a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Platform.java b/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Platform.java index 5561c654fa..9d83c907d0 100644 --- a/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Platform.java +++ b/okhttp/third_party/okhttp/java/io/grpc/okhttp/internal/Platform.java @@ -28,10 +28,14 @@ import java.lang.reflect.Proxy; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; + +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import okio.Buffer; @@ -62,6 +66,12 @@ public class Platform { return PLATFORM; } + private final Provider sslProvider; + + public Platform(Provider sslProvider) { + this.sslProvider = sslProvider; + } + /** Prefix used on custom headers. */ public String getPrefix() { return "OkHttp"; @@ -77,6 +87,10 @@ public class Platform { public void untagSocket(Socket socket) throws SocketException { } + public Provider getProvider() { + return sslProvider; + } + /** * Configure TLS extensions on {@code sslSocket} for {@code route}. * @@ -106,46 +120,72 @@ public class Platform { /** Attempt to match the host runtime to a capable Platform implementation. */ private static Platform findPlatform() { - // Attempt to find Android 2.3+ APIs. + // Find the conscrypt security provider if we can + Class rawProviderClass = null; try { + rawProviderClass = Class.forName("org.conscrypt.OpenSSLProvider"); + } catch (ClassNotFoundException cnfe1) { try { - Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl"); - } catch (ClassNotFoundException e) { - // Older platform before being unbundled. - Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl"); + rawProviderClass = Class.forName("com.android.org.conscrypt.OpenSSLProvider"); + } catch (ClassNotFoundException cnfe2) { + try { + rawProviderClass = Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLProvider"); + } catch (ClassNotFoundException cnfe3) { + // Stick with what we have + } } + } + boolean haveConscrypt = false; + Provider sslProvider = null; + try { + sslProvider = SSLContext.getDefault().getProvider(); + } catch (NoSuchAlgorithmException nsae) { + // Ignore + } + if (rawProviderClass != null) { + if (sslProvider != null && sslProvider.getClass().equals(rawProviderClass)) { + haveConscrypt = true; + } else { + try { + Class providerClass = rawProviderClass.asSubclass(Provider.class); + sslProvider = providerClass.newInstance(); + haveConscrypt = true; + } catch (InstantiationException iae) { + // Unable to use conscrypt, fall through to Jetty + logger.log(Level.WARNING, + "Unable to create conscrypt provider " + rawProviderClass.getName(), iae); + } catch (IllegalAccessException iaxe) { + // Unable to use conscrypt, fall through to Jetty + logger.log(Level.WARNING, + "Unable to create conscrypt provider " + rawProviderClass.getName(), iaxe); + } + } + } + + if (haveConscrypt) { + // Attempt to find Android 2.3+ APIs. OptionalMethod setUseSessionTickets = new OptionalMethod(null, "setUseSessionTickets", boolean.class); OptionalMethod setHostname = new OptionalMethod(null, "setHostname", String.class); Method trafficStatsTagSocket = null; Method trafficStatsUntagSocket = null; - OptionalMethod getAlpnSelectedProtocol = null; - OptionalMethod setAlpnProtocols = null; + OptionalMethod getAlpnSelectedProtocol = + new OptionalMethod(byte[].class, "getAlpnSelectedProtocol"); + OptionalMethod setAlpnProtocols = + new OptionalMethod(null, "setAlpnProtocols", byte[].class); // Attempt to find Android 4.0+ APIs. try { Class trafficStats = Class.forName("android.net.TrafficStats"); trafficStatsTagSocket = trafficStats.getMethod("tagSocket", Socket.class); trafficStatsUntagSocket = trafficStats.getMethod("untagSocket", Socket.class); - - // Attempt to find Android 5.0+ APIs. - try { - Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0. - getAlpnSelectedProtocol = - new OptionalMethod(byte[].class, "getAlpnSelectedProtocol"); - setAlpnProtocols = new OptionalMethod(null, "setAlpnProtocols", byte[].class); - } catch (ClassNotFoundException ignored) { - } } catch (ClassNotFoundException ignored) { } catch (NoSuchMethodException ignored) { } - return new Android(setUseSessionTickets, setHostname, trafficStatsTagSocket, - trafficStatsUntagSocket, getAlpnSelectedProtocol, setAlpnProtocols); - } catch (ClassNotFoundException ignored) { - // This isn't an Android runtime. + trafficStatsUntagSocket, getAlpnSelectedProtocol, setAlpnProtocols, sslProvider); } // Find Jetty's ALPN extension for OpenJDK. @@ -159,16 +199,17 @@ public class Platform { Method getMethod = negoClass.getMethod("get", SSLSocket.class); Method removeMethod = negoClass.getMethod("remove", SSLSocket.class); return new JdkWithJettyBootPlatform( - putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass); + putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass, sslProvider); } catch (ClassNotFoundException ignored) { } catch (NoSuchMethodException ignored) { } - return new Platform(); + return new Platform(sslProvider); } /** Android 2.3 or better. */ private static class Android extends Platform { + private final OptionalMethod setUseSessionTickets; private final OptionalMethod setHostname; @@ -182,7 +223,9 @@ public class Platform { public Android(OptionalMethod setUseSessionTickets, OptionalMethod setHostname, Method trafficStatsTagSocket, Method trafficStatsUntagSocket, - OptionalMethod getAlpnSelectedProtocol, OptionalMethod setAlpnProtocols) { + OptionalMethod getAlpnSelectedProtocol, OptionalMethod setAlpnProtocols, + Provider provider) { + super(provider); this.setUseSessionTickets = setUseSessionTickets; this.setHostname = setHostname; this.trafficStatsTagSocket = trafficStatsTagSocket; @@ -213,14 +256,13 @@ public class Platform { } // Enable ALPN. - if (setAlpnProtocols != null && setAlpnProtocols.isSupported(sslSocket)) { + if (setAlpnProtocols.isSupported(sslSocket)) { Object[] parameters = { concatLengthPrefixed(protocols) }; setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters); } } @Override public String getSelectedProtocol(SSLSocket socket) { - if (getAlpnSelectedProtocol == null) return null; if (!getAlpnSelectedProtocol.isSupported(socket)) return null; byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket); @@ -263,7 +305,8 @@ public class Platform { private final Class serverProviderClass; public JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod, - Class clientProviderClass, Class serverProviderClass) { + Class clientProviderClass, Class serverProviderClass, Provider provider) { + super(provider); this.putMethod = putMethod; this.getMethod = getMethod; this.removeMethod = removeMethod; diff --git a/testing/src/main/java/io/grpc/testing/TestUtils.java b/testing/src/main/java/io/grpc/testing/TestUtils.java index d292a6457d..452479d142 100644 --- a/testing/src/main/java/io/grpc/testing/TestUtils.java +++ b/testing/src/main/java/io/grpc/testing/TestUtils.java @@ -51,6 +51,8 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -246,7 +248,16 @@ public class TestUtils { /** * Creates an SSLSocketFactory which contains {@code certChainFile} as its only root certificate. */ - public static SSLSocketFactory newSslSocketFactoryForCa(InputStream certChain) throws Exception { + public static SSLSocketFactory newSslSocketFactoryForCa( + InputStream certChain) throws Exception { + return newSslSocketFactoryForCa(Security.getProviders()[0], certChain); + } + + /** + * Creates an SSLSocketFactory which contains {@code certChainFile} as its only root certificate. + */ + public static SSLSocketFactory newSslSocketFactoryForCa(Provider provider, + InputStream certChain) throws Exception { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null, null); CertificateFactory cf = CertificateFactory.getInstance("X.509"); @@ -259,7 +270,7 @@ public class TestUtils { TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(ks); - SSLContext context = SSLContext.getInstance("TLS"); + SSLContext context = SSLContext.getInstance("TLS", provider); context.init(null, trustManagerFactory.getTrustManagers(), null); return context.getSocketFactory(); }