diff --git a/nucleus/admin/config-api/src/main/java/org/glassfish/config/support/TranslatedConfigView.java b/nucleus/admin/config-api/src/main/java/org/glassfish/config/support/TranslatedConfigView.java index e5415e552f9..f3b108a536d 100644 --- a/nucleus/admin/config-api/src/main/java/org/glassfish/config/support/TranslatedConfigView.java +++ b/nucleus/admin/config-api/src/main/java/org/glassfish/config/support/TranslatedConfigView.java @@ -101,7 +101,6 @@ public static Object getTranslatedValue(Object value) { } } } - // Perform property substitution in the value // The loop limit is imposed to prevent infinite looping to values // such as a=${a} or a=foo ${b} and b=bar {$a} diff --git a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java index c26ef2b65a8..bf8c1965efb 100644 --- a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java +++ b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/AutoDisposableGlassFish.java @@ -24,6 +24,7 @@ import java.lang.System.Logger.Level; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.Set; @@ -63,6 +64,7 @@ class AutoDisposableGlassFish extends GlassFishImpl { // If there are custom configurations like http.port, https.port, jmx.port then configure them. CommandRunner commandRunner = null; Set knownPropertyPrefixes = new HashSet<>(); + ArrayList configPropertiesToSet = new ArrayList<>(); for (String key : gfProps.getPropertyNames()) { String propertyName = key; if (key.startsWith(GENERAL_CONFIG_PROP_PREFIX)) { @@ -92,10 +94,12 @@ class AutoDisposableGlassFish extends GlassFishImpl { continue; } } - CommandResult result = commandRunner.run("set", propertyName + "=" + propertyValue); - if (result.getExitStatus() != CommandResult.ExitStatus.SUCCESS) { - throw new GlassFishException(result.getOutput(), result.getFailureCause()); - } + configPropertiesToSet.add(propertyName + "=" + propertyValue); + } + + CommandResult result = commandRunner.run("set", configPropertiesToSet.toArray(String[]::new)); + if (result.getExitStatus() != CommandResult.ExitStatus.SUCCESS) { + throw new GlassFishException(result.getOutput(), result.getFailureCause()); } } diff --git a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java index 2c3f564a05e..bfb36e074b1 100644 --- a/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java +++ b/nucleus/core/bootstrap-osgi/src/main/java/org/glassfish/main/boot/embedded/EmbeddedGlassFishRuntime.java @@ -23,6 +23,7 @@ import com.sun.enterprise.module.bootstrap.ModuleStartup; import com.sun.enterprise.module.bootstrap.StartupContext; import com.sun.enterprise.module.common_impl.AbstractFactory; +import com.sun.enterprise.util.Utility; import java.io.File; import java.io.FileOutputStream; @@ -35,15 +36,20 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ForkJoinPool; import java.util.function.Consumer; import java.util.logging.Logger; +import org.glassfish.embeddable.CommandRunner; import org.glassfish.embeddable.GlassFish; import org.glassfish.embeddable.GlassFishException; import org.glassfish.embeddable.GlassFishProperties; import org.glassfish.embeddable.GlassFishRuntime; +import org.glassfish.hk2.api.ActiveDescriptor; import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.utilities.BuilderHelper; import org.glassfish.hk2.utilities.DuplicatePostProcessor; import org.glassfish.main.boot.log.LogFacade; @@ -89,18 +95,25 @@ public synchronized GlassFish newGlassFish(GlassFishProperties glassFishProperti properties.putAll(glassFishProperties.getProperties()); final GlassFishProperties gfProps = new GlassFishProperties(properties); - setEnv(gfProps); + final CompletableFuture setEnvFuture = setEnv(gfProps); final StartupContext startupContext = new StartupContext(gfProps.getProperties()); final ModulesRegistry modulesRegistry = AbstractFactory.getInstance().createModulesRegistry(); + // Building the serviceLocator takes about 600ms, around 1/5 final ServiceLocator serviceLocator = main.createServiceLocator(modulesRegistry, startupContext, List.of(new EmbeddedInhabitantsParser(), new DuplicatePostProcessor()), null); + preloadEssentialServicesInBackground(serviceLocator); + final ModuleStartup gfKernel = main.findStartupService(modulesRegistry, serviceLocator, null, startupContext); final Consumer onDispose = gf -> glassFishInstances.remove(gfProps.getInstanceRoot()); - final GlassFish glassFish = new AutoDisposableGlassFish(gfKernel, serviceLocator, gfProps, onDispose); + + // We need to complete the setup before creating AutoDisposableGlassFish + setEnvFuture.join(); + + final GlassFish glassFish = new AutoDisposableGlassFish(gfKernel, serviceLocator, gfProps, onDispose); // takes 900ms glassFishInstances.put(gfProps.getInstanceRoot(), glassFish); return glassFish; @@ -111,6 +124,35 @@ public synchronized GlassFish newGlassFish(GlassFishProperties glassFishProperti } } + // Preloads some services in background. They will always be needed during startup. + // Instead of loading them lazily in the main thread, we load them immediately in background so that they are loaded sooner + private void preloadEssentialServicesInBackground(final ServiceLocator serviceLocator) { + // Preload command runner. AutoDisposableGlassFish always needs to run it + // - first load preloads all the singleton dependencies which take about 500ms to initialize. + // It's typically not loaded in time before it's needed on the main thread but it still decreases + // the time the main thread needs to wait until the initialization completes by about 2/3, to about 150ms + runAsynchWithThreadContext(() -> serviceLocator.getService(CommandRunner.class), Thread.currentThread()); + + // Preload the service for the set command (name="set") in a background thread. + // AutoDisposableGlassFish always needs to run it so it can retrieve it faster + // - we load the singleton GetSetModularityHelper dependency heere eagerly because it takes about 250ms to initialize + // Again, it doesn't load completely in time before it's needed on the main thread but decreases the wait time on the main to about 80ms + runAsynchWithThreadContext( + () -> { + final ActiveDescriptor setCommandDescriptor = serviceLocator.getBestDescriptor(BuilderHelper.createNameFilter("set")); + serviceLocator.getServiceHandle(setCommandDescriptor) + .getService(); + }, + Thread.currentThread()); + } + + private static void runAsynchWithThreadContext(Utility.RunnableWithException action, Thread spawningThread) { + final ClassLoader contextClassLoader = spawningThread.getContextClassLoader(); + ForkJoinPool.commonPool().submit(() -> { + Utility.runWithContextClassLoader(contextClassLoader, action); + }); + } + @Override public synchronized void shutdown() throws GlassFishException { for (GlassFish glassFish : glassFishInstances.values()) { @@ -130,7 +172,15 @@ public synchronized void shutdown() throws GlassFishException { } } - private void setEnv(GlassFishProperties gfProps) throws Exception { + /* + Unpacks the domain directory and sets up the properties related to the environment + + Asynchronously completes tasks that aren't essential during startup to move them from the main thread. + The returned future should be joined before creating AutoDisposableGlassFish, which applies properties + that might depend on having the environment completely set up. + */ + private CompletableFuture setEnv(GlassFishProperties gfProps) throws Exception { + CompletableFuture asyncResult = CompletableFuture.completedFuture(null); String instanceRootValue = gfProps.getInstanceRoot(); if (instanceRootValue == null) { instanceRootValue = createTempInstanceRoot(gfProps); @@ -145,7 +195,13 @@ private void setEnv(GlassFishProperties gfProps) throws Exception { if (installRootValue == null) { installRootValue = instanceRoot.getAbsolutePath(); gfProps.setProperty("-type", "EMBEDDED"); - JarUtil.extractRars(installRootValue); + final String installRootFinalValue = installRootValue; + final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + asyncResult = asyncResult.thenRunAsync( + () -> { + Utility.runWithContextClassLoader(contextClassLoader, + () -> JarUtil.extractRars(installRootFinalValue)); + }); } JarUtil.setEnv(installRootValue); @@ -159,6 +215,8 @@ private void setEnv(GlassFishProperties gfProps) throws Exception { // StartupContext requires the installRoot to be set in the GlassFishProperties. gfProps.setProperty(INSTALL_ROOT.getPropertyName(), installRoot.getAbsolutePath()); gfProps.setProperty(BootstrapKeys.INSTALL_ROOT_URI_PROP_NAME, installRoot.toURI().toString()); + + return asyncResult; } private String createTempInstanceRoot(GlassFishProperties gfProps) throws Exception {