Skip to content

Commit

Permalink
Add support for native compilation.
Browse files Browse the repository at this point in the history
This commit adds the following:

- Oracle SVM, part of the Graal VM that allows substitutes for classes and methods, deleting them and aliasing them on the GraalVM.
- Substitutions to make Netty work natively with SSL. Those substitutions live under internal/svm and are not visible when used outside GraalVM

The PR also removes Nettys own native-image properties and replaces them with the setting needed for our driver.

Those settings cannot be easily rewritten using the Shade transformer, so it’s more simple to do it by hand.

The changes have been tested sucessfully on Graal 19.2.0.1 with Quarkus and Helidon microservices framework compiled natively.
  • Loading branch information
michael-simons committed Sep 27, 2019
1 parent e257197 commit b03bc89
Show file tree
Hide file tree
Showing 6 changed files with 459 additions and 0 deletions.
12 changes: 12 additions & 0 deletions driver/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
</dependency>
<dependency>
<groupId>com.oracle.substratevm</groupId>
<artifactId>svm</artifactId>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -232,6 +236,14 @@
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
<artifact>io.netty:*</artifact>
<excludes>
<exclude>META-INF/native-image/**</exclude>
</excludes>
</filter>
</filters>
<shadeTestJar>true</shadeTestJar>
<createSourcesJar>true</createSourcesJar>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
/*
* Copyright (c) 2002-2019 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* 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 org.neo4j.driver.internal.svm;

import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.jdk.JDK11OrLater;

import java.security.PrivateKey;
import java.security.Provider;
import java.security.cert.X509Certificate;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingDeque;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;

import io.netty.bootstrap.AbstractBootstrapConfig;
import io.netty.bootstrap.ChannelFactory;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.DefaultChannelPromise;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.CipherSuiteFilter;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.JdkAlpnApplicationProtocolNegotiator;
import io.netty.handler.ssl.JdkApplicationProtocolNegotiator;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslProvider;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.util.internal.logging.JdkLoggerFactory;

/**
* This substitution avoid having loggers added to the build
*/
@TargetClass(className = "io.netty.util.internal.logging.InternalLoggerFactory")
final class Target_io_netty_util_internal_logging_InternalLoggerFactory {

@Substitute
private static InternalLoggerFactory newDefaultFactory( String name) {
return JdkLoggerFactory.INSTANCE;
}
}

// SSL
// This whole section is mostly about removing static analysis references to openssl/tcnative

@TargetClass(className = "io.netty.handler.ssl.JdkSslServerContext")
final class Target_io_netty_handler_ssl_JdkSslServerContext {

@Alias
Target_io_netty_handler_ssl_JdkSslServerContext( Provider provider,
X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword,
KeyManagerFactory keyManagerFactory, Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
ApplicationProtocolConfig apn, long sessionCacheSize, long sessionTimeout,
ClientAuth clientAuth, String[] protocols, boolean startTls,
String keyStore)
throws SSLException
{
}
}

@TargetClass(className = "io.netty.handler.ssl.JdkSslClientContext")
final class Target_io_netty_handler_ssl_JdkSslClientContext {

@Alias
Target_io_netty_handler_ssl_JdkSslClientContext( Provider sslContextProvider,
X509Certificate[] trustCertCollection,
TrustManagerFactory trustManagerFactory, X509Certificate[] keyCertChain, PrivateKey key,
String keyPassword, KeyManagerFactory keyManagerFactory, Iterable<String> ciphers,
CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, String[] protocols,
long sessionCacheSize, long sessionTimeout, String keyStoreType)
throws SSLException
{

}
}

@TargetClass(className = "io.netty.handler.ssl.SslHandler$SslEngineType")
final class Target_io_netty_handler_ssl_SslHandler$SslEngineType {

@Alias
public static Target_io_netty_handler_ssl_SslHandler$SslEngineType JDK;

@Substitute
static Target_io_netty_handler_ssl_SslHandler$SslEngineType forEngine( SSLEngine engine) {
return JDK;
}
}

@TargetClass(className = "io.netty.handler.ssl.JdkAlpnApplicationProtocolNegotiator$AlpnWrapper", onlyWith = JDK11OrLater.class)
final class Target_io_netty_handler_ssl_JdkAlpnApplicationProtocolNegotiator_AlpnWrapper {
@Substitute
@SuppressWarnings( "deprecation" )
public SSLEngine wrapSslEngine( SSLEngine engine, ByteBufAllocator alloc,
JdkApplicationProtocolNegotiator applicationNegotiator, boolean isServer) {
return (SSLEngine) (Object) new Target_io_netty_handler_ssl_Java9SslEngine(
engine, applicationNegotiator, isServer);
}

}

@TargetClass(className = "io.netty.handler.ssl.Java9SslEngine", onlyWith = JDK11OrLater.class)
final class Target_io_netty_handler_ssl_Java9SslEngine {
@Alias
@SuppressWarnings( "deprecation" )
Target_io_netty_handler_ssl_Java9SslEngine(final SSLEngine engine,
final JdkApplicationProtocolNegotiator applicationNegotiator, final boolean isServer) {

}
}

@TargetClass(className = "io.netty.handler.ssl.SslContext")
final class Target_io_netty_handler_ssl_SslContext {

@Substitute
static SslContext newServerContextInternal(SslProvider provider,
Provider sslContextProvider,
X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
boolean enableOcsp, String keyStoreType)
throws SSLException
{

if (enableOcsp) {
throw new IllegalArgumentException("OCSP is not supported with this SslProvider: " + provider);
}
return (SslContext) (Object) new Target_io_netty_handler_ssl_JdkSslServerContext(
sslContextProvider,
trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,
clientAuth, protocols, startTls, keyStoreType);
}

@Substitute
static SslContext newClientContextInternal(
SslProvider provider,
Provider sslContextProvider,
X509Certificate[] trustCert, TrustManagerFactory trustManagerFactory,
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, String[] protocols,
long sessionCacheSize, long sessionTimeout, boolean enableOcsp, String keyStoreType) throws SSLException
{
if (enableOcsp) {
throw new IllegalArgumentException("OCSP is not supported with this SslProvider: " + provider);
}
return (SslContext) (Object) new Target_io_netty_handler_ssl_JdkSslClientContext(
sslContextProvider,
trustCert, trustManagerFactory, keyCertChain, key, keyPassword,
keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize,
sessionTimeout, keyStoreType);
}

}

@TargetClass(className = "io.netty.handler.ssl.JdkDefaultApplicationProtocolNegotiator")
final class Target_io_netty_handler_ssl_JdkDefaultApplicationProtocolNegotiator {

@Alias
public static Target_io_netty_handler_ssl_JdkDefaultApplicationProtocolNegotiator INSTANCE;
}

@TargetClass(className = "io.netty.handler.ssl.JdkSslContext")
final class Target_io_netty_handler_ssl_JdkSslContext {

@Substitute
static JdkApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config, boolean isServer) {
if (config == null) {
return (JdkApplicationProtocolNegotiator) (Object) Target_io_netty_handler_ssl_JdkDefaultApplicationProtocolNegotiator.INSTANCE;
}

switch (config.protocol()) {
case NONE:
return (JdkApplicationProtocolNegotiator) (Object) Target_io_netty_handler_ssl_JdkDefaultApplicationProtocolNegotiator.INSTANCE;
case ALPN:
if (isServer) {
// GRAAL RC9 bug: https://github.com/oracle/graal/issues/813
// switch(config.selectorFailureBehavior()) {
// case FATAL_ALERT:
// return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
// case NO_ADVERTISE:
// return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
// default:
// throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
// .append(config.selectorFailureBehavior()).append(" failure behavior").toString());
// }
SelectorFailureBehavior behavior = config.selectorFailureBehavior();
if (behavior == SelectorFailureBehavior.FATAL_ALERT)
return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
else if (behavior == SelectorFailureBehavior.NO_ADVERTISE)
return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
else {
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.selectorFailureBehavior()).append(" failure behavior").toString());
}
} else {
switch (config.selectedListenerFailureBehavior()) {
case ACCEPT:
return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
case FATAL_ALERT:
return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.selectedListenerFailureBehavior()).append(" failure behavior")
.toString());
}
}
default:
throw new UnsupportedOperationException(
new StringBuilder("JDK provider does not support ").append(config.protocol()).append(" protocol")
.toString());
}
}

}

/*
* This one only prints exceptions otherwise we get a useless bogus
* exception message: https://github.com/eclipse-vertx/vert.x/issues/1657
*/
@TargetClass(className = "io.netty.bootstrap.AbstractBootstrap")
final class Target_io_netty_bootstrap_AbstractBootstrap {

@Alias
private ChannelFactory channelFactory;

@Alias
void init(Channel channel) throws Exception
{
}

@Alias
public AbstractBootstrapConfig config() {
return null;
}

@Substitute
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch ( Throwable t) {
// THE FIX IS HERE:
t.printStackTrace();
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}

ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}

// If we are here and the promise is not failed, it's one of the following cases:
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
// i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
// 2) If we attempted registration from the other thread, the registration request has been successfully
// added to the event loop's task queue for later execution.
// i.e. It's safe to attempt bind() or connect() now:
// because bind() or connect() will be executed *after* the scheduled registration task is executed
// because register(), bind(), and connect() are all bound to the same thread.

return regFuture;

}
}

@TargetClass(className = "io.netty.channel.nio.NioEventLoop")
final class Target_io_netty_channel_nio_NioEventLoop {

@Substitute
private static Queue<Runnable> newTaskQueue0(int maxPendingTasks) {
return new LinkedBlockingDeque<>();
}
}

class NettySubstitutions {

}
Loading

0 comments on commit b03bc89

Please sign in to comment.