| // |
| // ======================================================================== |
| // Copyright (c) 1995-2015 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.InetSocketAddress; |
| import java.net.ServerSocket; |
| import java.net.Socket; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Arrays; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArraySet; |
| |
| import org.eclipse.jetty.util.component.Destroyable; |
| import org.eclipse.jetty.util.component.LifeCycle; |
| import org.eclipse.jetty.util.thread.ShutdownThread; |
| |
| /** |
| * Shutdown/Stop Monitor thread. |
| * <p> |
| * This thread listens on the host/port specified by the STOP.HOST/STOP.PORT system parameter (defaults to 127.0.0.1/-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 |
| { |
| private final Set<LifeCycle> _lifeCycles = new CopyOnWriteArraySet<LifeCycle>(); |
| |
| // 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; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public static synchronized void register(LifeCycle... lifeCycles) |
| { |
| getInstance()._lifeCycles.addAll(Arrays.asList(lifeCycles)); |
| } |
| |
| |
| /* ------------------------------------------------------------ */ |
| public static synchronized void deregister(LifeCycle lifeCycle) |
| { |
| getInstance()._lifeCycles.remove(lifeCycle); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public static synchronized boolean isRegistered(LifeCycle lifeCycle) |
| { |
| return getInstance()._lifeCycles.contains(lifeCycle); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * ShutdownMonitorRunnable |
| * |
| * 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. |
| * |
| */ |
| private class ShutdownMonitorRunnable implements Runnable |
| { |
| public ShutdownMonitorRunnable() |
| { |
| startListenSocket(); |
| } |
| |
| @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".equalsIgnoreCase(cmd)) //historic, for backward compatibility |
| { |
| //Stop the lifecycles, only if they are registered with the ShutdownThread, only destroying if vm is exiting |
| debug("Issuing stop..."); |
| |
| for (LifeCycle l:_lifeCycles) |
| { |
| try |
| { |
| if (l.isStarted() && ShutdownThread.isRegistered(l)) |
| { |
| l.stop(); |
| } |
| |
| if ((l instanceof Destroyable) && exitVm) |
| ((Destroyable)l).destroy(); |
| } |
| catch (Exception e) |
| { |
| debug(e); |
| } |
| } |
| |
| //Stop accepting any more commands |
| stopInput(socket); |
| |
| // Reply to client |
| debug("Informing client that we are stopped."); |
| informClient(out, "Stopped\r\n"); |
| |
| //Stop the output and close the monitor socket |
| stopOutput(socket); |
| |
| if (exitVm) |
| { |
| // Kill JVM |
| debug("Killing JVM"); |
| System.exit(0); |
| } |
| } |
| else if ("forcestop".equalsIgnoreCase(cmd)) |
| { |
| debug("Issuing force stop..."); |
| |
| //Ensure that objects are stopped, destroyed only if vm is forcibly exiting |
| stopLifeCycles(exitVm); |
| |
| //Stop accepting any more commands |
| stopInput(socket); |
| |
| // Reply to client |
| debug("Informing client that we are stopped."); |
| informClient(out, "Stopped\r\n"); |
| |
| //Stop the output and close the monitor socket |
| stopOutput(socket); |
| |
| //Honour any pre-setup config to stop the jvm when this command is given |
| if (exitVm) |
| { |
| // Kill JVM |
| debug("Killing JVM"); |
| System.exit(0); |
| } |
| } |
| else if ("stopexit".equalsIgnoreCase(cmd)) |
| { |
| debug("Issuing stop and exit..."); |
| //Make sure that objects registered with the shutdown thread will be stopped |
| stopLifeCycles(true); |
| |
| //Stop accepting any more input |
| stopInput(socket); |
| |
| // Reply to client |
| debug("Informing client that we are stopped."); |
| informClient(out, "Stopped\r\n"); |
| |
| //Stop the output and close the monitor socket |
| stopOutput(socket); |
| |
| debug("Killing JVM"); |
| System.exit(0); |
| } |
| else if ("exit".equalsIgnoreCase(cmd)) |
| { |
| debug("Killing JVM"); |
| System.exit(0); |
| } |
| else if ("status".equalsIgnoreCase(cmd)) |
| { |
| // Reply to client |
| informClient(out, "OK\r\n"); |
| } |
| } |
| catch (Exception e) |
| { |
| debug(e); |
| System.err.println(e.toString()); |
| } |
| finally |
| { |
| close(socket); |
| socket = null; |
| } |
| } |
| } |
| |
| public void stopInput (Socket socket) |
| { |
| //Stop accepting any more input |
| close(serverSocket); |
| serverSocket = null; |
| //Shutdown input from client |
| shutdownInput(socket); |
| } |
| |
| public void stopOutput (Socket socket) throws IOException |
| { |
| socket.shutdownOutput(); |
| close(socket); |
| socket = null; |
| debug("Shutting down monitor"); |
| serverSocket = null; |
| } |
| |
| public void informClient (OutputStream out, String message) throws IOException |
| { |
| out.write(message.getBytes(StandardCharsets.UTF_8)); |
| out.flush(); |
| } |
| |
| /** |
| * Stop the registered lifecycles, optionally |
| * calling destroy on them. |
| * |
| * @param destroy true if {@link Destroyable}'s should also be destroyed. |
| */ |
| public void stopLifeCycles (boolean destroy) |
| { |
| for (LifeCycle l:_lifeCycles) |
| { |
| try |
| { |
| if (l.isStarted()) |
| { |
| l.stop(); |
| } |
| |
| if ((l instanceof Destroyable) && destroy) |
| ((Destroyable)l).destroy(); |
| } |
| catch (Exception e) |
| { |
| debug(e); |
| } |
| } |
| } |
| |
| public void startListenSocket() |
| { |
| if (port < 0) |
| { |
| if (DEBUG) |
| System.err.println("ShutdownMonitor not in use (port < 0): " + port); |
| return; |
| } |
| |
| try |
| { |
| serverSocket = new ServerSocket(); |
| serverSocket.setReuseAddress(true); |
| serverSocket.bind(new InetSocketAddress(InetAddress.getByName(host), port), 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 String host; |
| private int port; |
| private String key; |
| private boolean exitVm; |
| private ServerSocket serverSocket; |
| private Thread 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() |
| { |
| this.DEBUG = System.getProperty("DEBUG") != null; |
| |
| // Use values passed thru via /jetty-start/ |
| this.host = System.getProperty("STOP.HOST","127.0.0.1"); |
| this.port = Integer.parseInt(System.getProperty("STOP.PORT","-1")); |
| this.key = System.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; |
| } |
| |
| /** |
| * @param exitVm true to exit the VM on shutdown |
| */ |
| 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 |
| { |
| Thread t = null; |
| |
| synchronized (this) |
| { |
| if (thread != null && thread.isAlive()) |
| { |
| if (DEBUG) |
| System.err.printf("ShutdownMonitorThread already started"); |
| return; // cannot start it again |
| } |
| |
| thread = new Thread(new ShutdownMonitorRunnable()); |
| thread.setDaemon(true); |
| thread.setName("ShutdownMonitor"); |
| 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); |
| } |
| } |