diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java index 170af5c355af35443bb7331652ba88957d4d1c9d..cf15fee89475a43307280f3644dc5328a4bb2df4 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java @@ -8,8 +8,12 @@ import net.i2p.apps.systray.UrlLauncher; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.startup.ClientAppConfig; +import net.i2p.util.Log; import org.tanukisoftware.wrapper.WrapperManager; +import org.tanukisoftware.wrapper.event.WrapperControlEvent; +import org.tanukisoftware.wrapper.event.WrapperEvent; +import org.tanukisoftware.wrapper.event.WrapperEventListener; /** * Handler to deal with form submissions from the service config form and act @@ -18,6 +22,10 @@ import org.tanukisoftware.wrapper.WrapperManager; */ public class ConfigServiceHandler extends FormHandler { + private static WrapperEventListener _signalHandler; + + private static final String PROP_GRACEFUL_HUP = "router.gracefulHUP"; + /** * Register two shutdown hooks, one to rekey and/or tell the wrapper we are stopping, * and a final one to tell the wrapper we are stopped. @@ -127,6 +135,79 @@ public class ConfigServiceHandler extends FormHandler { } } + /** + * Register a handler for signals, + * so we can handle HUP from the wrapper (non-Windows only) + * + * @since 0.8.13 + */ + synchronized static void registerSignalHandler(RouterContext ctx) { + if (ctx.hasWrapper() && _signalHandler == null && + !System.getProperty("os.name").startsWith("Win")) { + _signalHandler = new SignalHandler(ctx); + long mask = WrapperEventListener.EVENT_FLAG_CONTROL; + WrapperManager.addWrapperEventListener(_signalHandler, mask); + } + } + + /** + * Unregister the handler for signals + * + * @since 0.8.13 + */ + public synchronized static void unregisterSignalHandler() { + if (_signalHandler != null) { + WrapperManager.removeWrapperEventListener(_signalHandler); + _signalHandler = null; + } + } + + /** + * Catch signals. + * The wrapper will potentially forward HUP, USR1, and USR2. + * But USR1 and USR2 are used by the JVM GC and cannot be trapped. + * So we will only get HUP. + * + * @since 0.8.13 + */ + private static class SignalHandler implements WrapperEventListener { + private final RouterContext _ctxt; + + public SignalHandler(RouterContext ctx) { + _ctxt = ctx; + } + + public void fired(WrapperEvent event) { + if (!(event instanceof WrapperControlEvent)) + return; + WrapperControlEvent wce = (WrapperControlEvent) event; + Log log = _ctxt.logManager().getLog(ConfigServiceHandler.class); + if (log.shouldLog(Log.WARN)) + log.warn("Got signal: " + wce.getControlEventName()); + int sig = wce.getControlEvent(); + switch (sig) { + case WrapperManager.WRAPPER_CTRL_HUP_EVENT: + if (_ctxt.getBooleanProperty(PROP_GRACEFUL_HUP)) { + wce.consume(); + if (!(_ctxt.router().gracefulShutdownInProgress() || + _ctxt.router().isFinalShutdownInProgress())) { + System.err.println("WARN: Graceful shutdown initiated by SIGHUP"); + log.logAlways(Log.WARN, "Graceful shutdown initiated by SIGHUP"); + registerWrapperNotifier(_ctxt, Router.EXIT_GRACEFUL, false); + _ctxt.router().shutdownGracefully(); + } + } else { + log.log(Log.CRIT, "Hard shutdown initiated by SIGHUP"); + // JVM will call ShutdownHook if we don't do it ourselves + //wce.consume(); + //registerWrapperNotifier(_ctxt, Router.EXIT_HARD, false); + //_ctxt.router().shutdown(Router.EXIT_HARD); + } + break; + } + } + } + @Override protected void processForm() { if (_action == null) return; @@ -194,6 +275,7 @@ public class ConfigServiceHandler extends FormHandler { addFormError(_("Warning: unable to install the service") + " - " + ioe.getMessage()); } } + private void uninstallService() { try { Runtime.getRuntime().exec("uninstall_i2p_service_winnt.bat"); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 15c7f76f5f2faf52cdb71f620a6427f6d8aa6087..ddcb98a0da57044531eb248da309d0ee251aefa7 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -367,6 +367,7 @@ public class RouterConsoleRunner { ctx.addShutdownTask(new NewsShutdown(fetcher, newsThread)); // stat summarizer registers its own hook ctx.addShutdownTask(new ServerShutdown()); + ConfigServiceHandler.registerSignalHandler(ctx); } // else log CRIT ? } diff --git a/installer/resources/i2prouter b/installer/resources/i2prouter index f911535d40bc732591bb1128d3a2d89314780b3a..02b21ac98799faad2ec68ffe83236f63fb7a36da 100644 --- a/installer/resources/i2prouter +++ b/installer/resources/i2prouter @@ -1012,6 +1012,7 @@ start() { startwait } + stopit() { # $1 exit if down flag @@ -1028,7 +1029,7 @@ stopit() { if [ "X$IGNORE_SIGNALS" = "X" ] then # Running so try to stop it. - kill $pid + kill -TERM $pid if [ $? -ne 0 ] then # An explanation for the failure should have been given @@ -1080,6 +1081,43 @@ stopit() { fi } +graceful() { + # $1 exit if down flag + + eval echo `gettext 'Stopping $APP_LONG_NAME gracefully...'` + getpid + if [ "X$pid" = "X" ] + then + eval echo `gettext '$APP_LONG_NAME was not running.'` + if [ "X$1" = "X1" ] + then + exit 1 + fi + else + if [ "X$IGNORE_SIGNALS" = "X" ] + then + # Running so try to stop it. + # This sends HUP. router.gracefulHUP must be set in router.config, + # or else this will do the same as stop. + kill $pid + if [ $? -ne 0 ] + then + # An explanation for the failure should have been given + eval echo `gettext 'Unable to stop $APP_LONG_NAME.'` + exit 1 + fi + else + rm -f "$ANCHORFILE" + if [ -f "$ANCHORFILE" ] + then + # An explanation for the failure should have been given + eval echo `gettext 'Unable to stop $APP_LONG_NAME.'` + exit 1 + fi + fi + fi +} + pause() { eval echo `gettext 'Pausing $APP_LONG_NAME.'` } @@ -1557,6 +1595,7 @@ showUsage() { echo "`gettext ' console Launch in the current console.'`" echo "`gettext ' start Start in the background as a daemon process.'`" echo "`gettext ' stop Stop if running as a daemon or in another console.'`" + echo "`gettext ' graceful Stop gracefully, may take up to 11 minutes.'`" echo "`gettext ' restart Stop if running and then start.'`" echo "`gettext ' condrestart Restart only if already running.'`" if [ -n "$PAUSABLE" ] ; then @@ -1624,6 +1663,11 @@ docommand() { stopit "0" ;; + 'graceful') + checkUser "" "$COMMAND" + graceful "0" + ;; + 'restart') checkUser touchlock "$COMMAND" if [ ! -n "$FIXED_COMMAND" ] ; then diff --git a/installer/resources/wrapper.config b/installer/resources/wrapper.config index 6f364146171e703dea0292f127038453449477bb..9eee7dda210fc150dcc14c2ffd71fe73751bdc73 100644 --- a/installer/resources/wrapper.config +++ b/installer/resources/wrapper.config @@ -168,6 +168,10 @@ wrapper.logfile.maxfiles=2 # Log Level for sys/event log output. (See docs for log levels) wrapper.syslog.loglevel=NONE +# these will shut down or crash the JVM +wrapper.signal.mode.usr1=IGNORE +wrapper.signal.mode.usr2=IGNORE + # choose what to do if the JVM kills itself based on the exit code wrapper.on_exit.default=SHUTDOWN wrapper.on_exit.0=SHUTDOWN diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 08ea93bd8c3a5984ea157335ec7a10f4ab727184..8e343aeecea6972ca7f80ed00bb9ace5c9b93062 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -233,6 +233,8 @@ public class Router implements RouterClock.ClockShiftListener { String now = Long.toString(System.currentTimeMillis()); _config.put("router.firstInstalled", now); _config.put("router.updateLastInstalled", now); + // only compatible with new i2prouter script + _config.put("router.gracefulHUP", "true"); saveConfig(); } @@ -376,7 +378,7 @@ public class Router implements RouterClock.ClockShiftListener { _isAlive = true; _started = _context.clock().now(); try { - Runtime.getRuntime().removeShutdownHook(_shutdownHook); + Runtime.getRuntime().addShutdownHook(_shutdownHook); } catch (IllegalStateException ise) {} I2PThread.addOOMEventListener(_oomListener); @@ -987,9 +989,12 @@ public class Router implements RouterClock.ClockShiftListener { /** * Cancel the JVM runtime hook before calling this. + * Called by the ShutdownHook. * NOT to be called by others, use shutdown(). */ public void shutdown2(int exitCode) { + _shutdownInProgress = true; + _log.log(Log.CRIT, "Starting final shutdown(" + exitCode + ')'); // So we can get all the way to the end // No, you can't do Thread.currentThread.setDaemon(false) if (_killVMOnEnd) { @@ -1004,6 +1009,7 @@ public class Router implements RouterClock.ClockShiftListener { // Run the shutdown hooks first in case they want to send some goodbye messages // Maybe we need a delay after this too? for (Runnable task : _context.getShutdownTasks()) { + //System.err.println("Running shutdown task " + task.getClass()); if (_log.shouldLog(Log.WARN)) _log.warn("Running shutdown task " + task.getClass()); try { @@ -1098,7 +1104,7 @@ public class Router implements RouterClock.ClockShiftListener { //Runtime.getRuntime().halt(exitCode); // allow the Runtime shutdown hooks to execute Runtime.getRuntime().exit(exitCode); - } else { + } else if (System.getProperty("java.vendor").contains("Android")) { Runtime.getRuntime().gc(); } } diff --git a/router/java/src/net/i2p/router/tasks/ShutdownHook.java b/router/java/src/net/i2p/router/tasks/ShutdownHook.java index 9bc57d6435642bea828915c4c51c77f61f025504..ae8e38d7a48da097e07429eb956c0b0eff277f5f 100644 --- a/router/java/src/net/i2p/router/tasks/ShutdownHook.java +++ b/router/java/src/net/i2p/router/tasks/ShutdownHook.java @@ -32,6 +32,10 @@ public class ShutdownHook extends Thread { setName("Router " + _id + " shutdown"); Log l = _context.logManager().getLog(Router.class); l.log(Log.CRIT, "Shutting down the router..."); + // Needed to make the wrapper happy, otherwise it gets confused + // and thinks we haven't shut down, possibly because it + // prevents other shutdown hooks from running + _context.router().setKillVMOnEnd(false); _context.router().shutdown2(Router.EXIT_HARD); } }