All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.server.ShutdownMonitor Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
// 
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// 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.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
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.
 *  

* 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 * for admin requests. *

* If the stop port is set to zero, then a random port is assigned and the * port number is printed to stdout. *

* Commands "stop" and "status" are currently supported. * * @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported. */ @Deprecated(since = "2021-05-27") public class ShutdownMonitor { // Implementation of safe lazy init, using Initialization on Demand Holder technique.@deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported. @Deprecated(since = "2021-05-27") private static class Holder { static ShutdownMonitor instance = new ShutdownMonitor(); } public static ShutdownMonitor getInstance() { return Holder.instance; } protected static void reset() { Holder.instance = new ShutdownMonitor(); } public static void register(LifeCycle... lifeCycles) { getInstance().addLifeCycles(lifeCycles); } public static void deregister(LifeCycle lifeCycle) { getInstance().removeLifeCycle(lifeCycle); } public static boolean isRegistered(LifeCycle lifeCycle) { return getInstance().containsLifeCycle(lifeCycle); } private final Set _lifeCycles = new LinkedHashSet<>(); private boolean debug; private final String host; private int port; private String key; private boolean exitVm = true; private boolean alive; /** * Creates a ShutdownMonitor using configuration from the System properties. *

* STOP.PORT = the port to listen on (empty, null, or values less than 0 disable the stop ability)
* STOP.KEY = the magic key/passphrase to allow the stop
*

* 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; this.host = System.getProperty("STOP.HOST", "127.0.0.1"); this.port = Integer.getInteger("STOP.PORT", -1); this.key = System.getProperty("STOP.KEY", null); // only change the default exitVm setting if STOP.EXIT is explicitly set this.exitVm = Boolean.valueOf(System.getProperty("STOP.EXIT", "true")); } private void addLifeCycles(LifeCycle... lifeCycles) { synchronized (this) { _lifeCycles.addAll(Arrays.asList(lifeCycles)); } } private void removeLifeCycle(LifeCycle lifeCycle) { synchronized (this) { _lifeCycles.remove(lifeCycle); } } private boolean containsLifeCycle(LifeCycle lifeCycle) { synchronized (this) { return _lifeCycles.contains(lifeCycle); } } 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() { synchronized (this) { return key; } } public int getPort() { synchronized (this) { return port; } } public boolean isExitVm() { synchronized (this) { 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 (alive) throw new IllegalStateException("ShutdownMonitor already started"); this.exitVm = exitVm; } } public void setKey(String key) { synchronized (this) { if (alive) throw new IllegalStateException("ShutdownMonitor already started"); this.key = key; } } public void setPort(int port) { synchronized (this) { if (alive) throw new IllegalStateException("ShutdownMonitor already started"); this.port = port; } } protected void start() throws Exception { synchronized (this) { if (alive) { debug("Already started"); // cannot start it again return; } ServerSocket serverSocket = listen(); if (serverSocket != null) { alive = true; Thread thread = new Thread(new ShutdownMonitorRunnable(serverSocket)); thread.setDaemon(true); thread.setName("ShutdownMonitor"); thread.start(); } } } private void stop() { synchronized (this) { alive = false; notifyAll(); } } // For test purposes only. void await() throws InterruptedException { synchronized (this) { while (alive) { wait(); } } } protected boolean isAlive() { synchronized (this) { return alive; } } private ServerSocket listen() { int port = getPort(); if (port < 0) { debug("Not enabled (port < 0): %d", port); return null; } String key = getKey(); try { ServerSocket serverSocket = new ServerSocket(); try { serverSocket.setReuseAddress(true); serverSocket.bind(new InetSocketAddress(InetAddress.getByName(host), port)); } catch (Throwable e) { IO.close(serverSocket); throw e; } if (port == 0) { port = serverSocket.getLocalPort(); System.out.printf("STOP.PORT=%d%n", port); setPort(port); } if (key == null) { key = Long.toString((long) (Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()), 36); System.out.printf("STOP.KEY=%s%n", key); setKey(key); } return serverSocket; } catch (Throwable x) { debug(x); System.err.println("Error binding ShutdownMonitor to port " + port + ": " + x.toString()); return null; } finally { // establish the port and key that are in use debug("STOP.PORT=%d", port); debug("STOP.KEY=%s", key); // also show if we're exiting the jvm or not debug("STOP.EXIT=%b", exitVm); } } @Override public String toString() { return String.format("%s[port=%d,alive=%b]", this.getClass().getName(), getPort(), isAlive()); } /** * Thread for listening to STOP.PORT for command to stop Jetty. * If ShutdownMonitor.exitVm is true, then System.exit will also be * called after the stop. * * @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported. */ @Deprecated(since = "2021-05-27") private class ShutdownMonitorRunnable implements Runnable { private final ServerSocket serverSocket; private ShutdownMonitorRunnable(ServerSocket serverSocket) { this.serverSocket = serverSocket; } @Override public void run() { debug("Started"); try { String key = getKey(); while (true) { try (Socket socket = serverSocket.accept()) { LineNumberReader reader = new LineNumberReader(new InputStreamReader(socket.getInputStream())); String receivedKey = reader.readLine(); if (!key.equals(receivedKey)) { debug("Ignoring command with incorrect key: %s", receivedKey); continue; } String cmd = reader.readLine(); debug("command=%s", cmd); OutputStream out = socket.getOutputStream(); boolean exitVm = isExitVm(); if (// historic, for backward compatibility "stop".equalsIgnoreCase(cmd)) { // Stop the lifecycles, only if they are registered with the ShutdownThread, only destroying if vm is exiting debug("Performing stop command"); stopLifeCycles(ShutdownThread::isRegistered, exitVm); // Reply to client debug("Informing client that we are stopped"); informClient(out, "Stopped\r\n"); if (!exitVm) break; // Kill JVM debug("Killing JVM"); System.exit(0); } else if ("forcestop".equalsIgnoreCase(cmd)) { debug("Performing forced stop command"); stopLifeCycles(l -> true, exitVm); // Reply to client debug("Informing client that we are stopped"); informClient(out, "Stopped\r\n"); if (!exitVm) break; // Kill JVM debug("Killing JVM"); System.exit(0); } else if ("stopexit".equalsIgnoreCase(cmd)) { debug("Performing stop and exit commands"); stopLifeCycles(ShutdownThread::isRegistered, true); // Reply to client debug("Informing client that we are stopped"); informClient(out, "Stopped\r\n"); 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 (Throwable x) { debug(x); } } } catch (Throwable x) { debug(x); } finally { IO.close(serverSocket); stop(); debug("Stopped"); } } private void informClient(OutputStream out, String message) throws IOException { out.write(message.getBytes(StandardCharsets.UTF_8)); out.flush(); } private void stopLifeCycles(Predicate predicate, boolean destroy) { List lifeCycles = new ArrayList<>(); synchronized (this) { lifeCycles.addAll(_lifeCycles); } for (LifeCycle l : lifeCycles) { try { if (l.isStarted() && predicate.test(l)) l.stop(); if ((l instanceof Destroyable) && destroy) ((Destroyable) l).destroy(); } catch (Throwable x) { debug(x); } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy