Skip to content

Commit

Permalink
🐛 Can build native image again. Don't use virtual threads on native i…
Browse files Browse the repository at this point in the history
…mage, since GraalVM still doesn't support it.
  • Loading branch information
ujibang committed Mar 15, 2024
1 parent 9238602 commit 4254c28
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
* @param <V> the class of the values (is Optional-ized).
*/
public class CaffeineLoadingCache<K, V> implements org.restheart.cache.LoadingCache<K, V> {
private static final Executor newVirtualThreadPerTaskExecutor = ThreadsUtils.virtualThreadsExecutor();
private static final Executor newVirtualThreadPerTaskExecutor = ThreadsUtils.threadsExecutor();
private final AsyncLoadingCache<K, Optional<V>> wrapped;

public CaffeineLoadingCache(long size, EXPIRE_POLICY expirePolicy, long ttl, Function<K, V> loader) {
Expand Down
45 changes: 30 additions & 15 deletions commons/src/main/java/org/restheart/configuration/CoreModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import java.util.List;
import java.util.Map;

import org.restheart.graal.ImageInfo;

import static org.restheart.configuration.Utils.asMap;
import static org.restheart.configuration.Utils.getOrDefault;

Expand Down Expand Up @@ -51,24 +53,37 @@ public record CoreModule(String name,
public static final String FORCE_GZIP_ENCODING_KEY = "force-gzip-encoding";
public static final String ALLOW_UNESCAPED_CHARS_IN_ULR_KEY = "allow-unescaped-characters-in-url";

private static final CoreModule DEFAULT_CORE_MODULE = new CoreModule("default", "plugins", new ArrayList<>(), false, null, 0, -1, 16364, true, false, true);
private static final CoreModule DEFAULT_CORE_MODULE = new CoreModule("default", "plugins", new ArrayList<>(), false, null, 0, 0, 16364, true, false, true);

public CoreModule(Map<String, Object> conf, boolean silent) {
this(
getOrDefault(conf, INSTANCE_NAME_KEY, DEFAULT_CORE_MODULE.name(), silent),
getOrDefault(conf, PLUGINS_DIRECTORY_PATH_KEY, DEFAULT_CORE_MODULE.pluginsDirectory(), silent),
// following is optional, so get it always in silent mode
getOrDefault(conf, PLUGINS_PACKAGES_KEY, DEFAULT_CORE_MODULE.pluginsPackages(), true),
getOrDefault(conf, PLUGINS_SCANNING_VERBOSE_KEY, false, true),
getOrDefault(conf, BASE_URL_KEY, DEFAULT_CORE_MODULE.baseUrl(), true),
getOrDefault(conf, IO_THREADS_KEY, DEFAULT_CORE_MODULE.ioThreads(), silent),
getOrDefault(conf, WORKER_THREADS_KEY, DEFAULT_CORE_MODULE.workerThreads(), silent),
getOrDefault(conf, BUFFER_SIZE_KEY, DEFAULT_CORE_MODULE.bufferSize(), silent),
getOrDefault(conf, DIRECT_BUFFERS_KEY, DEFAULT_CORE_MODULE.directBuffers(), silent),
// following is optional, so get it always in silent mode
getOrDefault(conf, FORCE_GZIP_ENCODING_KEY, DEFAULT_CORE_MODULE.forceGzipEncoding(), true),
// following is optional, so get it always in silent mode
getOrDefault(conf, ALLOW_UNESCAPED_CHARS_IN_ULR_KEY, DEFAULT_CORE_MODULE.allowUnescapedCharsInUrl(), true));
getOrDefault(conf, INSTANCE_NAME_KEY, DEFAULT_CORE_MODULE.name(), silent),
getOrDefault(conf, PLUGINS_DIRECTORY_PATH_KEY, DEFAULT_CORE_MODULE.pluginsDirectory(), silent),
// following is optional, so get it always in silent mode
getOrDefault(conf, PLUGINS_PACKAGES_KEY, DEFAULT_CORE_MODULE.pluginsPackages(), true),
getOrDefault(conf, PLUGINS_SCANNING_VERBOSE_KEY, false, true),
getOrDefault(conf, BASE_URL_KEY, DEFAULT_CORE_MODULE.baseUrl(), true),
getOrDefault(conf, IO_THREADS_KEY, DEFAULT_CORE_MODULE.ioThreads(), silent),
workerThreads(conf),
getOrDefault(conf, BUFFER_SIZE_KEY, DEFAULT_CORE_MODULE.bufferSize(), silent),
getOrDefault(conf, DIRECT_BUFFERS_KEY, DEFAULT_CORE_MODULE.directBuffers(), silent),
// following is optional, so get it always in silent mode
getOrDefault(conf, FORCE_GZIP_ENCODING_KEY, DEFAULT_CORE_MODULE.forceGzipEncoding(), true),
// following is optional, so get it always in silent mode
getOrDefault(conf, ALLOW_UNESCAPED_CHARS_IN_ULR_KEY, DEFAULT_CORE_MODULE.allowUnescapedCharsInUrl(), true));
}

/**
* native image doesn't support Virtual Threads
* so workerThreads cannot be 0
* @param conf
*/
private static int workerThreads(Map<String, Object> conf) {
int workerThreads = getOrDefault(conf, WORKER_THREADS_KEY, DEFAULT_CORE_MODULE.workerThreads(), true);

return workerThreads == 0 && ImageInfo.inImageCode()
? -1 // this is autodetect
: workerThreads;
}

public static CoreModule build(Map<String, Object> conf, boolean silent) {
Expand Down
15 changes: 12 additions & 3 deletions commons/src/main/java/org/restheart/utils/ThreadsUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,20 @@

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.restheart.graal.ImageInfo;

public class ThreadsUtils {
private static final Executor VIRTUAL_THREAD_EXECUTOR = Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("RH-VIRTUAL-WORKER-", 0L).factory());
private static final Executor THREAD_EXECUTOR;

public static Executor virtualThreadsExecutor() {
return VIRTUAL_THREAD_EXECUTOR;
static {
if(ImageInfo.inImageCode()) {
THREAD_EXECUTOR = Executors.newWorkStealingPool();
} else {
THREAD_EXECUTOR = Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("RH-VIRTUAL-WORKER-", 0L).factory());
}
}

public static Executor threadsExecutor() {
return THREAD_EXECUTOR;
}
}
44 changes: 28 additions & 16 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,31 @@
<artifactId>js-community</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.graalvm.truffle</groupId>
<artifactId>truffle-compiler</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.graalvm.truffle</groupId>
<artifactId>truffle-runtime</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>nativeimage</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>collections</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>word</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
Expand Down Expand Up @@ -232,19 +257,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<artifactSet>
<!-- Following exclusions fix native-image errors such
Error: Class-path entry file:restheart.jar contains class com.oracle.svm.core.annotate.TargetElement.
This class is part of the image builder itself (in jrt:/org.graalvm.nativeimage) and must not be passed via -cp.
This can be caused by a fat-jar that illegally includes svm.jar (or graal-sdk.jar) due to its build-time dependency on it.
-->
<excludes>
<exclude>org.graalvm.sdk:nativeimage</exclude>
<exclude>org.graalvm.truffle:truffle-compiler</exclude>
<exclude>org.graalvm.sdk:collections</exclude>
<exclude>org.graalvm.sdk:word</exclude>
</excludes>
</artifactSet>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
Expand Down Expand Up @@ -284,9 +296,9 @@
<filter>
<artifact>*:*</artifact>
<excludes>
<!-- this fixes native-image build error about module
Error: Processing jar:file:restheart.jar!/META-INF/native-image/org.graalvm.jniutils/native-image.properties failed
Caused by: com.oracle.svm.driver.NativeImage$NativeImageError: Failed to process ForceOnModulePath attribute: No module descriptor was not found in class-path entry: restheart/core/target/restheart.jar.
<!-- this fixes native-image build errors such:
Error: Processing jar:file:///...restheart.jar!/META-INF/native-image/org.graalvm.jniutils/native-image.properties failed
Caused by: com.oracle.svm.driver.NativeImage$NativeImageError: Failed to process ForceOnModulePath attribute: No module descriptor was not found in class-path entry: /.../restheart/core/target/restheart.jar
-->
<exclude>META-INF/native-image/org.restheart/restheart-commons/native-image.properties</exclude>
<exclude>META-INF/native-image/org.mongodb/bson/native-image.properties</exclude>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ private void executeAsyncResponseInterceptor(HttpServerExchange exchange, Servic
return false;
}
})
.forEachOrdered(ri -> ThreadsUtils.virtualThreadsExecutor().execute(() -> {
.forEachOrdered(ri -> ThreadsUtils.threadsExecutor().execute(() -> {
LOGGER.debug("Executing interceptor {} for {} on intercept point {}", PluginUtils.name(ri), exchange.getRequestPath(), InterceptPoint.RESPONSE_ASYNC);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@

import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.BlockingHandler;
import org.restheart.graal.ImageInfo;

/**
*
* @author Andrea Di Cesare {@literal <[email protected]>}
*
* Dispatches the execution of the service to the Working Thread Pool
*
* Unless running as a native image, it uses Virtual Threads Executor
*
* This applies to Services annotated
* with @RegisterPlugin(blocking=true)
*
Expand All @@ -43,7 +46,14 @@
*/
public class WorkingThreadsPoolDispatcher extends PipelinedHandler {
private final BlockingHandler blockingHandler = new BlockingHandler(this);
private static final Executor virtualThreadsExecutor = ThreadsUtils.virtualThreadsExecutor();
private static final Executor virtualThreadsExecutor;

static {
// As GraalVM version 23.1.2 Virtual threads are not supported together with Truffle JIT compilation.
virtualThreadsExecutor = ImageInfo.inImageCode()
? null
: ThreadsUtils.threadsExecutor();
}

/**
* Creates a new instance of WorkingThreadsPoolDispatcher
Expand All @@ -69,7 +79,9 @@ public WorkingThreadsPoolDispatcher(PipelinedHandler next) {
*/
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.setDispatchExecutor(virtualThreadsExecutor);
if (virtualThreadsExecutor != null) {
exchange.setDispatchExecutor(virtualThreadsExecutor);
}

if (exchange.isInIoThread()) {
blockingHandler.handleRequest(exchange);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ Args = --initialize-at-build-time=org.restheart.plugins.PluginsScanner,io.github
--install-exit-handlers \
--features=org.restheart.graal.PluginsReflectionRegistrationFeature \
--add-exports=java.net.http/jdk.internal.net.http=org.graalvm.truffle \
-march=native
# -Ob
-march=native \
--enable-preview \
-Ob

# --gc=G1 G1 can only be used in native images that are built on Linux for AMD64 with GraalVM Oracle
# --add-exports=java.net.http allows using java.net.http in JavaScript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,10 @@
"name":"org.graalvm.polyglot.Engine",
"methods":[{"name":"getImpl","parameterTypes":[] }]
},
{
"name":"org.graalvm.nativeimage.ImageInfo",
"allPublicMethods":true
},
{
"name":"org.graalvm.polyglot.Value"
},
Expand Down Expand Up @@ -1382,6 +1386,12 @@
"name":"org.restheart.cache.impl.CaffeineLoadingCache$1",
"methods":[{"name":"loadAll","parameterTypes":["java.util.Set"] }]
},
{
"name":"com.github.benmanes.caffeine.cache.PSWMW",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true
},
{
"name":"org.restheart.configuration.Configuration"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,9 @@ core:
io-threads: 0

# Number of threads created for blocking tasks (such as ones involving db access). Suggested value: core*8
# if < 0, use the number of cores * 8. With 0 working threads, blocking services won't work.
worker-threads: -1
# if < 0, use the number of cores * 8.
# This option is only used by restheart native image. In other cases requests are handled by the virtual thread executor.
worker-threads: 0

# Use 16k buffers for best performance - as in linux 16k is generally the default amount of data that can be sent in a single write() call
# Setting to 1024 * 16 - 20; the 20 is to allow some space for getProtocol headers, see UNDERTOW-1209
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/resources/restheart-default-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,9 @@ core:
io-threads: 0

# Number of threads created for blocking tasks (such as ones involving db access). Suggested value: core*8
# if < 0, use the number of cores * 8. With 0 working threads, blocking services won't work.
worker-threads: -1
# if < 0, use the number of cores * 8.
# This option is only used by restheart native image. In other cases requests are handled by the virtual thread executor.
worker-threads: 0

# Use 16k buffers for best performance - as in linux 16k is generally the default amount of data that can be sent in a single write() call
# Setting to 1024 * 16 - 20; the 20 is to allow some space for getProtocol headers, see UNDERTOW-1209
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ private void _changeStreamEventsLoop() {

var msg = BsonUtils.toJson(getDocument(changeEvent), key.getJsonMode());

this.websocketSessions.stream().forEach(session -> ThreadsUtils.virtualThreadsExecutor().execute(() -> {
this.websocketSessions.stream().forEach(session -> ThreadsUtils.threadsExecutor().execute(() -> {
try {
this.send(session, msg);
LOGGER.trace("Change event sent to WebSocket session {}", session.getId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ private synchronized void initChangeStreamWorker(HttpServerExchange exchange) th

ChangeStreamWorkers.getInstance().put(changeStreamWorker);

ThreadsUtils.virtualThreadsExecutor().execute(changeStreamWorker);
ThreadsUtils.threadsExecutor().execute(changeStreamWorker);

LOGGER.debug("Started Change Stream Worker, {}", csKey);
} else {
Expand Down
48 changes: 47 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.165</version>
<version>4.8.168</version>
</dependency>
<dependency>
<groupId>org.graalvm.polyglot</groupId>
Expand All @@ -361,6 +361,52 @@
<version>${graalvm.version}</version>
<type>pom</type>
</dependency>
<!-- The following transitive dependencies of org.graalvm.polyglot artifacts
are overridden with scope=provided to not include them in restheart jars
This leads to errors in building native images
Error: Class-path entry file:///.../restheart.jar contains class com.oracle.svm.core.annotate.TargetElement.
his class is part of the image builder itself (in jrt:/org.graalvm.nativeimage) and must not be passed via -cp.
And the following warning form GraalVM JVM
WARNING: The polyglot engine uses a fallback runtime that does not support runtime compilation to native code.
Execution without runtime compilation will negatively impact the guest application performance.
The following cause was found: No optimizing Truffle runtime found on the module or class-path.-->
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>nativeimage</artifactId>
<version>${graalvm.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.graalvm.truffle</groupId>
<artifactId>truffle-compiler</artifactId>
<version>${graalvm.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.graalvm.truffle</groupId>
<artifactId>truffle-runtime</artifactId>
<version>${graalvm.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>nativeimage</artifactId>
<version>${graalvm.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>collections</artifactId>
<version>${graalvm.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>word</artifactId>
<version>${graalvm.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
Expand Down

0 comments on commit 4254c28

Please sign in to comment.