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 
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      /**
57       * ShutdownMonitorThread
58       *
59       * Thread for listening to STOP.PORT for command to stop Jetty.
60       * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be
61       * called after the stop.
62       *
63       */
64      public class ShutdownMonitorThread extends Thread
65      {
66  
67          public ShutdownMonitorThread ()
68          {
69              setDaemon(true);
70              setName("ShutdownMonitor");
71          }
72          
73          @Override
74          public void run()
75          {
76              if (serverSocket == null)
77              {
78                  return;
79              }
80  
81              while (serverSocket != null)
82              {
83                  Socket socket = null;
84                  try
85                  {
86                      socket = serverSocket.accept();
87  
88                      LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
89                      String receivedKey = lin.readLine();
90                      if (!key.equals(receivedKey))
91                      {
92                          System.err.println("Ignoring command with incorrect key");
93                          continue;
94                      }
95  
96                      OutputStream out = socket.getOutputStream();
97  
98                      String cmd = lin.readLine();
99                      debug("command=%s",cmd);
100                     if ("stop".equals(cmd))
101                     {
102                         // Graceful Shutdown
103                         debug("Issuing graceful shutdown..");
104                         ShutdownThread.getInstance().run();
105 
106                         // Reply to client
107                         debug("Informing client that we are stopped.");
108                         out.write("Stopped\r\n".getBytes(StringUtil.__UTF8));
109                         out.flush();
110 
111                         // Shutdown Monitor
112                         debug("Shutting down monitor");
113                         close(socket);
114                         socket = null;
115                         close(serverSocket);
116                         serverSocket = null;
117 
118                         if (exitVm)
119                         {
120                             // Kill JVM
121                             debug("Killing JVM");
122                             System.exit(0);
123                         }
124                     }
125                     else if ("status".equals(cmd))
126                     {
127                         // Reply to client
128                         out.write("OK\r\n".getBytes(StringUtil.__UTF8));
129                         out.flush();
130                     }
131                 }
132                 catch (Exception e)
133                 {
134                     debug(e);
135                     System.err.println(e.toString());
136                 }
137                 finally
138                 {
139                     close(socket);
140                     socket = null;
141                 }
142             }
143         }
144         
145         public void start()
146         {
147             if (isAlive())
148             {
149                 System.err.printf("ShutdownMonitorThread already started");
150                 return; // cannot start it again
151             }
152 
153             startListenSocket();
154             
155             if (serverSocket == null)
156             {
157                 return;
158             }
159             if (DEBUG)
160                 System.err.println("Starting ShutdownMonitorThread");
161             super.start();
162         }
163         
164         private void startListenSocket()
165         {
166             if (port < 0)
167             {            
168                 if (DEBUG)
169                     System.err.println("ShutdownMonitor not in use (port < 0): " + port);
170                 return;
171             }
172 
173             try
174             {
175                 serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
176                 if (port == 0)
177                 {
178                     // server assigned port in use
179                     port = serverSocket.getLocalPort();
180                     System.out.printf("STOP.PORT=%d%n",port);
181                 }
182 
183                 if (key == null)
184                 {
185                     // create random key
186                     key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
187                     System.out.printf("STOP.KEY=%s%n",key);
188                 }
189             }
190             catch (Exception e)
191             {
192                 debug(e);
193                 System.err.println("Error binding monitor port " + port + ": " + e.toString());
194                 serverSocket = null;
195             }
196             finally
197             {
198                 // establish the port and key that are in use
199                 debug("STOP.PORT=%d",port);
200                 debug("STOP.KEY=%s",key);
201                 debug("%s",serverSocket);
202             }
203         }
204 
205     }
206     
207     private boolean DEBUG;
208     private int port;
209     private String key;
210     private boolean exitVm;
211     private ServerSocket serverSocket;
212     private ShutdownMonitorThread thread;
213     
214     
215 
216     /**
217      * Create a ShutdownMonitor using configuration from the System properties.
218      * <p>
219      * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br>
220      * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br>
221      * <p>
222      * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call.
223      */
224     private ShutdownMonitor()
225     {
226         Properties props = System.getProperties();
227 
228         this.DEBUG = props.containsKey("DEBUG");
229 
230         // Use values passed thru via /jetty-start/
231         this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
232         this.key = props.getProperty("STOP.KEY",null);
233         this.exitVm = true;
234     }
235 
236     private void close(ServerSocket server)
237     {
238         if (server == null)
239         {
240             return;
241         }
242 
243         try
244         {
245             server.close();
246         }
247         catch (IOException ignore)
248         {
249             /* ignore */
250         }
251     }
252 
253     private void close(Socket socket)
254     {
255         if (socket == null)
256         {
257             return;
258         }
259 
260         try
261         {
262             socket.close();
263         }
264         catch (IOException ignore)
265         {
266             /* ignore */
267         }
268     }
269 
270     private void debug(String format, Object... args)
271     {
272         if (DEBUG)
273         {
274             System.err.printf("[ShutdownMonitor] " + format + "%n",args);
275         }
276     }
277 
278     private void debug(Throwable t)
279     {
280         if (DEBUG)
281         {
282             t.printStackTrace(System.err);
283         }
284     }
285 
286     public String getKey()
287     {
288         return key;
289     }
290 
291     public int getPort()
292     {
293         return port;
294     }
295 
296     public ServerSocket getServerSocket()
297     {
298         return serverSocket;
299     }
300 
301     public boolean isExitVm()
302     {
303         return exitVm;
304     }
305 
306 
307     public void setDebug(boolean flag)
308     {
309         this.DEBUG = flag;
310     }
311 
312     public void setExitVm(boolean exitVm)
313     {
314         synchronized (this)
315         {
316             if (thread != null && thread.isAlive())
317             {
318                 throw new IllegalStateException("ShutdownMonitorThread already started");
319             }
320             this.exitVm = exitVm;
321         }
322     }
323 
324     public void setKey(String key)
325     {
326         synchronized (this)
327         {
328             if (thread != null && thread.isAlive())
329             {
330                 throw new IllegalStateException("ShutdownMonitorThread already started");
331             }
332             this.key = key;
333         }
334     }
335 
336     public void setPort(int port)
337     {
338         synchronized (this)
339         {
340             if (thread != null && thread.isAlive())
341             {
342                 throw new IllegalStateException("ShutdownMonitorThread already started");
343             }
344             this.port = port;
345         }
346     }
347 
348     protected void start() throws Exception
349     {
350         ShutdownMonitorThread t = null;
351         synchronized (this)
352         {
353             if (thread != null && thread.isAlive())
354             {
355                 System.err.printf("ShutdownMonitorThread already started");
356                 return; // cannot start it again
357             }
358          
359             thread = new ShutdownMonitorThread();
360             t = thread;
361         }
362          
363         if (t != null)
364             t.start();
365     }
366 
367 
368     protected boolean isAlive ()
369     {
370         boolean result = false;
371         synchronized (this)
372         {
373             result = (thread != null && thread.isAlive());
374         }
375         return result;
376     }
377     
378  
379     @Override
380     public String toString()
381     {
382         return String.format("%s[port=%d]",this.getClass().getName(),port);
383     }
384 }