From 9a3cf6732b7b42d19e113bce32c799b283b8f79b Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sun, 5 May 2024 18:52:48 +0200 Subject: [PATCH] Simplify multi-runtime --- org.argeo.init/bnd.bnd | 2 +- .../org/argeo/api/init/RuntimeManager.java | 16 ++ .../org/argeo/init/RuntimeManagerMain.java | 90 ++-------- .../{Activator.java => InitActivator.java} | 16 +- .../argeo/init/osgi/OsgiRuntimeManager.java | 154 ++++++++++++++++++ .../init/osgi/SubFrameworkActivator.java | 2 +- 6 files changed, 198 insertions(+), 82 deletions(-) rename org.argeo.init/src/org/argeo/init/osgi/{Activator.java => InitActivator.java} (76%) create mode 100644 org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java diff --git a/org.argeo.init/bnd.bnd b/org.argeo.init/bnd.bnd index c207cb8f1..5a44c0da0 100644 --- a/org.argeo.init/bnd.bnd +++ b/org.argeo.init/bnd.bnd @@ -1,7 +1,7 @@ Main-Class: org.argeo.init.Service Class-Path: org.eclipse.osgi.jar -Bundle-Activator: org.argeo.init.osgi.Activator +Bundle-Activator: org.argeo.init.osgi.InitActivator Import-Package: \ org.osgi.*;version=0.0.0,\ diff --git a/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java b/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java index cb8caeda9..302237f5e 100644 --- a/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java +++ b/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java @@ -2,6 +2,8 @@ package org.argeo.api.init; import java.io.IOException; import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; @@ -19,6 +21,20 @@ public interface RuntimeManager { public void closeRuntime(String relPath, boolean async); + default void startRuntime(String relPath, String props) { + Properties properties = new Properties(); + try (Reader reader = new StringReader(props)) { + properties.load(reader); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties", e); + } + startRuntime(relPath, (config) -> { + for (Object key : properties.keySet()) { + config.put(key.toString(), properties.getProperty(key.toString())); + } + }); + } + /** * Load configs recursively starting with the parent directories, until a * jvm.args file is found. diff --git a/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java b/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java index f4ed507c0..d092242f7 100644 --- a/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java +++ b/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java @@ -28,7 +28,7 @@ import org.osgi.framework.launch.Framework; * Dynamically configures and launches multiple runtimes, coordinated by a main * one. */ -public class RuntimeManagerMain implements RuntimeManager { +public class RuntimeManagerMain { private final static Logger logger = System.getLogger(RuntimeManagerMain.class.getName()); private final static String ENV_STATE_DIRECTORY = "STATE_DIRECTORY"; @@ -41,14 +41,17 @@ public class RuntimeManagerMain implements RuntimeManager { private Path baseWritableArea; private Map configuration = new HashMap<>(); - private Map runtimeContexts = new TreeMap<>(); - RuntimeManagerMain(Path configArea, Path stateArea) { RuntimeManager.loadConfig(configArea, configuration); - configuration.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, stateArea.resolve(STATE).toUri().toString()); + + // integration with OSGi runtime; this will be read by the init bundle + configuration.put(ServiceMain.PROP_ARGEO_INIT_MAIN, "true"); + configuration.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea.toUri().toString()); + + configuration.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, stateArea.resolve(RuntimeManager.STATE).toUri().toString()); // use config area if instance area is not set if (!configuration.containsKey(InitConstants.PROP_OSGI_INSTANCE_AREA)) - configuration.put(InitConstants.PROP_OSGI_INSTANCE_AREA, stateArea.resolve(DATA).toUri().toString()); + configuration.put(InitConstants.PROP_OSGI_INSTANCE_AREA, stateArea.resolve(RuntimeManager.DATA).toUri().toString()); this.baseConfigArea = configArea.getParent(); this.baseWritableArea = stateArea.getParent(); @@ -66,10 +69,10 @@ public class RuntimeManagerMain implements RuntimeManager { // shutdown on exit Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdown(), "Runtime shutdown")); - BundleContext bc = managerRuntimeContext.getFramework().getBundleContext(); - // uninstall init as a bundle since it will be available via OSGi system - OsgiBoot.uninstallBundles(bc, SYMBOLIC_NAME_INIT); - bc.registerService(RuntimeManager.class, this, new Hashtable<>(configuration)); +// BundleContext bc = managerRuntimeContext.getFramework().getBundleContext(); +// // uninstall init as a bundle since it will be available via OSGi system +// OsgiBoot.uninstallBundles(bc, SYMBOLIC_NAME_INIT); +// bc.registerService(RuntimeManager.class, this, new Hashtable<>(configuration)); logger.log(Level.DEBUG, "Registered runtime manager"); managerRuntimeContext.waitForStop(0); @@ -81,21 +84,6 @@ public class RuntimeManagerMain implements RuntimeManager { } protected void shutdown() { - // shutdowm runtimes - Map shutdowning = new HashMap<>(runtimeContexts); - for (String id : new HashSet<>(runtimeContexts.keySet())) { - logger.log(Logger.Level.DEBUG, "Shutting down runtime " + id + " ..."); - closeRuntime(id, true); - } - for (String id : shutdowning.keySet()) - try { - RuntimeContext runtimeContext = shutdowning.get(id); - runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); - } catch (InterruptedException e) { - // silent - } catch (Exception e) { - logger.log(Logger.Level.DEBUG, "Cannot wait for " + id + " to shutdown", e); - } // shutdown manager runtime try { InternalState.getMainRuntimeContext().close(); @@ -108,60 +96,6 @@ public class RuntimeManagerMain implements RuntimeManager { } } - OsgiRuntimeContext loadRuntime(String relPath, Consumer> configCallback) { - closeRuntime(relPath, false); - Path writableArea = baseWritableArea.resolve(relPath); - Path configArea = baseConfigArea.resolve(relPath); - Map config = new HashMap<>(); - RuntimeManager.loadConfig(configArea, config); - config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, writableArea.resolve(STATE).toUri().toString()); - - if (configCallback != null) - configCallback.accept(config); - - // use config area if instance area is not set - if (!config.containsKey(InitConstants.PROP_OSGI_INSTANCE_AREA)) - config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, writableArea.resolve(DATA).toUri().toString()); - - OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(config); - runtimeContexts.put(relPath, runtimeContext); - return runtimeContext; - } - - public void startRuntime(String relPath, Consumer> configCallback) { - OsgiRuntimeContext runtimeContext = loadRuntime(relPath, configCallback); - runtimeContext.run(); - Framework framework = runtimeContext.getFramework(); - if (framework != null) {// in case the framework has closed very quickly after run - framework.getBundleContext().addFrameworkListener((e) -> { - if (e.getType() >= FrameworkEvent.STOPPED) { - logger.log(Level.DEBUG, "Externally stopped runtime " + relPath + ". Unregistering...", e); - runtimeContexts.remove(relPath); - } - }); - } else { - closeRuntime(relPath, false); - } - } - - public void closeRuntime(String relPath, boolean async) { - if (!runtimeContexts.containsKey(relPath)) - return; - RuntimeContext runtimeContext = runtimeContexts.get(relPath); - try { - runtimeContext.close(); - if (!async) { - runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); - System.gc(); - } - } catch (Exception e) { - logger.log(Level.ERROR, "Cannot close runtime context " + relPath, e); - } finally { - runtimeContexts.remove(relPath); - } - - } - public static void main(String[] args) { ThinLoggerFinder.reloadConfiguration(); logger.log(Logger.Level.DEBUG, () -> "Argeo Init starting with PID " + ProcessHandle.current().pid()); diff --git a/org.argeo.init/src/org/argeo/init/osgi/Activator.java b/org.argeo.init/src/org/argeo/init/osgi/InitActivator.java similarity index 76% rename from org.argeo.init/src/org/argeo/init/osgi/Activator.java rename to org.argeo.init/src/org/argeo/init/osgi/InitActivator.java index 057c21786..310bce70e 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/Activator.java +++ b/org.argeo.init/src/org/argeo/init/osgi/InitActivator.java @@ -4,6 +4,7 @@ import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.util.Objects; +import org.argeo.api.init.RuntimeManager; import org.argeo.init.ServiceMain; import org.argeo.init.logging.ThinLoggerFinder; import org.osgi.framework.BundleActivator; @@ -14,12 +15,12 @@ import org.osgi.framework.BundleContext; * http: * //wiki.eclipse.org/Configurator */ -public class Activator implements BundleActivator { +public class InitActivator implements BundleActivator { static { // must be called first ThinLoggerFinder.lazyInit(); } - private final static Logger logger = System.getLogger(Activator.class.getName()); + private final static Logger logger = System.getLogger(InitActivator.class.getName()); private Long checkpoint = null; @@ -27,6 +28,8 @@ public class Activator implements BundleActivator { /** Not null if we created it ourselves. */ private OsgiRuntimeContext runtimeContext; + private static OsgiRuntimeManager runtimeManager; + public void start(final BundleContext bundleContext) throws Exception { // The OSGi runtime was created by us, and therefore already initialized argeoInit = Boolean.parseBoolean(bundleContext.getProperty(ServiceMain.PROP_ARGEO_INIT_MAIN)); @@ -50,6 +53,10 @@ public class Activator implements BundleActivator { checkpoint = System.currentTimeMillis(); } } + + if (runtimeManager != null) + throw new IllegalArgumentException("Runtime manager is already set"); + runtimeManager = new OsgiRuntimeManager(bundleContext); } public void stop(BundleContext context) throws Exception { @@ -58,6 +65,11 @@ public class Activator implements BundleActivator { runtimeContext.stop(context); runtimeContext = null; } + runtimeManager = null; + } + + public static RuntimeManager getRuntimeManager() { + return runtimeManager; } } diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java new file mode 100644 index 000000000..3fce54df1 --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java @@ -0,0 +1,154 @@ +package org.argeo.init.osgi; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; + +import org.argeo.api.init.InitConstants; +import org.argeo.api.init.RuntimeContext; +import org.argeo.api.init.RuntimeManager; +import org.argeo.internal.init.InternalState; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.FrameworkEvent; +import org.osgi.framework.connect.ConnectFrameworkFactory; +import org.osgi.framework.launch.Framework; + +/** + * Dynamically configures and launches multiple runtimes. + */ +class OsgiRuntimeManager implements RuntimeManager { + private final static Logger logger = System.getLogger(OsgiRuntimeManager.class.getName()); + + private final static long RUNTIME_SHUTDOWN_TIMEOUT = 60 * 1000; + + private final static String EQUINOX_FRAMEWORK_FACTORY_CLASS = "org.eclipse.osgi.launch.EquinoxFactory"; + + private Path baseConfigArea; + private Path baseWritableArea; + private Map configuration = new HashMap<>(); + + private Map runtimeContexts = new TreeMap<>(); + + private ConnectFrameworkFactory frameworkFactory; + + OsgiRuntimeManager(BundleContext bundleContext) { + try { + @SuppressWarnings("unchecked") + Class frameworkFactoryClass = (Class) Framework.class + .getClassLoader().loadClass(EQUINOX_FRAMEWORK_FACTORY_CLASS); + frameworkFactory = frameworkFactoryClass.getConstructor().newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException | NoSuchMethodException | SecurityException e) { + throw new IllegalStateException("Cannot create OSGi framework factory", e); + } + + this.baseConfigArea = Paths + .get(URI.create(bundleContext.getProperty(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA))) + .getParent(); + this.baseWritableArea = Paths + .get(URI.create(bundleContext.getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA))).getParent() + .getParent(); + + logger.log(Level.TRACE, () -> "Runtime manager configuration: " + configuration); + +// System.out.println("java.library.path=" + System.getProperty("java.library.path")); + } + + protected void shutdown() { + // shutdowm runtimes + Map shutdowning = new HashMap<>(runtimeContexts); + for (String id : new HashSet<>(runtimeContexts.keySet())) { + logger.log(Logger.Level.DEBUG, "Shutting down runtime " + id + " ..."); + closeRuntime(id, true); + } + for (String id : shutdowning.keySet()) + try { + RuntimeContext runtimeContext = shutdowning.get(id); + runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); + } catch (InterruptedException e) { + // silent + } catch (Exception e) { + logger.log(Logger.Level.DEBUG, "Cannot wait for " + id + " to shutdown", e); + } + // shutdown manager runtime + try { + InternalState.getMainRuntimeContext().close(); + InternalState.getMainRuntimeContext().waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); +// logger.log(Logger.Level.INFO, "Argeo Init stopped with PID " + ProcessHandle.current().pid()); + System.out.flush(); + } catch (Exception e) { + e.printStackTrace(); + Runtime.getRuntime().halt(1); + } + } + + OsgiRuntimeContext loadRuntime(String relPath, Consumer> configCallback) { + closeRuntime(relPath, false); + Path writableArea = baseWritableArea.resolve(relPath); + Path configArea = baseConfigArea.resolve(relPath); + Map config = new HashMap<>(); + RuntimeManager.loadConfig(configArea, config); + config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, writableArea.resolve(STATE).toUri().toString()); + + if (configCallback != null) + configCallback.accept(config); + + // use config area if instance area is not set + if (!config.containsKey(InitConstants.PROP_OSGI_INSTANCE_AREA)) + config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, writableArea.resolve(DATA).toUri().toString()); + + // create framework + Framework framework = frameworkFactory.newFramework(config, null); + try { + framework.start(); + } catch (BundleException e) { + throw new IllegalStateException("Cannot initialise framework", e); + } + OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(framework.getBundleContext()); + runtimeContexts.put(relPath, runtimeContext); + return runtimeContext; + } + + public void startRuntime(String relPath, Consumer> configCallback) { + OsgiRuntimeContext runtimeContext = loadRuntime(relPath, configCallback); + //runtimeContext.run(); + Framework framework = runtimeContext.getFramework(); + if (framework != null) {// in case the framework has closed very quickly after run + framework.getBundleContext().addFrameworkListener((e) -> { + if (e.getType() >= FrameworkEvent.STOPPED) { + logger.log(Level.DEBUG, "Externally stopped runtime " + relPath + ". Unregistering...", e); + runtimeContexts.remove(relPath); + } + }); + } else { + closeRuntime(relPath, false); + } + } + + public void closeRuntime(String relPath, boolean async) { + if (!runtimeContexts.containsKey(relPath)) + return; + RuntimeContext runtimeContext = runtimeContexts.get(relPath); + try { + runtimeContext.close(); + if (!async) { + runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); + System.gc(); + } + } catch (Exception e) { + logger.log(Level.ERROR, "Cannot close runtime context " + relPath, e); + } finally { + runtimeContexts.remove(relPath); + } + + } +} diff --git a/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java b/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java index 2aa488050..17d6d500f 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java +++ b/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java @@ -66,7 +66,7 @@ public class SubFrameworkActivator implements BundleActivator { .getClassLoader().loadClass(EQUINOX_FRAMEWORK_FACTORY_CLASS); frameworkFactory = frameworkFactoryClass.getConstructor().newInstance(); - boolean test = true; + boolean test = false; if (test) new Thread() { -- 2.30.2