Skip to content

Commit b03bc89

Browse files
Add support for native compilation.
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.
1 parent e257197 commit b03bc89

File tree

6 files changed

+459
-0
lines changed

6 files changed

+459
-0
lines changed

driver/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@
7878
<groupId>io.projectreactor</groupId>
7979
<artifactId>reactor-test</artifactId>
8080
</dependency>
81+
<dependency>
82+
<groupId>com.oracle.substratevm</groupId>
83+
<artifactId>svm</artifactId>
84+
</dependency>
8185
</dependencies>
8286

8387
<build>
@@ -232,6 +236,14 @@
232236
<transformers>
233237
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
234238
</transformers>
239+
<filters>
240+
<filter>
241+
<artifact>io.netty:*</artifact>
242+
<excludes>
243+
<exclude>META-INF/native-image/**</exclude>
244+
</excludes>
245+
</filter>
246+
</filters>
235247
<shadeTestJar>true</shadeTestJar>
236248
<createSourcesJar>true</createSourcesJar>
237249
</configuration>
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
/*
2+
* Copyright (c) 2002-2019 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver.internal.svm;
20+
21+
import com.oracle.svm.core.annotate.Alias;
22+
import com.oracle.svm.core.annotate.Substitute;
23+
import com.oracle.svm.core.annotate.TargetClass;
24+
import com.oracle.svm.core.jdk.JDK11OrLater;
25+
26+
import java.security.PrivateKey;
27+
import java.security.Provider;
28+
import java.security.cert.X509Certificate;
29+
import java.util.Queue;
30+
import java.util.concurrent.LinkedBlockingDeque;
31+
import javax.net.ssl.KeyManagerFactory;
32+
import javax.net.ssl.SSLEngine;
33+
import javax.net.ssl.SSLException;
34+
import javax.net.ssl.TrustManagerFactory;
35+
36+
import io.netty.bootstrap.AbstractBootstrapConfig;
37+
import io.netty.bootstrap.ChannelFactory;
38+
import io.netty.buffer.ByteBufAllocator;
39+
import io.netty.channel.Channel;
40+
import io.netty.channel.ChannelFuture;
41+
import io.netty.channel.DefaultChannelPromise;
42+
import io.netty.handler.ssl.ApplicationProtocolConfig;
43+
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
44+
import io.netty.handler.ssl.CipherSuiteFilter;
45+
import io.netty.handler.ssl.ClientAuth;
46+
import io.netty.handler.ssl.JdkAlpnApplicationProtocolNegotiator;
47+
import io.netty.handler.ssl.JdkApplicationProtocolNegotiator;
48+
import io.netty.handler.ssl.SslContext;
49+
import io.netty.handler.ssl.SslProvider;
50+
import io.netty.util.concurrent.GlobalEventExecutor;
51+
import io.netty.util.internal.logging.InternalLoggerFactory;
52+
import io.netty.util.internal.logging.JdkLoggerFactory;
53+
54+
/**
55+
* This substitution avoid having loggers added to the build
56+
*/
57+
@TargetClass(className = "io.netty.util.internal.logging.InternalLoggerFactory")
58+
final class Target_io_netty_util_internal_logging_InternalLoggerFactory {
59+
60+
@Substitute
61+
private static InternalLoggerFactory newDefaultFactory( String name) {
62+
return JdkLoggerFactory.INSTANCE;
63+
}
64+
}
65+
66+
// SSL
67+
// This whole section is mostly about removing static analysis references to openssl/tcnative
68+
69+
@TargetClass(className = "io.netty.handler.ssl.JdkSslServerContext")
70+
final class Target_io_netty_handler_ssl_JdkSslServerContext {
71+
72+
@Alias
73+
Target_io_netty_handler_ssl_JdkSslServerContext( Provider provider,
74+
X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
75+
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword,
76+
KeyManagerFactory keyManagerFactory, Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
77+
ApplicationProtocolConfig apn, long sessionCacheSize, long sessionTimeout,
78+
ClientAuth clientAuth, String[] protocols, boolean startTls,
79+
String keyStore)
80+
throws SSLException
81+
{
82+
}
83+
}
84+
85+
@TargetClass(className = "io.netty.handler.ssl.JdkSslClientContext")
86+
final class Target_io_netty_handler_ssl_JdkSslClientContext {
87+
88+
@Alias
89+
Target_io_netty_handler_ssl_JdkSslClientContext( Provider sslContextProvider,
90+
X509Certificate[] trustCertCollection,
91+
TrustManagerFactory trustManagerFactory, X509Certificate[] keyCertChain, PrivateKey key,
92+
String keyPassword, KeyManagerFactory keyManagerFactory, Iterable<String> ciphers,
93+
CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, String[] protocols,
94+
long sessionCacheSize, long sessionTimeout, String keyStoreType)
95+
throws SSLException
96+
{
97+
98+
}
99+
}
100+
101+
@TargetClass(className = "io.netty.handler.ssl.SslHandler$SslEngineType")
102+
final class Target_io_netty_handler_ssl_SslHandler$SslEngineType {
103+
104+
@Alias
105+
public static Target_io_netty_handler_ssl_SslHandler$SslEngineType JDK;
106+
107+
@Substitute
108+
static Target_io_netty_handler_ssl_SslHandler$SslEngineType forEngine( SSLEngine engine) {
109+
return JDK;
110+
}
111+
}
112+
113+
@TargetClass(className = "io.netty.handler.ssl.JdkAlpnApplicationProtocolNegotiator$AlpnWrapper", onlyWith = JDK11OrLater.class)
114+
final class Target_io_netty_handler_ssl_JdkAlpnApplicationProtocolNegotiator_AlpnWrapper {
115+
@Substitute
116+
@SuppressWarnings( "deprecation" )
117+
public SSLEngine wrapSslEngine( SSLEngine engine, ByteBufAllocator alloc,
118+
JdkApplicationProtocolNegotiator applicationNegotiator, boolean isServer) {
119+
return (SSLEngine) (Object) new Target_io_netty_handler_ssl_Java9SslEngine(
120+
engine, applicationNegotiator, isServer);
121+
}
122+
123+
}
124+
125+
@TargetClass(className = "io.netty.handler.ssl.Java9SslEngine", onlyWith = JDK11OrLater.class)
126+
final class Target_io_netty_handler_ssl_Java9SslEngine {
127+
@Alias
128+
@SuppressWarnings( "deprecation" )
129+
Target_io_netty_handler_ssl_Java9SslEngine(final SSLEngine engine,
130+
final JdkApplicationProtocolNegotiator applicationNegotiator, final boolean isServer) {
131+
132+
}
133+
}
134+
135+
@TargetClass(className = "io.netty.handler.ssl.SslContext")
136+
final class Target_io_netty_handler_ssl_SslContext {
137+
138+
@Substitute
139+
static SslContext newServerContextInternal(SslProvider provider,
140+
Provider sslContextProvider,
141+
X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
142+
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
143+
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
144+
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
145+
boolean enableOcsp, String keyStoreType)
146+
throws SSLException
147+
{
148+
149+
if (enableOcsp) {
150+
throw new IllegalArgumentException("OCSP is not supported with this SslProvider: " + provider);
151+
}
152+
return (SslContext) (Object) new Target_io_netty_handler_ssl_JdkSslServerContext(
153+
sslContextProvider,
154+
trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,
155+
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,
156+
clientAuth, protocols, startTls, keyStoreType);
157+
}
158+
159+
@Substitute
160+
static SslContext newClientContextInternal(
161+
SslProvider provider,
162+
Provider sslContextProvider,
163+
X509Certificate[] trustCert, TrustManagerFactory trustManagerFactory,
164+
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
165+
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, String[] protocols,
166+
long sessionCacheSize, long sessionTimeout, boolean enableOcsp, String keyStoreType) throws SSLException
167+
{
168+
if (enableOcsp) {
169+
throw new IllegalArgumentException("OCSP is not supported with this SslProvider: " + provider);
170+
}
171+
return (SslContext) (Object) new Target_io_netty_handler_ssl_JdkSslClientContext(
172+
sslContextProvider,
173+
trustCert, trustManagerFactory, keyCertChain, key, keyPassword,
174+
keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize,
175+
sessionTimeout, keyStoreType);
176+
}
177+
178+
}
179+
180+
@TargetClass(className = "io.netty.handler.ssl.JdkDefaultApplicationProtocolNegotiator")
181+
final class Target_io_netty_handler_ssl_JdkDefaultApplicationProtocolNegotiator {
182+
183+
@Alias
184+
public static Target_io_netty_handler_ssl_JdkDefaultApplicationProtocolNegotiator INSTANCE;
185+
}
186+
187+
@TargetClass(className = "io.netty.handler.ssl.JdkSslContext")
188+
final class Target_io_netty_handler_ssl_JdkSslContext {
189+
190+
@Substitute
191+
static JdkApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config, boolean isServer) {
192+
if (config == null) {
193+
return (JdkApplicationProtocolNegotiator) (Object) Target_io_netty_handler_ssl_JdkDefaultApplicationProtocolNegotiator.INSTANCE;
194+
}
195+
196+
switch (config.protocol()) {
197+
case NONE:
198+
return (JdkApplicationProtocolNegotiator) (Object) Target_io_netty_handler_ssl_JdkDefaultApplicationProtocolNegotiator.INSTANCE;
199+
case ALPN:
200+
if (isServer) {
201+
// GRAAL RC9 bug: https://github.com/oracle/graal/issues/813
202+
// switch(config.selectorFailureBehavior()) {
203+
// case FATAL_ALERT:
204+
// return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
205+
// case NO_ADVERTISE:
206+
// return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
207+
// default:
208+
// throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
209+
// .append(config.selectorFailureBehavior()).append(" failure behavior").toString());
210+
// }
211+
SelectorFailureBehavior behavior = config.selectorFailureBehavior();
212+
if (behavior == SelectorFailureBehavior.FATAL_ALERT)
213+
return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
214+
else if (behavior == SelectorFailureBehavior.NO_ADVERTISE)
215+
return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
216+
else {
217+
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
218+
.append(config.selectorFailureBehavior()).append(" failure behavior").toString());
219+
}
220+
} else {
221+
switch (config.selectedListenerFailureBehavior()) {
222+
case ACCEPT:
223+
return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
224+
case FATAL_ALERT:
225+
return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
226+
default:
227+
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
228+
.append(config.selectedListenerFailureBehavior()).append(" failure behavior")
229+
.toString());
230+
}
231+
}
232+
default:
233+
throw new UnsupportedOperationException(
234+
new StringBuilder("JDK provider does not support ").append(config.protocol()).append(" protocol")
235+
.toString());
236+
}
237+
}
238+
239+
}
240+
241+
/*
242+
* This one only prints exceptions otherwise we get a useless bogus
243+
* exception message: https://github.com/eclipse-vertx/vert.x/issues/1657
244+
*/
245+
@TargetClass(className = "io.netty.bootstrap.AbstractBootstrap")
246+
final class Target_io_netty_bootstrap_AbstractBootstrap {
247+
248+
@Alias
249+
private ChannelFactory channelFactory;
250+
251+
@Alias
252+
void init(Channel channel) throws Exception
253+
{
254+
}
255+
256+
@Alias
257+
public AbstractBootstrapConfig config() {
258+
return null;
259+
}
260+
261+
@Substitute
262+
final ChannelFuture initAndRegister() {
263+
Channel channel = null;
264+
try {
265+
channel = channelFactory.newChannel();
266+
init(channel);
267+
} catch ( Throwable t) {
268+
// THE FIX IS HERE:
269+
t.printStackTrace();
270+
if (channel != null) {
271+
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
272+
channel.unsafe().closeForcibly();
273+
}
274+
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
275+
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
276+
}
277+
278+
ChannelFuture regFuture = config().group().register(channel);
279+
if (regFuture.cause() != null) {
280+
if (channel.isRegistered()) {
281+
channel.close();
282+
} else {
283+
channel.unsafe().closeForcibly();
284+
}
285+
}
286+
287+
// If we are here and the promise is not failed, it's one of the following cases:
288+
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
289+
// i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
290+
// 2) If we attempted registration from the other thread, the registration request has been successfully
291+
// added to the event loop's task queue for later execution.
292+
// i.e. It's safe to attempt bind() or connect() now:
293+
// because bind() or connect() will be executed *after* the scheduled registration task is executed
294+
// because register(), bind(), and connect() are all bound to the same thread.
295+
296+
return regFuture;
297+
298+
}
299+
}
300+
301+
@TargetClass(className = "io.netty.channel.nio.NioEventLoop")
302+
final class Target_io_netty_channel_nio_NioEventLoop {
303+
304+
@Substitute
305+
private static Queue<Runnable> newTaskQueue0(int maxPendingTasks) {
306+
return new LinkedBlockingDeque<>();
307+
}
308+
}
309+
310+
class NettySubstitutions {
311+
312+
}

0 commit comments

Comments
 (0)