diff --git a/bom/pom.xml b/bom/pom.xml index cfd9a3835f8..8b52f7c2885 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -898,6 +898,11 @@ helidon-mp-graal-native-image-extension ${helidon.version} + + io.helidon.integrations.crac + helidon-integrations-crac + ${helidon.version} + io.helidon.integrations.common helidon-integrations-common-rest diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 50416562383..c92a14f2f95 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -163,6 +163,7 @@ 2.16.4 2.12.5 3.5.7 + 1.4.0 @@ -1092,6 +1093,11 @@ reactive-streams-tck ${version.lib.reactivestreams} + + org.crac + crac + ${version.lib.crac} + diff --git a/integrations/crac/pom.xml b/integrations/crac/pom.xml new file mode 100644 index 00000000000..09847a2d89d --- /dev/null +++ b/integrations/crac/pom.xml @@ -0,0 +1,45 @@ + + + + + + io.helidon.integrations + helidon-integrations-project + 4.2.0-SNAPSHOT + + 4.0.0 + + io.helidon.integrations.crac + helidon-integrations-crac + Helidon CRaC support + + + + io.helidon.webserver + helidon-webserver + + + org.crac + crac + + + + \ No newline at end of file diff --git a/integrations/crac/src/main/java/io/helidon/integrations/crac/CracImpl.java b/integrations/crac/src/main/java/io/helidon/integrations/crac/CracImpl.java new file mode 100644 index 00000000000..8380fe66066 --- /dev/null +++ b/integrations/crac/src/main/java/io/helidon/integrations/crac/CracImpl.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 io.helidon.integrations.crac; + +import java.util.HashSet; +import java.util.Set; + +import io.helidon.webserver.crac.spi.Crac; + +import org.crac.management.CRaCMXBean; + +/** + * Helidon abstraction over CRaC API, replaces no-op implementation when this module is present on classpath. + */ +public class CracImpl implements Crac { + + private static final System.Logger LOGGER = System.getLogger(CracImpl.class.getName()); + private static final Set STRONG_REFERENCES = new HashSet<>(); + + @Override + public io.helidon.webserver.crac.spi.Crac.Context getGlobalContext() { + return new HelidonContext(org.crac.Core.getGlobalContext()); + } + + @Override + public long getUptimeSinceRestore() { + return CRaCMXBean.getCRaCMXBean().getUptimeSinceRestore(); + } + + @Override + public long getRestoreTime() { + return CRaCMXBean.getCRaCMXBean().getRestoreTime(); + } + + @Override + public void checkpointRestore() throws RestoreException, CheckpointException { + try { + org.crac.Core.checkpointRestore(); + } catch (org.crac.RestoreException e) { + throw new HelidonRestoreException(e); + } catch (org.crac.CheckpointException e) { + throw new HelidonCheckpointException(e); + } + } + + @Override + public void checkpointRestoreOnStartup() { + if ("onStart".equalsIgnoreCase(System.getProperty("io.helidon.crac.checkpoint"))) { + try { + org.crac.Core.checkpointRestore(); + } catch (UnsupportedOperationException e) { + LOGGER.log(System.Logger.Level.DEBUG, "CRaC feature is not available", e); + } catch (org.crac.RestoreException e) { + LOGGER.log(System.Logger.Level.ERROR, "CRaC restore wasn't successful!", e); + } catch (org.crac.CheckpointException e) { + LOGGER.log(System.Logger.Level.ERROR, "CRaC checkpoint creation wasn't successful!", e); + } + } + } + + private static class HelidonContext extends io.helidon.webserver.crac.spi.Crac.Context { + + private final org.crac.Context delegate; + + HelidonContext(org.crac.Context delegate) { + this.delegate = delegate; + } + + @SuppressWarnings("unchecked") + @Override + public void beforeCheckpoint( + io.helidon.webserver.crac.spi.Crac.Context context) + throws io.helidon.webserver.crac.spi.Crac.CheckpointException { + try { + delegate.beforeCheckpoint(new CracContext((Context) context)); + } catch (org.crac.CheckpointException e) { + throw new HelidonCheckpointException(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public void afterRestore(Context context) throws io.helidon.webserver.crac.spi.Crac.RestoreException { + try { + delegate.afterRestore(new CracContext((Context) context)); + } catch (org.crac.RestoreException e) { + throw new HelidonRestoreException(e); + } + } + + @Override + public void register(Resource resource) { + CracResource resourceRef = new CracResource(resource); + // CRaC API keeps resources as weak refs, we don't want our wrappers being GCed + CracImpl.STRONG_REFERENCES.add(resourceRef); + delegate.register(resourceRef); + } + + } + + private static class HelidonCheckpointException extends io.helidon.webserver.crac.spi.Crac.CheckpointException { + private final org.crac.CheckpointException delegate; + + HelidonCheckpointException(org.crac.CheckpointException delegate) { + this.delegate = delegate; + this.setStackTrace(delegate.getStackTrace()); + } + + @Override + public String getMessage() { + return delegate.getMessage(); + } + + } + + private static class CracCheckpointException extends org.crac.CheckpointException { + private final io.helidon.webserver.crac.spi.Crac.CheckpointException delegate; + + CracCheckpointException(io.helidon.webserver.crac.spi.Crac.CheckpointException delegate) { + this.delegate = delegate; + this.setStackTrace(delegate.getStackTrace()); + } + + @Override + public String getMessage() { + return delegate.getMessage(); + } + + } + + private static class HelidonRestoreException extends io.helidon.webserver.crac.spi.Crac.RestoreException { + private final org.crac.RestoreException delegate; + + HelidonRestoreException(org.crac.RestoreException delegate) { + this.delegate = delegate; + this.setStackTrace(delegate.getStackTrace()); + } + + @Override + public String getMessage() { + return delegate.getMessage(); + } + } + + private static class CracRestoreException extends org.crac.RestoreException { + private final io.helidon.webserver.crac.spi.Crac.RestoreException delegate; + + CracRestoreException(io.helidon.webserver.crac.spi.Crac.RestoreException delegate) { + this.delegate = delegate; + this.setStackTrace(delegate.getStackTrace()); + } + + @Override + public String getMessage() { + return delegate.getMessage(); + } + } + + private static class CracContext extends org.crac.Context { + + private final io.helidon.webserver.crac.spi.Crac.Context delegate; + + CracContext(io.helidon.webserver.crac.spi.Crac.Context delegate) { + this.delegate = delegate; + } + + @SuppressWarnings("unchecked") + @Override + public void beforeCheckpoint(org.crac.Context context) throws org.crac.CheckpointException { + try { + delegate.beforeCheckpoint(new HelidonContext((org.crac.Context) context)); + } catch (CheckpointException e) { + throw new CracCheckpointException(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public void afterRestore(org.crac.Context context) throws org.crac.RestoreException { + try { + delegate.afterRestore(new HelidonContext((org.crac.Context) context)); + } catch (RestoreException e) { + throw new CracRestoreException(e); + } + } + + @Override + public void register(org.crac.Resource resource) { + delegate.register(new HelidonResource(resource)); + } + + } + + private static class CracResource implements org.crac.Resource { + + private final Resource delegate; + + CracResource(Resource delegate) { + this.delegate = delegate; + } + + @SuppressWarnings("unchecked") + @Override + public void beforeCheckpoint(org.crac.Context context) throws Exception { + delegate.beforeCheckpoint(new HelidonContext((org.crac.Context) context)); + } + + @SuppressWarnings("unchecked") + @Override + public void afterRestore(org.crac.Context context) throws Exception { + delegate.afterRestore(new HelidonContext((org.crac.Context) context)); + } + + } + + private static class HelidonResource implements io.helidon.webserver.crac.spi.Crac.Resource { + + private final org.crac.Resource delegate; + + HelidonResource(org.crac.Resource delegate) { + this.delegate = delegate; + } + + @SuppressWarnings("unchecked") + @Override + public void beforeCheckpoint( + io.helidon.webserver.crac.spi.Crac.Context context) + throws Exception { + delegate.beforeCheckpoint(new CracContext((Context) context)); + } + + @SuppressWarnings("unchecked") + @Override + public void afterRestore( + io.helidon.webserver.crac.spi.Crac.Context context) + throws Exception { + delegate.afterRestore(new CracContext((Context) context)); + } + + } +} diff --git a/integrations/crac/src/main/java/io/helidon/integrations/crac/package-info.java b/integrations/crac/src/main/java/io/helidon/integrations/crac/package-info.java new file mode 100644 index 00000000000..66233d56f1d --- /dev/null +++ b/integrations/crac/src/main/java/io/helidon/integrations/crac/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * Helidon abstraction over CRaC API, replaces no-op implementation when this module is present on classpath. + */ +package io.helidon.integrations.crac; diff --git a/integrations/crac/src/main/java/module-info.java b/integrations/crac/src/main/java/module-info.java new file mode 100644 index 00000000000..f9ee851120d --- /dev/null +++ b/integrations/crac/src/main/java/module-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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. + */ + +module io.helidon.integrations.crac { + requires crac; + requires io.helidon.webserver; + exports io.helidon.integrations.crac to io.helidon.webserver; + provides io.helidon.webserver.crac.spi.Crac with io.helidon.integrations.crac.CracImpl; +} \ No newline at end of file diff --git a/integrations/pom.xml b/integrations/pom.xml index a34e5d32a11..3697f4363ff 100644 --- a/integrations/pom.xml +++ b/integrations/pom.xml @@ -42,6 +42,7 @@ common cdi + crac graal db jdbc diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java index a93ac06e6fb..8e12313bbf6 100644 --- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java +++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java @@ -205,7 +205,7 @@ static List createTags(String pairs) { * @return true to include meters related to virtual threads */ @Option.Configured("virtual-threads.enabled") - @Option.DefaultBoolean(true) + @Option.DefaultBoolean(false) boolean virtualThreadsEnabled(); /** diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java index 445b3065d1b..6378d705ca8 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024 Oracle and/or its affiliates. + * Copyright (c) 2018, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package io.helidon.microprofile.server; import java.lang.System.Logger.Level; -import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.file.Path; @@ -29,7 +28,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; @@ -49,6 +47,7 @@ import io.helidon.webserver.WebServer; import io.helidon.webserver.WebServerConfig; import io.helidon.webserver.context.ContextFeature; +import io.helidon.webserver.crac.spi.Crac; import io.helidon.webserver.http.HttpRouting; import io.helidon.webserver.http.HttpService; import io.helidon.webserver.http.ServerRequest; @@ -80,10 +79,6 @@ import jakarta.enterprise.inject.spi.ProcessProducerMethod; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.ext.ParamConverterProvider; -import org.crac.CheckpointException; -import org.crac.Core; -import org.crac.Resource; -import org.crac.RestoreException; import org.eclipse.microprofile.config.ConfigProvider; import org.glassfish.jersey.internal.inject.Bindings; import org.glassfish.jersey.internal.inject.InjectionManager; @@ -97,7 +92,7 @@ /** * Extension to handle web server configuration and lifecycle. */ -public class ServerCdiExtension implements Extension, Resource { +public class ServerCdiExtension implements Extension { private static final System.Logger LOGGER = System.getLogger(ServerCdiExtension.class.getName()); private static final System.Logger STARTUP_LOGGER = System.getLogger("io.helidon.microprofile.startup.server"); private static final AtomicBoolean IN_PROGRESS_OR_RUNNING = new AtomicBoolean(); @@ -125,10 +120,6 @@ public class ServerCdiExtension implements Extension, Resource { private Context context; - private long cracRestoreTime = -1; - private final CompletableFuture> restored = new CompletableFuture<>(); - - /** * Default constructor required by {@link java.util.ServiceLoader}. */ @@ -470,18 +461,6 @@ private void startServer(@Observes @Priority(PLATFORM_AFTER + 100) @Initialized( } webserver = serverBuilder.build(); - if ("onStart".equalsIgnoreCase(System.getProperty("io.helidon.crac.checkpoint"))) { - try { - Core.checkpointRestore(); - } catch (UnsupportedOperationException e) { - LOGGER.log(Level.DEBUG, "CRaC feature is not available", e); - } catch (RestoreException e) { - LOGGER.log(Level.ERROR, "CRaC restore wasn't successful!", e); - } catch (CheckpointException e) { - LOGGER.log(Level.ERROR, "CRaC checkpoint creation wasn't successful!", e); - } - } - try { webserver.start(); started = true; @@ -491,20 +470,16 @@ private void startServer(@Observes @Priority(PLATFORM_AFTER + 100) @Initialized( this.port = webserver.port(); - long initializationElapsedTime = ManagementFactory.getRuntimeMXBean().getUptime(); + Crac.get().checkpointRestoreOnStartup(); + long initializationElapsedTime = Crac.get().getUptime(); String protocol = "http" + (webserver.hasTls() ? "s" : ""); String host = "0.0.0.0".equals(listenHost) ? "localhost" : listenHost; String note = "0.0.0.0".equals(listenHost) ? " (and all other host addresses)" : ""; - - String startupTimeReport = cracRestoreTime == -1 - ? " in " + initializationElapsedTime + " milliseconds (since JVM startup). " - : " in " + (System.currentTimeMillis() - cracRestoreTime) + " milliseconds (since CRaC restore)."; - LOGGER.log(Level.INFO, () -> "Server started on " + protocol + "://" + host + ":" + port - + note + startupTimeReport); + + note + " in " + initializationElapsedTime + " milliseconds (since JVM startup)."); // this is not needed at runtime, collect garbage serverBuilder = null; @@ -783,17 +758,4 @@ private HttpRouting.Builder findRouting(String className, return serverNamedRoutingBuilder(routingName); } - - @Override - public void beforeCheckpoint(org.crac.Context context) throws Exception { - LOGGER.log(Level.INFO, "Creating CRaC snapshot after " - + ManagementFactory.getRuntimeMXBean().getUptime() - + "ms of runtime."); - } - - @Override - public void afterRestore(org.crac.Context context) throws Exception { - cracRestoreTime = System.currentTimeMillis(); - LOGGER.log(Level.INFO, "CRaC snapshot restored!"); - } } diff --git a/microprofile/server/src/main/java/module-info.java b/microprofile/server/src/main/java/module-info.java index 161737fc3db..35f61eae80d 100644 --- a/microprofile/server/src/main/java/module-info.java +++ b/microprofile/server/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024 Oracle and/or its affiliates. + * Copyright (c) 2018, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/webserver/webserver/pom.xml b/webserver/webserver/pom.xml index d20da117294..f7b2e4652d2 100644 --- a/webserver/webserver/pom.xml +++ b/webserver/webserver/pom.xml @@ -1,6 +1,6 @@