]> WPIA git - gigi.git/blobdiff - lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / server / ShutdownMonitor.java
diff --git a/lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java b/lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java
new file mode 100644 (file)
index 0000000..abc6d61
--- /dev/null
@@ -0,0 +1,411 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.thread.ShutdownThread;
+
+/**
+ * Shutdown/Stop Monitor thread.
+ * <p>
+ * This thread listens on the port specified by the STOP.PORT system parameter (defaults to -1 for not listening) for request authenticated with the key given
+ * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
+ * <p>
+ * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
+ * <p>
+ * Commands "stop" and "status" are currently supported.
+ */
+public class ShutdownMonitor 
+{
+    // Implementation of safe lazy init, using Initialization on Demand Holder technique.
+    static class Holder
+    {
+        static ShutdownMonitor instance = new ShutdownMonitor();
+    }
+
+    public static ShutdownMonitor getInstance()
+    {
+        return Holder.instance;
+    }
+
+    /**
+     * ShutdownMonitorThread
+     *
+     * Thread for listening to STOP.PORT for command to stop Jetty.
+     * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be
+     * called after the stop.
+     *
+     */
+    public class ShutdownMonitorThread extends Thread
+    {
+
+        public ShutdownMonitorThread ()
+        {
+            setDaemon(true);
+            setName("ShutdownMonitor");
+        }
+        
+        @Override
+        public void run()
+        {
+            if (serverSocket == null)
+            {
+                return;
+            }
+
+            while (serverSocket != null)
+            {
+                Socket socket = null;
+                try
+                {
+                    socket = serverSocket.accept();
+
+                    LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
+                    String receivedKey = lin.readLine();
+                    if (!key.equals(receivedKey))
+                    {
+                        System.err.println("Ignoring command with incorrect key");
+                        continue;
+                    }
+
+                    OutputStream out = socket.getOutputStream();
+
+                    String cmd = lin.readLine();
+                    debug("command=%s",cmd);
+                    if ("stop".equals(cmd))
+                    {
+                        // Graceful Shutdown
+                        debug("Issuing graceful shutdown..");
+                        ShutdownThread.getInstance().run();
+                        
+                        //Stop accepting any more
+                        close(serverSocket);
+                        serverSocket = null;
+                        
+                        //Shutdown input from client
+                        shutdownInput(socket);
+
+                        // Reply to client
+                        debug("Informing client that we are stopped.");
+                        out.write("Stopped\r\n".getBytes(StandardCharsets.UTF_8));
+                        out.flush();
+
+                        // Shutdown Monitor
+                        socket.shutdownOutput();
+                        close(socket);
+                        socket = null;                        
+                        debug("Shutting down monitor");
+
+                        if (exitVm)
+                        {
+                            // Kill JVM
+                            debug("Killing JVM");
+                            System.exit(0);
+                        }
+                    }
+                    else if ("status".equals(cmd))
+                    {
+                        // Reply to client
+                        out.write("OK\r\n".getBytes(StandardCharsets.UTF_8));
+                        out.flush();
+                    }
+                }
+                catch (Exception e)
+                {
+                    debug(e);
+                    System.err.println(e.toString());
+                }
+                finally
+                {
+                    close(socket);
+                    socket = null;
+                }
+            }
+        }
+        
+        public void start()
+        {
+            if (isAlive())
+            {
+                // TODO why are we reentrant here?
+                if (DEBUG)
+                    System.err.printf("ShutdownMonitorThread already started");
+                return; // cannot start it again
+            }
+
+            startListenSocket();
+            
+            if (serverSocket == null)
+            {
+                return;
+            }
+            if (DEBUG)
+                System.err.println("Starting ShutdownMonitorThread");
+            super.start();
+        }
+        
+        private void startListenSocket()
+        {
+            if (port < 0)
+            {            
+                if (DEBUG)
+                    System.err.println("ShutdownMonitor not in use (port < 0): " + port);
+                return;
+            }
+
+            try
+            {
+                serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
+                if (port == 0)
+                {
+                    // server assigned port in use
+                    port = serverSocket.getLocalPort();
+                    System.out.printf("STOP.PORT=%d%n",port);
+                }
+
+                if (key == null)
+                {
+                    // create random key
+                    key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
+                    System.out.printf("STOP.KEY=%s%n",key);
+                }
+            }
+            catch (Exception e)
+            {
+                debug(e);
+                System.err.println("Error binding monitor port " + port + ": " + e.toString());
+                serverSocket = null;
+            }
+            finally
+            {
+                // establish the port and key that are in use
+                debug("STOP.PORT=%d",port);
+                debug("STOP.KEY=%s",key);
+                debug("%s",serverSocket);
+            }
+        }
+
+    }
+    
+    private boolean DEBUG;
+    private int port;
+    private String key;
+    private boolean exitVm;
+    private ServerSocket serverSocket;
+    private ShutdownMonitorThread thread;
+    
+    
+
+    /**
+     * Create a ShutdownMonitor using configuration from the System properties.
+     * <p>
+     * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br>
+     * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br>
+     * <p>
+     * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call.
+     */
+    private ShutdownMonitor()
+    {
+        Properties props = System.getProperties();
+
+        this.DEBUG = props.containsKey("DEBUG");
+
+        // Use values passed thru via /jetty-start/
+        this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
+        this.key = props.getProperty("STOP.KEY",null);
+        this.exitVm = true;
+    }
+
+    private void close(ServerSocket server)
+    {
+        if (server == null)
+        {
+            return;
+        }
+
+        try
+        {
+            server.close();
+        }
+        catch (IOException ignore)
+        {
+            debug(ignore);
+        }
+    }
+
+    private void close(Socket socket)
+    {
+        if (socket == null)
+        {
+            return;
+        }
+
+        try
+        {
+            socket.close();
+        }
+        catch (IOException ignore)
+        {
+            debug(ignore);
+        }
+    }
+
+    
+    private void shutdownInput(Socket socket)
+    {
+        if (socket == null)
+            return;
+        
+        try
+        {
+            socket.shutdownInput();
+        }   
+        catch (IOException ignore)
+        {
+            debug(ignore);
+        }
+    }
+    
+    
+    private void debug(String format, Object... args)
+    {
+        if (DEBUG)
+        {
+            System.err.printf("[ShutdownMonitor] " + format + "%n",args);
+        }
+    }
+
+    private void debug(Throwable t)
+    {
+        if (DEBUG)
+        {
+            t.printStackTrace(System.err);
+        }
+    }
+
+    public String getKey()
+    {
+        return key;
+    }
+
+    public int getPort()
+    {
+        return port;
+    }
+
+    public ServerSocket getServerSocket()
+    {
+        return serverSocket;
+    }
+
+    public boolean isExitVm()
+    {
+        return exitVm;
+    }
+
+
+    public void setDebug(boolean flag)
+    {
+        this.DEBUG = flag;
+    }
+
+    public void setExitVm(boolean exitVm)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.exitVm = exitVm;
+        }
+    }
+
+    public void setKey(String key)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.key = key;
+        }
+    }
+
+    public void setPort(int port)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.port = port;
+        }
+    }
+
+    protected void start() throws Exception
+    {
+        ShutdownMonitorThread t = null;
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                // TODO why are we reentrant here?
+                if (DEBUG)
+                    System.err.printf("ShutdownMonitorThread already started");
+                return; // cannot start it again
+            }
+         
+            thread = new ShutdownMonitorThread();
+            t = thread;
+        }
+         
+        if (t != null)
+            t.start();
+    }
+
+
+    protected boolean isAlive ()
+    {
+        boolean result = false;
+        synchronized (this)
+        {
+            result = (thread != null && thread.isAlive());
+        }
+        return result;
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s[port=%d]",this.getClass().getName(),port);
+    }
+}