From 6467ad9d66075116645a47cff16e0f3bd0c598d8 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 2 Oct 2024 16:11:27 +0300 Subject: [PATCH 1/3] Add comments explaining the NettyProcessor native-image config --- .../netty/deployment/NettyProcessor.java | 118 +++++++++++++++--- 1 file changed, 104 insertions(+), 14 deletions(-) diff --git a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java index b807f875032aa..919e28bf0a660 100644 --- a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java +++ b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java @@ -101,29 +101,55 @@ NativeImageConfigBuildItem build( // Since buffers are cached to threads, the malloc overhead is temporary anyway .addNativeImageSystemProperty("io.netty.allocator.maxOrder", maxOrder) .addRuntimeInitializedClass("io.netty.handler.ssl.JdkNpnApplicationProtocolNegotiator") + // Runtime initialize to respect io.netty.handler.ssl.conscrypt.useBufferAllocator .addRuntimeInitializedClass("io.netty.handler.ssl.ConscryptAlpnSslEngine") + // Runtime initialize due to the use of tcnative in the static initializers? .addRuntimeInitializedClass("io.netty.handler.ssl.ReferenceCountedOpenSslEngine") + // Runtime initialize to respect run-time provided values of the following properties: + // - io.netty.handler.ssl.openssl.bioNonApplicationBufferSize + // - io.netty.handler.ssl.openssl.useTasks + // - jdk.tls.client.enableSessionTicketExtension + // - io.netty.handler.ssl.openssl.sessionCacheServer + // - io.netty.handler.ssl.openssl.sessionCacheClient + // - jdk.tls.ephemeralDHKeySize .addRuntimeInitializedClass("io.netty.handler.ssl.ReferenceCountedOpenSslContext") .addRuntimeInitializedClass("io.netty.handler.ssl.ReferenceCountedOpenSslClientContext") + // .addRuntimeInitializedClass("io.netty.handler.ssl.ReferenceCountedOpenSslClientContext") + // Runtime initialize to respect run-time provided values of the following properties: + // - keystore.type + // - ssl.KeyManagerFactory.algorithm + // - ssl.TrustManagerFactory.algorithm .addRuntimeInitializedClass("io.netty.handler.ssl.JdkSslServerContext") .addRuntimeInitializedClass("io.netty.handler.ssl.JdkSslClientContext") + // .addRuntimeInitializedClass("io.netty.handler.ssl.JdkSslClientContext") + // Runtime initialize to prevent embedding SecureRandom instances in the native image .addRuntimeInitializedClass("io.netty.handler.ssl.util.ThreadLocalInsecureRandom") - .addRuntimeInitializedClass("io.netty.buffer.ByteBufUtil$HexUtil") - .addRuntimeInitializedClass("io.netty.buffer.PooledByteBufAllocator") - .addRuntimeInitializedClass("io.netty.buffer.ByteBufAllocator") - .addRuntimeInitializedClass("io.netty.buffer.ByteBufUtil") - // The default channel id uses the process id, it should not be cached in the native image. + // The default channel id uses the process id, it should not be cached in the native image. This way we + // also respect the run-time provided value of the io.netty.processId property, io.netty.machineId + // property is being hardcoded in setNettyMachineId method .addRuntimeInitializedClass("io.netty.channel.DefaultChannelId") + // Disable leak detection by default, it can still be enabled via + // io.netty.util.ResourceLeakDetector.setLevel method .addNativeImageSystemProperty("io.netty.leakDetection.level", "DISABLED"); if (QuarkusClassLoader.isClassPresentAtRuntime("io.netty.handler.codec.http.HttpObjectEncoder")) { builder + // Runtime initialize due to transitive use of the io.netty.util.internal.PlatformDependent class + // when initializing CRLF_BUF and ZERO_CRLF_CRLF_BUF .addRuntimeInitializedClass("io.netty.handler.codec.http.HttpObjectEncoder") .addRuntimeInitializedClass("io.netty.handler.codec.http.websocketx.extensions.compression.DeflateDecoder") - .addRuntimeInitializedClass("io.netty.handler.codec.http.websocketx.WebSocket00FrameEncoder") - .addRuntimeInitializedClass("io.netty.handler.codec.compression.ZstdOptions") - .addRuntimeInitializedClass("io.netty.handler.codec.compression.ZstdConstants"); - // Brotli is an optional dependency + .addRuntimeInitializedClass("io.netty.handler.codec.http.websocketx.WebSocket00FrameEncoder"); + // Zstd is an optional dependency, runtime initialize to avoid IllegalStateException when zstd is not + // available. This will result in a runtime ClassNotFoundException if the user tries to use zstd. + if (!QuarkusClassLoader.isClassPresentAtRuntime("com.github.luben.zstd.Zstd")) { + builder.addRuntimeInitializedClass("io.netty.handler.codec.compression.ZstdOptions") + .addRuntimeInitializedClass("io.netty.handler.codec.compression.ZstdConstants"); + } + // Brotli is an optional dependency, we should only runtime initialize BrotliOptions to avoid + // IllegalStateException when brotli (e.g. com.aayushatharva.brotli4j.Brotli4jLoader) is not available. + // This will result in a runtime ClassNotFoundException if the user tries to use Brotli. + // Due to https://github.com/quarkusio/quarkus/issues/43662 we cannot do this yet though so we always enable + // runtime initialization of BrotliOptions if the class is present if (QuarkusClassLoader.isClassPresentAtRuntime("io.netty.handler.codec.compression.BrotliOptions")) { builder.addRuntimeInitializedClass("io.netty.handler.codec.compression.BrotliOptions"); } @@ -133,52 +159,116 @@ NativeImageConfigBuildItem build( if (QuarkusClassLoader.isClassPresentAtRuntime("io.netty.handler.codec.http2.Http2CodecUtil")) { builder + // Runtime initialize due to the transitive use of the io.netty.util.internal.PlatformDependent + // class in the static initializers .addRuntimeInitializedClass("io.netty.handler.codec.http2.Http2CodecUtil") - .addRuntimeInitializedClass("io.netty.handler.codec.http2.Http2ClientUpgradeCodec") .addRuntimeInitializedClass("io.netty.handler.codec.http2.DefaultHttp2FrameWriter") - .addRuntimeInitializedClass("io.netty.handler.codec.http2.Http2ConnectionHandler"); + .addRuntimeInitializedClass("io.netty.handler.codec.http2.Http2ConnectionHandler") + // Runtime initialize due to dependency on io.netty.handler.codec.http2.Http2CodecUtil + .addRuntimeInitializedClass("io.netty.handler.codec.http2.Http2ClientUpgradeCodec"); } else { log.debug("Not registering Netty HTTP2 classes as they were not found"); } if (QuarkusClassLoader.isClassPresentAtRuntime("io.netty.channel.unix.UnixChannel")) { + // Runtime initialize to avoid embedding quite a few Strings in the image heap builder.addRuntimeInitializedClass("io.netty.channel.unix.Errors") + // Runtime initialize due to the use of AtomicIntegerFieldUpdater? .addRuntimeInitializedClass("io.netty.channel.unix.FileDescriptor") + // Runtime initialize due to the use of Buffer.addressSize() in the static initializers .addRuntimeInitializedClass("io.netty.channel.unix.IovArray") + // Runtime initialize due to the use of native methods in the static initializers? .addRuntimeInitializedClass("io.netty.channel.unix.Limits"); } else { log.debug("Not registering Netty native unix classes as they were not found"); } if (QuarkusClassLoader.isClassPresentAtRuntime("io.netty.channel.epoll.EpollMode")) { + // Runtime initialize due to machine dependent native methods being called in static initializer and to + // respect the run-time provided value of io.netty.transport.noNative builder.addRuntimeInitializedClass("io.netty.channel.epoll.Epoll") + // Runtime initialize due to machine dependent native methods being called in static initializer .addRuntimeInitializedClass("io.netty.channel.epoll.EpollEventArray") + // Runtime initialize due to dependency on Epoll and to respect the run-time provided value of + // io.netty.channel.epoll.epollWaitThreshold .addRuntimeInitializedClass("io.netty.channel.epoll.EpollEventLoop") + // Runtime initialize due to InetAddress fields, dependencies on native methods and to transitively + // respect a number of properties, e.g. java.nio.channels.spi.SelectorProvider .addRuntimeInitializedClass("io.netty.channel.epoll.Native"); } else { log.debug("Not registering Netty native epoll classes as they were not found"); } if (QuarkusClassLoader.isClassPresentAtRuntime("io.netty.channel.kqueue.AcceptFilter")) { + // Runtime initialize due to machine dependent native methods being called in static initializer and to + // respect the run-time provided value of io.netty.transport.noNative builder.addRuntimeInitializedClass("io.netty.channel.kqueue.KQueue") + // Runtime initialize due to machine dependent native methods being called in static initializers .addRuntimeInitializedClass("io.netty.channel.kqueue.KQueueEventArray") - .addRuntimeInitializedClass("io.netty.channel.kqueue.KQueueEventLoop") - .addRuntimeInitializedClass("io.netty.channel.kqueue.Native"); + .addRuntimeInitializedClass("io.netty.channel.kqueue.Native") + // Runtime initialize due to dependency on Epoll and the use of AtomicIntegerFieldUpdater? + .addRuntimeInitializedClass("io.netty.channel.kqueue.KQueueEventLoop"); } else { log.debug("Not registering Netty native kqueue classes as they were not found"); } + // Runtime initialize due to platform dependent initialization and to respect the run-time provided value of the + // properties: + // - io.netty.maxDirectMemory + // - io.netty.uninitializedArrayAllocationThreshold + // - io.netty.noPreferDirect + // - io.netty.osClassifiers + // - io.netty.tmpdir + // - java.io.tmpdir + // - io.netty.bitMode + // - sun.arch.data.model + // - com.ibm.vm.bitmode builder.addRuntimeReinitializedClass("io.netty.util.internal.PlatformDependent") + // Similarly for properties: + // - io.netty.noUnsafe + // - sun.misc.unsafe.memory.access + // - io.netty.tryUnsafe + // - org.jboss.netty.tryUnsafe + // - io.netty.tryReflectionSetAccessible .addRuntimeReinitializedClass("io.netty.util.internal.PlatformDependent0"); if (QuarkusClassLoader.isClassPresentAtRuntime("io.netty.buffer.UnpooledByteBufAllocator")) { + // Runtime initialize due to the use of the io.netty.util.internal.PlatformDependent class builder.addRuntimeReinitializedClass("io.netty.buffer.UnpooledByteBufAllocator") .addRuntimeReinitializedClass("io.netty.buffer.Unpooled") + // Runtime initialize due to dependency on io.netty.buffer.Unpooled .addRuntimeReinitializedClass("io.netty.handler.codec.http.HttpObjectAggregator") - .addRuntimeReinitializedClass("io.netty.handler.codec.ReplayingDecoderByteBuf"); + .addRuntimeReinitializedClass("io.netty.handler.codec.ReplayingDecoderByteBuf") + // Runtime initialize to avoid embedding quite a few Strings in the image heap + .addRuntimeInitializedClass("io.netty.buffer.ByteBufUtil$HexUtil") + // Runtime initialize due to the use of the io.netty.util.internal.PlatformDependent class in the + // static initializers and to respect the run-time provided value of the following properties: + // - io.netty.allocator.directMemoryCacheAlignment + // - io.netty.allocator.pageSize + // - io.netty.allocator.maxOrder + // - io.netty.allocator.numHeapArenas + // - io.netty.allocator.numDirectArenas + // - io.netty.allocator.smallCacheSize + // - io.netty.allocator.normalCacheSize + // - io.netty.allocator.maxCachedBufferCapacity + // - io.netty.allocator.cacheTrimInterval + // - io.netty.allocation.cacheTrimIntervalMillis + // - io.netty.allocator.cacheTrimIntervalMillis + // - io.netty.allocator.useCacheForAllThreads + // - io.netty.allocator.maxCachedByteBuffersPerChunk + .addRuntimeInitializedClass("io.netty.buffer.PooledByteBufAllocator") + // Runtime initialize due to the use of ByteBufUtil.DEFAULT_ALLOCATOR in the static initializers + .addRuntimeInitializedClass("io.netty.buffer.ByteBufAllocator") + // Runtime initialize due to the use of the io.netty.util.internal.PlatformDependent class in the + // static initializers and to respect the run-time provided value of the following properties: + // - io.netty.allocator.type + // - io.netty.threadLocalDirectBufferSize + // - io.netty.maxThreadLocalCharBufferSize + .addRuntimeInitializedClass("io.netty.buffer.ByteBufUtil"); if (QuarkusClassLoader .isClassPresentAtRuntime("org.jboss.resteasy.reactive.client.impl.multipart.QuarkusMultipartFormUpload")) { + // Runtime initialize due to dependency on io.netty.buffer.Unpooled builder.addRuntimeReinitializedClass( "org.jboss.resteasy.reactive.client.impl.multipart.QuarkusMultipartFormUpload"); } From 41728eae5dd162145899b97420fe89ced84959f1 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 2 Oct 2024 16:12:58 +0300 Subject: [PATCH 2/3] Refactor HttpContentCompressorSubstitutions for consistency Adopt `Target_*` naming convention for substitution classes --- .../HttpContentCompressorSubstitutions.java | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/graal/HttpContentCompressorSubstitutions.java b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/graal/HttpContentCompressorSubstitutions.java index 2f2b360db19f6..2533b601525a2 100644 --- a/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/graal/HttpContentCompressorSubstitutions.java +++ b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/graal/HttpContentCompressorSubstitutions.java @@ -9,61 +9,61 @@ import io.netty.channel.ChannelHandlerContext; public class HttpContentCompressorSubstitutions { +} - @TargetClass(className = "io.netty.handler.codec.compression.ZstdEncoder", onlyWith = IsZstdAbsent.class) - public static final class ZstdEncoderFactorySubstitution { - - @Substitute - protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { - throw new UnsupportedOperationException(); - } +@TargetClass(className = "io.netty.handler.codec.compression.ZstdEncoder", onlyWith = IsZstdAbsent.class) +final class Target_io_netty_handler_codec_compression_ZstdEncoder { - @Substitute - protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) { - throw new UnsupportedOperationException(); - } + @Substitute + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { + throw new UnsupportedOperationException(); + } - @Substitute - public void flush(final ChannelHandlerContext ctx) { - throw new UnsupportedOperationException(); - } + @Substitute + protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) { + throw new UnsupportedOperationException(); } @Substitute - @TargetClass(className = "io.netty.handler.codec.compression.ZstdConstants", onlyWith = IsZstdAbsent.class) - public static final class ZstdConstants { + public void flush(final ChannelHandlerContext ctx) { + throw new UnsupportedOperationException(); + } +} - // The constants make calls to com.github.luben.zstd.Zstd so we cut links with that substitution. +@Substitute +@TargetClass(className = "io.netty.handler.codec.compression.ZstdConstants", onlyWith = IsZstdAbsent.class) +final class Target_io_netty_handler_codec_compression_ZstdConstants { - static final int DEFAULT_COMPRESSION_LEVEL = 0; + // The constants make calls to com.github.luben.zstd.Zstd so we cut links with that substitution. - static final int MIN_COMPRESSION_LEVEL = 0; + static final int DEFAULT_COMPRESSION_LEVEL = 0; - static final int MAX_COMPRESSION_LEVEL = 0; + static final int MIN_COMPRESSION_LEVEL = 0; - static final int MAX_BLOCK_SIZE = 0; + static final int MAX_COMPRESSION_LEVEL = 0; - static final int DEFAULT_BLOCK_SIZE = 0; - } + static final int MAX_BLOCK_SIZE = 0; - public static class IsZstdAbsent implements BooleanSupplier { + static final int DEFAULT_BLOCK_SIZE = 0; +} - private boolean zstdAbsent; +class IsZstdAbsent implements BooleanSupplier { - public IsZstdAbsent() { - try { - Class.forName("com.github.luben.zstd.Zstd"); - zstdAbsent = false; - } catch (Exception e) { - // It can be a classloading issue (the library is not available), or a native issue - // (the library for the current OS/arch is not available) - zstdAbsent = true; - } - } + private boolean zstdAbsent; - @Override - public boolean getAsBoolean() { - return zstdAbsent; + public IsZstdAbsent() { + try { + Class.forName("com.github.luben.zstd.Zstd"); + zstdAbsent = false; + } catch (Exception e) { + // It can be a classloading issue (the library is not available), or a native issue + // (the library for the current OS/arch is not available) + zstdAbsent = true; } } + + @Override + public boolean getAsBoolean() { + return zstdAbsent; + } } From 058eb14558fa441568397d412816348ef2331c97 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 2 Oct 2024 16:13:50 +0300 Subject: [PATCH 3/3] Remove obsolete runtime initialization of netty classes --- .../main/java/io/quarkus/netty/deployment/NettyProcessor.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java index 919e28bf0a660..8a50fd429a17e 100644 --- a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java +++ b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java @@ -100,7 +100,6 @@ NativeImageConfigBuildItem build( // Use small chunks to avoid a lot of wasted space. Default is 16mb * arenas (derived from core count) // Since buffers are cached to threads, the malloc overhead is temporary anyway .addNativeImageSystemProperty("io.netty.allocator.maxOrder", maxOrder) - .addRuntimeInitializedClass("io.netty.handler.ssl.JdkNpnApplicationProtocolNegotiator") // Runtime initialize to respect io.netty.handler.ssl.conscrypt.useBufferAllocator .addRuntimeInitializedClass("io.netty.handler.ssl.ConscryptAlpnSslEngine") // Runtime initialize due to the use of tcnative in the static initializers? @@ -113,14 +112,12 @@ NativeImageConfigBuildItem build( // - io.netty.handler.ssl.openssl.sessionCacheClient // - jdk.tls.ephemeralDHKeySize .addRuntimeInitializedClass("io.netty.handler.ssl.ReferenceCountedOpenSslContext") - .addRuntimeInitializedClass("io.netty.handler.ssl.ReferenceCountedOpenSslClientContext") // .addRuntimeInitializedClass("io.netty.handler.ssl.ReferenceCountedOpenSslClientContext") // Runtime initialize to respect run-time provided values of the following properties: // - keystore.type // - ssl.KeyManagerFactory.algorithm // - ssl.TrustManagerFactory.algorithm .addRuntimeInitializedClass("io.netty.handler.ssl.JdkSslServerContext") - .addRuntimeInitializedClass("io.netty.handler.ssl.JdkSslClientContext") // .addRuntimeInitializedClass("io.netty.handler.ssl.JdkSslClientContext") // Runtime initialize to prevent embedding SecureRandom instances in the native image .addRuntimeInitializedClass("io.netty.handler.ssl.util.ThreadLocalInsecureRandom")