391623 - Making --stop with STOP.WAIT perform graceful shutdown

+ Moving jetty-start Monitor to jetty-server ShutdownMonitor and
  using ShutdownThread to perform a graceful shutdown instead.
This commit is contained in:
Joakim Erdfelt 2012-12-04 10:18:54 -07:00
parent 7488452e8a
commit 7abd512b32
4 changed files with 296 additions and 185 deletions

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -260,11 +261,14 @@ public class Server extends HandlerWrapper implements Attributes
@Override
protected void doStart() throws Exception
{
if (getStopAtShutdown())
if (getStopAtShutdown()) {
ShutdownThread.register(this);
ShutdownMonitor.getInstance(); // initialize
}
LOG.info("jetty-"+__version);
HttpGenerator.setServerVersion(__version);
MultiException mex=new MultiException();
if (_threadPool==null)

View File

@ -0,0 +1,235 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.util.Properties;
import org.eclipse.jetty.util.StringUtil;
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 extends Thread
{
private static final ShutdownMonitor INSTANCE = new ShutdownMonitor();
public static ShutdownMonitor getInstance()
{
return INSTANCE;
}
private final boolean DEBUG;
private final int port;
private final String key;
private final ServerSocket serverSocket;
private ShutdownMonitor()
{
Properties props = System.getProperties();
// Use the same debug option as /jetty-start/
this.DEBUG = props.containsKey("DEBUG");
// Use values passed thru /jetty-start/
int stopPort = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
String stopKey = props.getProperty("STOP.KEY",null);
ServerSocket sock = null;
try
{
if (stopPort < 0)
{
System.out.println("ShutdownMonitor not in use");
sock = null;
return;
}
setDaemon(true);
setName("ShutdownMonitor");
sock = new ServerSocket(stopPort,1,InetAddress.getByName("127.0.0.1"));
if (stopPort == 0)
{
// server assigned port in use
stopPort = sock.getLocalPort();
System.out.printf("STOP.PORT=%d%n",stopPort);
}
if (stopKey == null)
{
// create random key
stopKey = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
System.out.printf("STOP.KEY=%s%n",stopKey);
}
}
catch (Exception e)
{
debug(e);
System.err.println("Error binding monitor port " + stopPort + ": " + e.toString());
}
finally
{
// establish the port and key that are in use
this.port = stopPort;
this.key = stopKey;
this.serverSocket = sock;
debug("STOP.PORT=%d", port);
debug("STOP.KEY=%s", key);
debug("%s", serverSocket);
}
this.start();
}
@Override
public String toString()
{
return String.format("%s[port=%d]",this.getClass().getName(),port);
}
private void debug(Throwable t)
{
if (DEBUG)
{
t.printStackTrace(System.err);
}
}
private void debug(String format, Object... args)
{
if (DEBUG)
{
System.err.printf("[ShutdownMonitor] " + format + "%n",args);
}
}
@Override
public void run()
{
while (true)
{
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);
close(serverSocket);
// 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;
}
}
}
private void close(Socket socket)
{
if (socket == null)
{
return;
}
try
{
socket.close();
}
catch (IOException ignore)
{
/* ignore */
}
}
private void close(ServerSocket server)
{
if (server == null)
{
return;
}
try
{
server.close();
}
catch (IOException ignore)
{
/* ignore */
}
}
}

View File

@ -53,12 +53,13 @@ import java.util.Set;
/*-------------------------------------------*/
/**
* <p>
* Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It allows an application to be started with
* the command "java -jar start.jar".
* Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It
* allows an application to be started with the command "java -jar start.jar".
* </p>
*
* <p>
* The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file obtained as a resource or file.
* The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file
* obtained as a resource or file.
* </p>
*/
public class Main
@ -492,11 +493,6 @@ public class Main
/* ------------------------------------------------------------ */
public void start(List<String> xmls) throws IOException, InterruptedException
{
// Setup Start / Stop Monitoring
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
String key = Config.getProperty("STOP.KEY",null);
Monitor monitor = new Monitor(port,key);
// Load potential Config (start.config)
List<String> configuredXmls = loadConfig(xmls);
@ -581,9 +577,8 @@ public class Main
copyInThread(process.getErrorStream(),System.err);
copyInThread(process.getInputStream(),System.out);
copyInThread(System.in,process.getOutputStream());
monitor.setProcess(process);
process.waitFor();
System.exit(0); // exit JVM when child process ends.
return;
}
@ -688,11 +683,18 @@ public class Main
cmd.addArg(x);
}
cmd.addRawArg("-Djetty.home=" + _jettyHome);
// Special Stop/Shutdown properties
ensureSystemPropertySet("STOP.PORT");
ensureSystemPropertySet("STOP.KEY");
// System Properties
for (String p : _sysProps)
{
String v = System.getProperty(p);
cmd.addEqualsArg("-D" + p,v);
}
cmd.addArg("-cp");
cmd.addRawArg(classpath.toString());
cmd.addRawArg(_config.getMainClassname());
@ -715,6 +717,34 @@ public class Main
return cmd;
}
/**
* Ensure that the System Properties are set (if defined as a System property, or start.config property, or
* start.ini property)
*
* @param key
* the key to be sure of
*/
private void ensureSystemPropertySet(String key)
{
if (_sysProps.contains(key))
{
return; // done
}
Properties props = Config.getProperties();
if (props.containsKey(key))
{
String val = props.getProperty(key,null);
if (val == null)
{
return; // no value to set
}
// setup system property
_sysProps.add(key);
System.setProperty(key,val);
}
}
private String findJavaBin()
{
File javaHome = new File(System.getProperty("java.home"));
@ -928,8 +958,8 @@ public class Main
/**
* Load Configuration.
*
* No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to execute the {@link Class} specified by
* {@link Config#getMainClassname()} is executed.
* No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to
* execute the {@link Class} specified by {@link Config#getMainClassname()} is executed.
*
* @param xmls
* the command line specified xml configuration options.
@ -1010,7 +1040,6 @@ public class Main
stop(port,key,0);
}
public void stop(int port, String key, int timeout)
{
int _port = port;

View File

@ -1,157 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.start;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/*-------------------------------------------*/
/** Monitor thread.
* 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 Monitor extends Thread
{
private Process _process;
private final int _port;
private final String _key;
ServerSocket _socket;
public Monitor(int port,String key)
{
try
{
if(port<0)
return;
setDaemon(true);
setName("StopMonitor");
_socket=new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
if (port==0)
{
port=_socket.getLocalPort();
System.out.println(port);
}
if (key==null)
{
key=Long.toString((long)(Long.MAX_VALUE*Math.random()+this.hashCode()+System.currentTimeMillis()),36);
System.out.println("STOP.KEY="+key);
}
}
catch(Exception e)
{
Config.debug(e);
System.err.println("Error binding monitor port "+port+": "+e.toString());
}
finally
{
_port=port;
_key=key;
}
if (_socket!=null)
this.start();
else
System.err.println("WARN: Not listening on monitor port: "+_port);
}
public Process getProcess()
{
return _process;
}
public void setProcess(Process process)
{
_process = process;
}
@Override
public void run()
{
while (true)
{
Socket socket=null;
try{
socket=_socket.accept();
LineNumberReader lin=
new LineNumberReader(new InputStreamReader(socket.getInputStream()));
String key=lin.readLine();
if (!_key.equals(key))
{
System.err.println("Ignoring command with incorrect key");
continue;
}
String cmd=lin.readLine();
Config.debug("command=" + cmd);
if ("stop".equals(cmd))
{
Config.debug("Child Process: " + _process);
if (_process!=null)
{
//if we have a child process, wait for it to finish before we stop
try
{
_process.destroy();
_process.waitFor();
}
catch (InterruptedException e)
{
System.err.println("Interrupted waiting for child to terminate");
}
}
socket.getOutputStream().write("Stopped\r\n".getBytes());
try {socket.close();}catch(Exception e){e.printStackTrace();}
try {_socket.close();}catch(Exception e){e.printStackTrace();}
System.exit(0);
}
else if ("status".equals(cmd))
{
socket.getOutputStream().write("OK\r\n".getBytes());
socket.getOutputStream().flush();
}
}
catch(Exception e)
{
Config.debug(e);
System.err.println(e.toString());
}
finally
{
if (socket!=null)
{
try{socket.close();}catch(Exception e){}
}
socket=null;
}
}
}
}