View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server;
20  
21  import java.io.IOException;
22  import java.io.InputStreamReader;
23  import java.io.LineNumberReader;
24  import java.io.OutputStream;
25  import java.net.InetAddress;
26  import java.net.ServerSocket;
27  import java.net.Socket;
28  import java.util.Properties;
29  
30  import org.eclipse.jetty.util.StringUtil;
31  import org.eclipse.jetty.util.thread.ShutdownThread;
32  
33  /**
34   * Shutdown/Stop Monitor thread.
35   * <p>
36   * 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
37   * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
38   * <p>
39   * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
40   * <p>
41   * Commands "stop" and "status" are currently supported.
42   */
43  public class ShutdownMonitor extends Thread
44  {
45      // Implementation of safe lazy init, using Initialization on Demand Holder technique.
46      static class Holder
47      {
48          static ShutdownMonitor instance = new ShutdownMonitor();
49      }
50  
51      public static ShutdownMonitor getInstance()
52      {
53          return Holder.instance;
54      }
55  
56      private boolean DEBUG;
57      private int port;
58      private String key;
59      private boolean exitVm;
60      private ServerSocket serverSocket;
61  
62      /**
63       * Create a ShutdownMonitor using configuration from the System properties.
64       * <p>
65       * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br>
66       * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br>
67       * <p>
68       * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call.
69       */
70      private ShutdownMonitor()
71      {
72          Properties props = System.getProperties();
73  
74          this.DEBUG = props.containsKey("DEBUG");
75  
76          // Use values passed thru via /jetty-start/
77          this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
78          this.key = props.getProperty("STOP.KEY","eclipse");
79          this.exitVm = true;
80      }
81  
82      private void close(ServerSocket server)
83      {
84          if (server == null)
85          {
86              return;
87          }
88  
89          try
90          {
91              server.close();
92          }
93          catch (IOException ignore)
94          {
95              /* ignore */
96          }
97      }
98  
99      private void close(Socket socket)
100     {
101         if (socket == null)
102         {
103             return;
104         }
105 
106         try
107         {
108             socket.close();
109         }
110         catch (IOException ignore)
111         {
112             /* ignore */
113         }
114     }
115 
116     private void debug(String format, Object... args)
117     {
118         if (DEBUG)
119         {
120             System.err.printf("[ShutdownMonitor] " + format + "%n",args);
121         }
122     }
123 
124     private void debug(Throwable t)
125     {
126         if (DEBUG)
127         {
128             t.printStackTrace(System.err);
129         }
130     }
131 
132     public String getKey()
133     {
134         return key;
135     }
136 
137     public int getPort()
138     {
139         return port;
140     }
141 
142     public ServerSocket getServerSocket()
143     {
144         return serverSocket;
145     }
146 
147     public boolean isExitVm()
148     {
149         return exitVm;
150     }
151 
152     @Override
153     public void run()
154     {
155         if (serverSocket == null)
156         {
157             return;
158         }
159 
160         while (true)
161         {
162             Socket socket = null;
163             try
164             {
165                 socket = serverSocket.accept();
166 
167                 LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
168                 String key = lin.readLine();
169                 if (!this.key.equals(key))
170                 {
171                     System.err.println("Ignoring command with incorrect key");
172                     continue;
173                 }
174 
175                 OutputStream out = socket.getOutputStream();
176 
177                 String cmd = lin.readLine();
178                 debug("command=%s",cmd);
179                 if ("stop".equals(cmd))
180                 {
181                     // Graceful Shutdown
182                     debug("Issuing graceful shutdown..");
183                     ShutdownThread.getInstance().run();
184 
185                     // Reply to client
186                     debug("Informing client that we are stopped.");
187                     out.write("Stopped\r\n".getBytes(StringUtil.__UTF8));
188                     out.flush();
189 
190                     // Shutdown Monitor
191                     debug("Shutting down monitor");
192                     close(socket);
193                     close(serverSocket);
194 
195                     if (exitVm)
196                     {
197                         // Kill JVM
198                         debug("Killing JVM");
199                         System.exit(0);
200                     }
201                 }
202                 else if ("status".equals(cmd))
203                 {
204                     // Reply to client
205                     out.write("OK\r\n".getBytes(StringUtil.__UTF8));
206                     out.flush();
207                 }
208             }
209             catch (Exception e)
210             {
211                 debug(e);
212                 System.err.println(e.toString());
213             }
214             finally
215             {
216                 close(socket);
217                 socket = null;
218             }
219         }
220     }
221 
222     public void setDebug(boolean flag)
223     {
224         this.DEBUG = flag;
225     }
226 
227     public void setExitVm(boolean exitVm)
228     {
229         if (isAlive())
230         {
231             throw new IllegalStateException("ShutdownMonitor already started");
232         }
233         this.exitVm = exitVm;
234     }
235 
236     public void setKey(String key)
237     {
238         if (isAlive())
239         {
240             throw new IllegalStateException("ShutdownMonitor already started");
241         }
242         this.key = key;
243     }
244 
245     public void setPort(int port)
246     {
247         if (isAlive())
248         {
249             throw new IllegalStateException("ShutdownMonitor already started");
250         }
251         this.port = port;
252     }
253 
254     public void start()
255     {
256         if (isAlive())
257         {
258             System.err.printf("ShutdownMonitor already started");
259             return; // cannot start it again
260         }
261         startListenSocket();
262         if (serverSocket == null)
263         {
264             return;
265         }
266 
267         super.start();
268     }
269 
270     private void startListenSocket()
271     {
272         if (this.port < 0)
273         {            
274             if (DEBUG)
275                 System.err.println("ShutdownMonitor not in use (port < 0): " + port);
276             return;
277         }
278 
279         try
280         {
281             setDaemon(true);
282             setName("ShutdownMonitor");
283 
284             this.serverSocket = new ServerSocket(this.port,1,InetAddress.getByName("127.0.0.1"));
285             if (this.port == 0)
286             {
287                 // server assigned port in use
288                 this.port = serverSocket.getLocalPort();
289                 System.out.printf("STOP.PORT=%d%n",this.port);
290             }
291 
292             if (this.key == null)
293             {
294                 // create random key
295                 this.key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
296                 System.out.printf("STOP.KEY=%s%n",this.key);
297             }
298         }
299         catch (Exception e)
300         {
301             debug(e);
302             System.err.println("Error binding monitor port " + this.port + ": " + e.toString());
303         }
304         finally
305         {
306             // establish the port and key that are in use
307             debug("STOP.PORT=%d",this.port);
308             debug("STOP.KEY=%s",this.key);
309             debug("%s",serverSocket);
310         }
311     }
312 
313     @Override
314     public String toString()
315     {
316         return String.format("%s[port=%d]",this.getClass().getName(),port);
317     }
318 }