diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java index 39d38e71af1..70347e036c5 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java @@ -40,7 +40,7 @@ import org.eclipse.jetty.util.thread.ShutdownThread; *

* Commands "stop" and "status" are currently supported. */ -public class ShutdownMonitor extends Thread +public class ShutdownMonitor { // Implementation of safe lazy init, using Initialization on Demand Holder technique. static class Holder @@ -53,11 +53,164 @@ public class ShutdownMonitor extends Thread 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(); + + // Reply to client + debug("Informing client that we are stopped."); + out.write("Stopped\r\n".getBytes(StringUtil.__UTF8)); + out.flush(); + + // Shutdown Monitor + debug("Shutting down monitor"); + close(socket); + socket = null; + close(serverSocket); + serverSocket = null; + + if (exitVm) + { + // Kill JVM + debug("Killing JVM"); + System.exit(0); + } + } + else if ("status".equals(cmd)) + { + // Reply to client + out.write("OK\r\n".getBytes(StringUtil.__UTF8)); + out.flush(); + } + } + catch (Exception e) + { + debug(e); + System.err.println(e.toString()); + } + finally + { + close(socket); + socket = null; + } + } + } + + public void start() + { + if (isAlive()) + { + System.err.printf("ShutdownMonitorThread already started"); + return; // cannot start it again + } + + startListenSocket(); + + if (serverSocket == null) + { + return; + } + 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. @@ -149,77 +302,6 @@ public class ShutdownMonitor extends Thread return exitVm; } - @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 key = lin.readLine(); - if (!this.key.equals(key)) - { - 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(); - - // Reply to client - debug("Informing client that we are stopped."); - out.write("Stopped\r\n".getBytes(StringUtil.__UTF8)); - out.flush(); - - // Shutdown Monitor - debug("Shutting down monitor"); - close(socket); - socket = null; - close(serverSocket); - serverSocket = null; - - if (exitVm) - { - // Kill JVM - debug("Killing JVM"); - System.exit(0); - } - } - else if ("status".equals(cmd)) - { - // Reply to client - out.write("OK\r\n".getBytes(StringUtil.__UTF8)); - out.flush(); - } - } - catch (Exception e) - { - debug(e); - System.err.println(e.toString()); - } - finally - { - close(socket); - socket = null; - } - } - } public void setDebug(boolean flag) { @@ -228,90 +310,71 @@ public class ShutdownMonitor extends Thread public void setExitVm(boolean exitVm) { - if (isAlive()) + synchronized (this) { - throw new IllegalStateException("ShutdownMonitor already started"); + if (thread != null && thread.isAlive()) + { + throw new IllegalStateException("ShutdownMonitorThread already started"); + } + this.exitVm = exitVm; } - this.exitVm = exitVm; } public void setKey(String key) { - if (isAlive()) + synchronized (this) { - throw new IllegalStateException("ShutdownMonitor already started"); + if (thread != null && thread.isAlive()) + { + throw new IllegalStateException("ShutdownMonitorThread already started"); + } + this.key = key; } - this.key = key; } public void setPort(int port) { - if (isAlive()) + synchronized (this) { - throw new IllegalStateException("ShutdownMonitor already started"); - } - this.port = port; - } - - public void start() - { - if (isAlive()) - { - System.err.printf("ShutdownMonitor already started"); - return; // cannot start it again - } - startListenSocket(); - if (serverSocket == null) - { - return; - } - - super.start(); - } - - private void startListenSocket() - { - if (this.port < 0) - { - if (DEBUG) - System.err.println("ShutdownMonitor not in use (port < 0): " + port); - return; - } - - try - { - setDaemon(true); - setName("ShutdownMonitor"); - - this.serverSocket = new ServerSocket(this.port,1,InetAddress.getByName("127.0.0.1")); - if (this.port == 0) + if (thread != null && thread.isAlive()) { - // server assigned port in use - this.port = serverSocket.getLocalPort(); - System.out.printf("STOP.PORT=%d%n",this.port); + throw new IllegalStateException("ShutdownMonitorThread already started"); } - - if (this.key == null) - { - // create random key - this.key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36); - System.out.printf("STOP.KEY=%s%n",this.key); - } - } - catch (Exception e) - { - debug(e); - System.err.println("Error binding monitor port " + this.port + ": " + e.toString()); - } - finally - { - // establish the port and key that are in use - debug("STOP.PORT=%d",this.port); - debug("STOP.KEY=%s",this.key); - debug("%s",serverSocket); + this.port = port; } } + protected void start() throws Exception + { + ShutdownMonitorThread t = null; + synchronized (this) + { + if (thread != null && thread.isAlive()) + { + 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() { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java new file mode 100644 index 00000000000..596f9ccc2bb --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java @@ -0,0 +1,112 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; + +import org.junit.Test; + +/** + * ShutdownMonitorTest + * + * + * + */ +public class ShutdownMonitorTest +{ + + + @Test + public void testShutdown () + throws Exception + { + + //test port and key assignment + ShutdownMonitor.getInstance().setPort(0); + ShutdownMonitor.getInstance().setExitVm(false); + ShutdownMonitor.getInstance().start(); + String key = ShutdownMonitor.getInstance().getKey(); + int port = ShutdownMonitor.getInstance().getPort(); + + //try starting a 2nd time (should be ignored) + ShutdownMonitor.getInstance().start(); + + + stop(port,key,true); + assertTrue(!ShutdownMonitor.getInstance().isAlive()); + + //should be able to change port and key because it is stopped + ShutdownMonitor.getInstance().setPort(0); + ShutdownMonitor.getInstance().setKey("foo"); + ShutdownMonitor.getInstance().start(); + + key = ShutdownMonitor.getInstance().getKey(); + port = ShutdownMonitor.getInstance().getPort(); + assertTrue(ShutdownMonitor.getInstance().isAlive()); + + stop(port,key,true); + assertTrue(!ShutdownMonitor.getInstance().isAlive()); + } + + + public void stop (int port, String key, boolean check) + throws Exception + { + Socket s = null; + + try + { + //send stop command + s = new Socket(InetAddress.getByName("127.0.0.1"),port); + + OutputStream out = s.getOutputStream(); + out.write((key + "\r\nstop\r\n").getBytes()); + out.flush(); + + if (check) + { + //wait a little + Thread.currentThread().sleep(600); + + //check for stop confirmation + LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream())); + String response; + if ((response = lin.readLine()) != null) + { + assertEquals("Stopped", response); + } + else + throw new IllegalStateException("No stop confirmation"); + } + } + finally + { + if (s != null) s.close(); + } + } + +}