From 7ed1fdae4054a6a94eb9e0a7b4132d11f6219ac8 Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Tue, 15 Apr 2025 23:11:07 +0800 Subject: [PATCH] update Netty to 4.2.0 --- .gitea/workflows/build.yaml | 20 +++++- docker/Dockerfile | 11 +++ docker/README.md | 8 ++- docker/build.gradle | 2 +- docker/rbcs-cli.sh | 3 + gradle.properties | 4 +- rbcs-cli/build.gradle | 33 ++++++++- rbcs-cli/native-image/reflect-config.json | 72 +++++++------------ rbcs-cli/native-image/resource-config.json | 6 +- rbcs-server/src/main/java/module-info.java | 12 ++-- .../rbcs/server/RemoteBuildCacheServer.kt | 60 +++++++--------- 11 files changed, 129 insertions(+), 102 deletions(-) create mode 100644 docker/rbcs-cli.sh diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 58acbae..aaaa53d 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -52,6 +52,8 @@ jobs: push: true pull: true tags: | + gitea.woggioni.net/woggioni/rbcs:latest + gitea.woggioni.net/woggioni/rbcs:${{ steps.retrieve-version.outputs.VERSION }} gitea.woggioni.net/woggioni/rbcs:memcache gitea.woggioni.net/woggioni/rbcs:memcache-${{ steps.retrieve-version.outputs.VERSION }} target: release-memcache @@ -66,8 +68,6 @@ jobs: push: true pull: true tags: | - gitea.woggioni.net/woggioni/rbcs:latest - gitea.woggioni.net/woggioni/rbcs:${{ steps.retrieve-version.outputs.VERSION }} gitea.woggioni.net/woggioni/rbcs:native gitea.woggioni.net/woggioni/rbcs:native-${{ steps.retrieve-version.outputs.VERSION }} target: release-native @@ -75,4 +75,20 @@ jobs: env: PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }} run: ./gradlew publish + - + name: Build rbcs jlink Docker image + uses: docker/build-push-action@v5.3.0 + with: + context: "docker/build/docker" + platforms: linux/amd64 + push: true + pull: true + tags: | + gitea.woggioni.net/woggioni/rbcs:jlink + gitea.woggioni.net/woggioni/rbcs:jlink-${{ steps.retrieve-version.outputs.VERSION }}-jlink + target: release-jlink + - name: Publish artifacts + env: + PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }} + run: ./gradlew publish diff --git a/docker/Dockerfile b/docker/Dockerfile index 7cab09e..3db02d1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -21,3 +21,14 @@ ADD rbcs-cli.upx /rbcs/rbcs-cli ENV RBCS_CONFIGURATION_DIR="/rbcs" WORKDIR /rbcs ENTRYPOINT ["/rbcs/rbcs-cli", "-XX:MaximumHeapSizePercent=70"] + +FROM debian:12-slim AS release-jlink +RUN mkdir -p /usr/share/java/rbcs +RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/rbcs-cli*.tar -C /usr/share/java/rbcs +ADD --chmod=755 rbcs-cli.sh /usr/local/bin/rbcs-cli +RUN adduser -u 1000 luser +USER luser +WORKDIR /home/luser +ADD logback.xml . +ENV JAVA_OPTS=-XX:-UseJVMCICompiler\ -Dlogback.configurationFile=logback.xml\ -XX:MaxRAMPercentage=70\ -XX:GCTimeRatio=24\ -XX:+UseZGC\ -XX:+ZGenerational +ENTRYPOINT ["/usr/local/bin/rbcs-cli"] diff --git a/docker/README.md b/docker/README.md index c28ff2f..605a995 100644 --- a/docker/README.md +++ b/docker/README.md @@ -11,11 +11,15 @@ The `memcache` image is similar to the `vanilla` image, except that it also cont the `rbcs-server-memcache` plugin in the `plugins` folder, use this image if you don't want to use the `native` image and want to use memcache as the cache backend -The `native` image contains a native, statically-linked executable created with GraalVM +The `native` image contains a native, statically-linked executable created with GraalVM Native Image that has no userspace dependencies. It also embeds the memcache plugin inside the executable. Use this image for maximum efficiency and minimal memory footprint. -## Which image shoud I use? +The `jlink` image contains a custom Java runtime created with GraalVM's Jlink +that only depends on glibc. It also contains the memcache plugin in the module path. +Use this image for best performance. + +## Which image should I use? The `native` image uses Java's SerialGC, so it's ideal for constrained environment like containers or small servers, if you have a lot of resources and want to squeeze out the maximum throughput you should consider the `vanilla` or `memcache` image, then choose and fine tune the garbage collector. diff --git a/docker/build.gradle b/docker/build.gradle index 4b23bce..5070a87 100644 --- a/docker/build.gradle +++ b/docker/build.gradle @@ -29,7 +29,7 @@ Provider prepareDockerBuild = tasks.register('prepareDockerBuild', Copy) { group = 'docker' into project.layout.buildDirectory.file('docker') from(configurations.docker) - from(file('Dockerfile')) + from(files('Dockerfile', 'rbcs-cli.sh')) from(rootProject.file('conf')) { include 'logback.xml' } diff --git a/docker/rbcs-cli.sh b/docker/rbcs-cli.sh new file mode 100644 index 0000000..28c3b6d --- /dev/null +++ b/docker/rbcs-cli.sh @@ -0,0 +1,3 @@ +#!/bin/sh +DIR=/usr/share/java/rbcs +$DIR/bin/java $JAVA_OPTS -m net.woggioni.rbcs.cli "$@" \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index d1cdb28..75df4f8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,9 +2,9 @@ org.gradle.configuration-cache=false org.gradle.parallel=true org.gradle.caching=true -rbcs.version = 0.2.1 +rbcs.version = 0.3.0-SNAPSHOT -lys.version = 2025.03.08 +lys.version = 2025.04.16 gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven docker.registry.url=gitea.woggioni.net diff --git a/rbcs-cli/build.gradle b/rbcs-cli/build.gradle index 868cdfc..953d4f9 100644 --- a/rbcs-cli/build.gradle +++ b/rbcs-cli/build.gradle @@ -18,6 +18,7 @@ import net.woggioni.gradle.graalvm.UpxTask import net.woggioni.gradle.graalvm.JlinkPlugin import net.woggioni.gradle.graalvm.JlinkTask + sourceSets { configureNativeImage { java { @@ -29,6 +30,13 @@ sourceSets { } configurations { + + implementation { + resolutionStrategy { + exclude group: 'io.netty', module: 'netty-codec-marshalling' + exclude group: 'io.netty', module: 'netty-codec-protobuf' + } + } release { transitive = false canBeConsumed = true @@ -120,16 +128,36 @@ nativeImage { linkAtBuildTime = false classpath = project.files(jarTaskProvider, configurations.nativeImage) compressExecutable = true - compressionLevel = 10 + compressionLevel = 6 useLZMA = false } Provider upxTaskProvider = tasks.named(NativeImagePlugin.UPX_TASK_NAME, UpxTask) { } -tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) { +Provider jlinkTaskProvider = tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + vendor = JvmVendorSpec.GRAAL_VM + } + mainClass = mainClassName mainModule = 'net.woggioni.rbcs.cli' + classpath = project.files( + configurations.configureNativeImageRuntimeClasspath, + sourceSets.configureNativeImage.output + ) + additionalModules = [ + 'net.woggioni.rbcs.server.memcache', + 'ch.qos.logback.classic', + 'jdk.crypto.ec' + ] + compressionLevel = 2 + stripDebug = false +} + +Provider jlinkDistTarTaskProvider = tasks.named(JlinkPlugin.JLINK_DIST_TAR_TASK_NAME, Tar) { + exclude 'lib/libjvmcicompiler.so' } tasks.named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, ProcessResources) { @@ -143,6 +171,7 @@ tasks.named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, ProcessResources) { artifacts { release(envelopeJarTaskProvider) release(upxTaskProvider) + release(jlinkDistTarTaskProvider) } publishing { diff --git a/rbcs-cli/native-image/reflect-config.json b/rbcs-cli/native-image/reflect-config.json index f8e0ee4..1833a1a 100644 --- a/rbcs-cli/native-image/reflect-config.json +++ b/rbcs-cli/native-image/reflect-config.json @@ -120,6 +120,14 @@ "name":"io.netty.buffer.AbstractReferenceCountedByteBuf", "fields":[{"name":"refCnt"}] }, +{ + "name":"io.netty.buffer.AdaptivePoolingAllocator$Chunk", + "fields":[{"name":"refCnt"}] +}, +{ + "name":"io.netty.buffer.AdaptivePoolingAllocator$Magazine", + "fields":[{"name":"nextInLine"}] +}, { "name":"io.netty.channel.AbstractChannelHandlerContext", "fields":[{"name":"handlerState"}] @@ -284,20 +292,13 @@ "fields":[{"name":"producerIndex"}] }, { - "name":"io.netty.util.internal.shaded.org.jctools.queues.unpadded.MpscUnpaddedArrayQueueConsumerIndexField", + "name":"io.netty.util.internal.shaded.org.jctools.queues.MpmcArrayQueueConsumerIndexField", "fields":[{"name":"consumerIndex"}] }, { - "name":"io.netty.util.internal.shaded.org.jctools.queues.unpadded.MpscUnpaddedArrayQueueProducerIndexField", + "name":"io.netty.util.internal.shaded.org.jctools.queues.MpmcArrayQueueProducerIndexField", "fields":[{"name":"producerIndex"}] }, -{ - "name":"io.netty.util.internal.shaded.org.jctools.queues.unpadded.MpscUnpaddedArrayQueueProducerLimitField", - "fields":[{"name":"producerLimit"}] -}, -{ - "name":"java.io.FilePermission" -}, { "name":"java.lang.Object", "allDeclaredFields":true, @@ -307,26 +308,14 @@ "name":"java.lang.ProcessHandle", "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }] }, -{ - "name":"java.lang.RuntimePermission" -}, { "name":"java.lang.System", "methods":[{"name":"console","parameterTypes":[] }] }, { "name":"java.lang.Thread", - "fields":[{"name":"threadLocalRandomProbe"}] -}, -{ - "name":"java.net.NetPermission" -}, -{ - "name":"java.net.SocketPermission" -}, -{ - "name":"java.net.URLPermission", - "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] + "fields":[{"name":"threadLocalRandomProbe"}], + "methods":[{"name":"isVirtual","parameterTypes":[] }] }, { "name":"java.nio.Bits", @@ -358,18 +347,12 @@ { "name":"java.security.AlgorithmParametersSpi" }, -{ - "name":"java.security.AllPermission" -}, { "name":"java.security.KeyStoreSpi" }, { "name":"java.security.SecureRandomParameters" }, -{ - "name":"java.security.SecurityPermission" -}, { "name":"java.sql.Connection" }, @@ -444,9 +427,6 @@ "name":"java.time.ZonedDateTime", "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] }, -{ - "name":"java.util.PropertyPermission" -}, { "name":"java.util.concurrent.ForkJoinTask", "fields":[{"name":"aux"}, {"name":"status"}] @@ -467,22 +447,11 @@ "name":"java.util.concurrent.atomic.Striped64$Cell", "fields":[{"name":"value"}] }, -{ - "name":"java.util.zip.Adler32", - "methods":[{"name":"update","parameterTypes":["java.nio.ByteBuffer"] }] -}, -{ - "name":"java.util.zip.CRC32", - "methods":[{"name":"update","parameterTypes":["java.nio.ByteBuffer"] }] -}, { "name":"javax.security.auth.x500.X500Principal", "fields":[{"name":"thisX500Name"}], "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] }, -{ - "name":"javax.smartcardio.CardPermission" -}, { "name":"jdk.internal.misc.Unsafe", "methods":[{"name":"getUnsafe","parameterTypes":[] }] @@ -588,6 +557,9 @@ "name":"net.woggioni.rbcs.server.exception.ExceptionHandler", "methods":[{"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] }, +{ + "name":"net.woggioni.rbcs.server.handler.BlackHoleRequestHandler" +}, { "name":"net.woggioni.rbcs.server.handler.MaxRequestSizeHandler", "methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] @@ -615,7 +587,7 @@ { "name":"sun.misc.Unsafe", "fields":[{"name":"theUnsafe"}], - "methods":[{"name":"copyMemory","parameterTypes":["java.lang.Object","long","java.lang.Object","long","long"] }, {"name":"getAndAddLong","parameterTypes":["java.lang.Object","long","long"] }, {"name":"getAndSetObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] }, {"name":"invokeCleaner","parameterTypes":["java.nio.ByteBuffer"] }, {"name":"storeFence","parameterTypes":[] }] + "methods":[{"name":"copyMemory","parameterTypes":["java.lang.Object","long","java.lang.Object","long","long"] }, {"name":"getAndAddLong","parameterTypes":["java.lang.Object","long","long"] }, {"name":"getAndSetObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] }, {"name":"invokeCleaner","parameterTypes":["java.nio.ByteBuffer"] }] }, { "name":"sun.nio.ch.SelectorImpl", @@ -637,10 +609,6 @@ "name":"sun.security.provider.DSA$SHA256withDSA", "methods":[{"name":"","parameterTypes":[] }] }, -{ - "name":"sun.security.provider.JavaKeyStore$JKS", - "methods":[{"name":"","parameterTypes":[] }] -}, { "name":"sun.security.provider.MD5", "methods":[{"name":"","parameterTypes":[] }] @@ -729,6 +697,14 @@ "name":"sun.security.x509.CertificatePoliciesExtension", "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] }, +{ + "name":"sun.security.x509.ExtendedKeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.IssuerAlternativeNameExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, { "name":"sun.security.x509.KeyUsageExtension", "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] diff --git a/rbcs-cli/native-image/resource-config.json b/rbcs-cli/native-image/resource-config.json index 301f38f..af86f8f 100644 --- a/rbcs-cli/native-image/resource-config.json +++ b/rbcs-cli/native-image/resource-config.json @@ -32,14 +32,12 @@ "pattern":"\\Qnet/woggioni/rbcs/cli/logback.xml\\E" }, { "pattern":"\\Qnet/woggioni/rbcs/client/schema/rbcs-client.xsd\\E" + }, { + "pattern":"\\Qnet/woggioni/rbcs/server/memcache/schema/rbcs-memcache.xsd\\E" }, { "pattern":"\\Qnet/woggioni/rbcs/server/rbcs-default.xml\\E" }, { "pattern":"\\Qnet/woggioni/rbcs/server/schema/rbcs-server.xsd\\E" - }, { - "pattern":"\\Q/net/woggioni/rbcs/server/memcache/schema/rbcs-memcache.xsd\\E" - }, { - "pattern":"java.base:\\Qsun/text/resources/LineBreakIteratorData\\E" }]}, "bundles":[{ "name":"com.sun.org.apache.xerces.internal.impl.xpath.regex.message", diff --git a/rbcs-server/src/main/java/module-info.java b/rbcs-server/src/main/java/module-info.java index 055e4a4..433c5f9 100644 --- a/rbcs-server/src/main/java/module-info.java +++ b/rbcs-server/src/main/java/module-info.java @@ -3,21 +3,19 @@ import net.woggioni.rbcs.server.cache.FileSystemCacheProvider; import net.woggioni.rbcs.server.cache.InMemoryCacheProvider; module net.woggioni.rbcs.server { - requires java.sql; requires java.xml; - requires java.logging; requires java.naming; requires kotlin.stdlib; - requires io.netty.buffer; - requires io.netty.transport; requires io.netty.codec.http; - requires io.netty.common; requires io.netty.handler; - requires io.netty.codec; - requires org.slf4j; requires net.woggioni.jwo; requires net.woggioni.rbcs.common; requires net.woggioni.rbcs.api; + requires io.netty.codec.compression; + requires io.netty.transport; + requires io.netty.buffer; + requires io.netty.common; + requires org.slf4j; exports net.woggioni.rbcs.server; diff --git a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/RemoteBuildCacheServer.kt b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/RemoteBuildCacheServer.kt index 3bbd44e..8cb7bde 100644 --- a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/RemoteBuildCacheServer.kt +++ b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/RemoteBuildCacheServer.kt @@ -11,7 +11,8 @@ import io.netty.channel.ChannelInboundHandlerAdapter import io.netty.channel.ChannelInitializer import io.netty.channel.ChannelOption import io.netty.channel.ChannelPromise -import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.MultiThreadIoEventLoopGroup +import io.netty.channel.nio.NioIoHandler import io.netty.channel.socket.DatagramChannel import io.netty.channel.socket.ServerSocketChannel import io.netty.channel.socket.SocketChannel @@ -34,8 +35,25 @@ import io.netty.handler.timeout.IdleState import io.netty.handler.timeout.IdleStateEvent import io.netty.handler.timeout.IdleStateHandler import io.netty.util.AttributeKey -import io.netty.util.concurrent.DefaultEventExecutorGroup import io.netty.util.concurrent.EventExecutorGroup +import java.io.OutputStream +import java.net.InetSocketAddress +import java.nio.file.Files +import java.nio.file.Path +import java.security.PrivateKey +import java.security.cert.X509Certificate +import java.time.Duration +import java.time.Instant +import java.util.Arrays +import java.util.Base64 +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Future +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException +import java.util.regex.Matcher +import java.util.regex.Pattern +import javax.naming.ldap.LdapName +import javax.net.ssl.SSLPeerUnverifiedException import net.woggioni.rbcs.api.AsyncCloseable import net.woggioni.rbcs.api.Configuration import net.woggioni.rbcs.api.exception.ConfigurationException @@ -59,24 +77,6 @@ import net.woggioni.rbcs.server.handler.MaxRequestSizeHandler import net.woggioni.rbcs.server.handler.ServerHandler import net.woggioni.rbcs.server.throttling.BucketManager import net.woggioni.rbcs.server.throttling.ThrottlingHandler -import java.io.OutputStream -import java.net.InetSocketAddress -import java.nio.file.Files -import java.nio.file.Path -import java.security.PrivateKey -import java.security.cert.X509Certificate -import java.time.Duration -import java.time.Instant -import java.util.Arrays -import java.util.Base64 -import java.util.concurrent.CompletableFuture -import java.util.concurrent.Future -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException -import java.util.regex.Matcher -import java.util.regex.Pattern -import javax.naming.ldap.LdapName -import javax.net.ssl.SSLPeerUnverifiedException class RemoteBuildCacheServer(private val cfg: Configuration) { @@ -208,7 +208,6 @@ class RemoteBuildCacheServer(private val cfg: Configuration) { private val cfg: Configuration, private val channelFactory : ChannelFactory, private val datagramChannelFactory : ChannelFactory, - private val eventExecutorGroup: EventExecutorGroup ) : ChannelInitializer(), AsyncCloseable { companion object { @@ -360,7 +359,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) { cacheHandlerFactory.newHandler(cfg, ch.eventLoop(), channelFactory, datagramChannelFactory) } } - pipeline.addLast(eventExecutorGroup, ServerHandler.NAME, serverHandler) + pipeline.addLast(ServerHandler.NAME, serverHandler) pipeline.addLast(ExceptionHandler.NAME, ExceptionHandler) pipeline.addLast(BlackHoleRequestHandler.NAME, BlackHoleRequestHandler()) } @@ -441,20 +440,13 @@ class RemoteBuildCacheServer(private val cfg: Configuration) { fun run(): ServerHandle { // Create the multithreaded event loops for the server - val bossGroup = NioEventLoopGroup(1) + val bossGroup = MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory()) val channelFactory = ChannelFactory { NioSocketChannel() } val datagramChannelFactory = ChannelFactory { NioDatagramChannel() } val serverChannelFactory = ChannelFactory { NioServerSocketChannel() } - val workerGroup = NioEventLoopGroup(0) - val eventExecutorGroup = run { - val threadFactory = if (cfg.eventExecutor.isUseVirtualThreads) { - Thread.ofVirtual().factory() - } else { - null - } - DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors(), threadFactory) - } - val serverInitializer = ServerInitializer(cfg, channelFactory, datagramChannelFactory, workerGroup) + val workerGroup = MultiThreadIoEventLoopGroup(0, NioIoHandler.newFactory()) + + val serverInitializer = ServerInitializer(cfg, channelFactory, datagramChannelFactory) val bootstrap = ServerBootstrap().apply { // Configure the server group(bossGroup, workerGroup) @@ -475,7 +467,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) { return ServerHandle( httpChannel.closeFuture(), bossGroup, - setOf(workerGroup, eventExecutorGroup), + setOf(workerGroup), serverInitializer ) }