From 9f7320fa6720bfd2aeb3d767b4ab7b72e1d0b917 Mon Sep 17 00:00:00 2001
From: jrandom <jrandom>
Date: Mon, 23 Aug 2004 07:33:14 +0000
Subject: [PATCH] * new configservice.jsp page that shuts down the router (and
 has hooks for a few other things) * new safer way of shutting down the router
 per discussions with oOo (dealing with a graceful shutdown where the user
 updates their config before the shutdown is complete, etc) * graceful
 shutdown implemented in the router - shutdownGracefully(),
 cancelGracefulShutdown(), shutdownInProgress()

---
 .../i2p/router/web/ConfigServiceHandler.java  | 46 ++++++++++++
 apps/routerconsole/jsp/confignav.jsp          |  2 +
 apps/routerconsole/jsp/configservice.jsp      | 55 ++++++++++++++
 router/java/src/net/i2p/router/Router.java    | 71 +++++++++++++++++++
 .../net/i2p/router/RouterThrottleImpl.java    |  9 ++-
 5 files changed, 180 insertions(+), 3 deletions(-)
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
 create mode 100644 apps/routerconsole/jsp/configservice.jsp

diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
new file mode 100644
index 0000000000..60ea04742d
--- /dev/null
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
@@ -0,0 +1,46 @@
+package net.i2p.router.web;
+
+import net.i2p.router.ClientTunnelSettings;
+
+/**
+ * Handler to deal with form submissions from the service config form and act
+ * upon the values.
+ *
+ */
+public class ConfigServiceHandler extends FormHandler {
+    private String _action;
+    private String _nonce;
+    
+    public void ConfigNetHandler() {
+        _action = null;
+        _nonce = null;
+    }
+    
+    protected void processForm() {
+        if (_action == null) return;
+        if (_nonce == null) {
+            addFormError("You trying to mess with me?  Huh?  Are you?");
+            return;
+        }
+        String nonce = System.getProperty(ConfigServiceHandler.class.getName() + ".nonce");
+        String noncePrev = System.getProperty(ConfigServiceHandler.class.getName() + ".noncePrev");
+        if ( (!_nonce.equals(nonce)) && (!_nonce.equals(noncePrev)) ) {
+            addFormError("Invalid nonce?  Hmmm, someone is spoofing you.  prev=["+ noncePrev + "] nonce=[" + nonce + "] param=[" + _nonce + "]");
+            return;
+        }
+        if ("Shutdown gracefully".equals(_action)) {
+            _context.router().shutdownGracefully();
+            addFormNotice("Graceful shutdown initiated");
+        } else if ("Shutdown immediately".equals(_action)) {
+            _context.router().shutdown();
+            addFormNotice("Shutdown immediately!  boom bye bye bad bwoy");
+        } else if ("Cancel graceful shutdown".equals(_action)) {
+            _context.router().cancelGracefulShutdown();
+            addFormNotice("Graceful shutdown cancelled");
+        } else {
+            addFormNotice("Blah blah blah.  whatever.  I'm not going to " + _action);
+        }
+    }
+    public void setAction(String action) { _action = action; }
+    public void setNonce(String nonce) { _nonce = nonce; }
+}
diff --git a/apps/routerconsole/jsp/confignav.jsp b/apps/routerconsole/jsp/confignav.jsp
index c17b54a19a..1da6c88cec 100644
--- a/apps/routerconsole/jsp/confignav.jsp
+++ b/apps/routerconsole/jsp/confignav.jsp
@@ -1,5 +1,7 @@
 <h4><% if (request.getRequestURI().indexOf("config.jsp") != -1) { 
  %>Network | <% } else { %><a href="config.jsp">Network</a> | <% }
+ if (request.getRequestURI().indexOf("configservice.jsp") != -1) {
+ %>Service | <% } else { %><a href="configservice.jsp">Service</a> | <% }
  if (request.getRequestURI().indexOf("configclients.jsp") != -1) {
  %>Clients | <% } else { %><a href="configclients.jsp">Clients</a> | <% }
  if (request.getRequestURI().indexOf("configlogging.jsp") != -1) {
diff --git a/apps/routerconsole/jsp/configservice.jsp b/apps/routerconsole/jsp/configservice.jsp
new file mode 100644
index 0000000000..771cbfcf7c
--- /dev/null
+++ b/apps/routerconsole/jsp/configservice.jsp
@@ -0,0 +1,55 @@
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - config clients</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+
+<div class="main" id="main">
+ <%@include file="confignav.jsp" %>
+  
+ <jsp:useBean class="net.i2p.router.web.ConfigServiceHandler" id="formhandler" scope="request" />
+ <jsp:setProperty name="formhandler" property="*" />
+ <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+ <font color="red"><jsp:getProperty name="formhandler" property="errors" /></font>
+ <i><jsp:getProperty name="formhandler" property="notices" /></i>
+ 
+ <form action="configservice.jsp" method="POST">
+ <% String prev = System.getProperty("net.i2p.router.web.ConfigServiceHandler.nonce");
+    if (prev != null) System.setProperty("net.i2p.router.web.ConfigServiceHandler.noncePrev", prev);
+    System.setProperty("net.i2p.router.web.ConfigServiceHandler.nonce", new java.util.Random().nextLong()+""); %>
+ <input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigServiceHandler.nonce")%>" />
+ <h4>Shutdown the router</h4>
+ Graceful shutdown lets the router satisfy the agreements it has already made 
+ before shutting down, but may take a few minutes.  If you need to kill the
+ router immediately, that option is available as well.<br />
+ <input type="submit" name="action" value="Shutdown gracefully" />
+ <input type="submit" name="action" value="Shutdown immediately" />
+ <input type="submit" name="action" value="Cancel graceful shutdown" />
+ <h4>Systray integration</h4>
+ On the windows platform, there is a small application to sit in the system 
+ tray, allowing you to view the router's status (later on, I2P client applications
+ will be able to integrate their own functionality into the system tray as well).
+ If you are on windows, you can either enable or disable that icon here. <br />
+ <input type="submit" name="action" value="Enable systray icon" />
+ <input type="submit" name="action" value="Disable systray icon" />
+ <h4>Run on startup</h4>
+ On the windows platform, you can control whether I2P is run on startup or not by
+ selecting one of the following options - I2P will install (or remove) a service 
+ accordingly.  On *nix machines, you need root permissions to add a script
+ to be run on startup (we hope you know better than to run I2P as root ;).  To 
+ have I2P run (or not run) at startup on *nix machines, please run 
+ <code>install_i2p_service_unix</code> or <code>install_i2p_service_unix</code>
+ as root.<br />
+ <input type="submit" name="action" value="Run I2P on startup" />
+ <input type="submit" name="action" value="Don't run I2P on startup" />
+ </form>
+</div>
+
+</body>
+</html>
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index f602d6eb4e..17c02cde5c 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -57,6 +57,7 @@ public class Router {
     private boolean _isAlive;
     private I2PThread.OOMEventListener _oomListener;
     private ShutdownHook _shutdownHook;
+    private I2PThread _gracefulShutdownDetector;
     
     public final static String PROP_CONFIG_FILE = "router.configLocation";
     
@@ -67,6 +68,7 @@ public class Router {
     public final static String PROP_INFO_FILENAME_DEFAULT = "router.info";
     public final static String PROP_KEYS_FILENAME = "router.keys.location";
     public final static String PROP_KEYS_FILENAME_DEFAULT = "router.keys";
+    public final static String PROP_SHUTDOWN_IN_PROGRESS = "__shutdownInProgress";
         
     static {
         // grumble about sun's java caching DNS entries *forever*
@@ -108,6 +110,8 @@ public class Router {
             }
         };
         _shutdownHook = new ShutdownHook();
+        _gracefulShutdownDetector = new I2PThread(new GracefulShutdown());
+        _gracefulShutdownDetector.start();
     }
     
     /**
@@ -184,6 +188,8 @@ public class Router {
             File f = new File(filename);
             if (f.canRead()) {
                 DataHelper.loadProps(props, f);
+                // dont be a wanker
+                props.remove(PROP_SHUTDOWN_IN_PROGRESS);
             } else {
                 log.warn("Configuration file " + filename + " does not exist");
             }
@@ -552,6 +558,71 @@ public class Router {
         }
     }
     
+    /**
+     * Call this if we want the router to kill itself as soon as we aren't 
+     * participating in any more tunnels (etc).  This will not block and doesn't
+     * guarantee any particular time frame for shutting down.  To shut the 
+     * router down immediately, use {@link #shutdown}.  If you want to cancel
+     * the graceful shutdown (prior to actual shutdown ;), call 
+     * {@link #cancelGracefulShutdown}.
+     *
+     */
+    public void shutdownGracefully() {
+        _config.setProperty(PROP_SHUTDOWN_IN_PROGRESS, "true");
+        synchronized (_gracefulShutdownDetector) {
+            _gracefulShutdownDetector.notifyAll();
+        }
+    }
+    
+    /**
+     * Cancel any prior request to shut the router down gracefully.
+     *
+     */
+    public void cancelGracefulShutdown() {
+        _config.remove(PROP_SHUTDOWN_IN_PROGRESS);
+        synchronized (_gracefulShutdownDetector) {
+            _gracefulShutdownDetector.notifyAll();
+        }        
+    }
+    
+    public boolean gracefulShutdownInProgress() {
+        return (null != _config.getProperty(PROP_SHUTDOWN_IN_PROGRESS));
+    }
+    
+    /**
+     * Simple thread that sits and waits forever, managing the
+     * graceful shutdown "process" (describing it would take more text
+     * than just reading the code...)
+     *
+     */
+    private class GracefulShutdown implements Runnable {
+        public void run() {
+            while (true) {
+                boolean shutdown = (null != _config.getProperty(PROP_SHUTDOWN_IN_PROGRESS));
+                if (shutdown) {
+                    if (_context.tunnelManager().getParticipatingCount() <= 0) {
+                        if (_log.shouldLog(Log.CRIT))
+                            _log.log(Log.CRIT, "Graceful shutdown progress - no more tunnels, safe to die");
+                        shutdown();
+                        return;
+                    } else {
+                        try {
+                            synchronized (Thread.currentThread()) {
+                                Thread.currentThread().wait(10*1000);
+                            }
+                        } catch (InterruptedException ie) {}
+                    }
+                } else {
+                    try {
+                        synchronized (Thread.currentThread()) {
+                            Thread.currentThread().wait();
+                        }
+                    } catch (InterruptedException ie) {}
+                }
+            }
+        }
+    }
+    
     /**
      * Save the current config options (returning true if save was 
      * successful, false otherwise)
diff --git a/router/java/src/net/i2p/router/RouterThrottleImpl.java b/router/java/src/net/i2p/router/RouterThrottleImpl.java
index dd33d23c48..05fafe9932 100644
--- a/router/java/src/net/i2p/router/RouterThrottleImpl.java
+++ b/router/java/src/net/i2p/router/RouterThrottleImpl.java
@@ -122,9 +122,12 @@ class RouterThrottleImpl implements RouterThrottle {
         int numTunnels = _context.tunnelManager().getParticipatingCount();
         double bytesAllocated =  (numTunnels + 1) * bytesPerTunnel;
 
-        // the max # tunnels throttle is useful for shutting down the router - 
-        // set this to 0, wait a few minutes, and the router can be shut off 
-        // without killing anyone's tunnels
+        if (_context.getProperty(Router.PROP_SHUTDOWN_IN_PROGRESS) != null) {
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Refusing tunnel request since we are shutting down ASAP");
+            return false;
+        }
+        
         String maxTunnels = _context.getProperty(PROP_MAX_TUNNELS);
         if (maxTunnels != null) {
             try {
-- 
GitLab