View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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.nio.charset.StandardCharsets;
29  import java.util.Arrays;
30  import java.util.List;
31  import java.util.Properties;
32  import java.util.Set;
33  import java.util.concurrent.CopyOnWriteArrayList;
34  import java.util.concurrent.CopyOnWriteArraySet;
35  
36  import org.eclipse.jetty.util.component.Destroyable;
37  import org.eclipse.jetty.util.component.LifeCycle;
38  import org.eclipse.jetty.util.thread.ShutdownThread;
39  
40  /**
41   * Shutdown/Stop Monitor thread.
42   * <p>
43   * 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
44   * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
45   * <p>
46   * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
47   * <p>
48   * Commands "stop" and "status" are currently supported.
49   */
50  public class ShutdownMonitor 
51  {
52      private final Set<LifeCycle> _lifeCycles = new CopyOnWriteArraySet<LifeCycle>();
53      
54      // Implementation of safe lazy init, using Initialization on Demand Holder technique.
55      static class Holder
56      {
57          static ShutdownMonitor instance = new ShutdownMonitor();
58      }
59  
60      public static ShutdownMonitor getInstance()
61      {
62          return Holder.instance;
63      }
64      
65      /* ------------------------------------------------------------ */
66      public static synchronized void register(LifeCycle... lifeCycles)
67      {
68          getInstance()._lifeCycles.addAll(Arrays.asList(lifeCycles));
69      }
70  
71     
72      /* ------------------------------------------------------------ */
73      public static synchronized void deregister(LifeCycle lifeCycle)
74      {
75          getInstance()._lifeCycles.remove(lifeCycle);
76      }
77      
78      /* ------------------------------------------------------------ */
79      public static synchronized boolean isRegistered(LifeCycle lifeCycle)
80      {
81          return getInstance()._lifeCycles.contains(lifeCycle);
82      }
83      
84  
85      /**
86       * ShutdownMonitorRunnable
87       *
88       * Thread for listening to STOP.PORT for command to stop Jetty.
89       * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be
90       * called after the stop.
91       *
92       */
93      private class ShutdownMonitorRunnable implements Runnable
94      {
95          public ShutdownMonitorRunnable()
96          {
97              startListenSocket();
98          }
99          
100         @Override
101         public void run()
102         {
103             if (serverSocket == null)
104             {
105                 return;
106             }
107 
108             while (serverSocket != null)
109             {
110                 Socket socket = null;
111                 try
112                 {
113                     socket = serverSocket.accept();
114 
115                     LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
116                     String receivedKey = lin.readLine();
117                     if (!key.equals(receivedKey))
118                     {
119                         System.err.println("Ignoring command with incorrect key");
120                         continue;
121                     }
122 
123                     OutputStream out = socket.getOutputStream();
124 
125                     String cmd = lin.readLine();
126                     debug("command=%s",cmd);
127                     if ("stop".equalsIgnoreCase(cmd)) //historic, for backward compatibility
128                     {
129                         //Stop the lifecycles, only if they are registered with the ShutdownThread, only destroying if vm is exiting
130                         debug("Issuing stop...");
131                         
132                         for (LifeCycle l:_lifeCycles)
133                         {
134                             try
135                             {
136                                 if (l.isStarted() && ShutdownThread.isRegistered(l))
137                                 {
138                                     l.stop();
139                                 }
140                                 
141                                 if ((l instanceof Destroyable) && exitVm)
142                                     ((Destroyable)l).destroy();
143                             }
144                             catch (Exception e)
145                             {
146                                 debug(e);
147                             }
148                         }
149 
150                         //Stop accepting any more commands
151                         stopInput(socket);
152 
153                         // Reply to client
154                         debug("Informing client that we are stopped.");
155                         informClient(out, "Stopped\r\n");
156 
157                         //Stop the output and close the monitor socket
158                         stopOutput(socket);
159 
160                         if (exitVm)
161                         {
162                             // Kill JVM
163                             debug("Killing JVM");
164                             System.exit(0);
165                         }
166                     }
167                     else if ("forcestop".equalsIgnoreCase(cmd))
168                     {
169                         debug("Issuing force stop...");
170                         
171                         //Ensure that objects are stopped, destroyed only if vm is forcibly exiting
172                         stopLifeCycles(exitVm);
173 
174                         //Stop accepting any more commands
175                         stopInput(socket);
176 
177                         // Reply to client
178                         debug("Informing client that we are stopped.");
179                         informClient(out, "Stopped\r\n");
180 
181                         //Stop the output and close the monitor socket
182                         stopOutput(socket);
183                         
184                         //Honour any pre-setup config to stop the jvm when this command is given
185                         if (exitVm)
186                         {
187                             // Kill JVM
188                             debug("Killing JVM");
189                             System.exit(0);
190                         }
191                     }
192                     else if ("stopexit".equalsIgnoreCase(cmd))
193                     {
194                         debug("Issuing stop and exit...");
195                         //Make sure that objects registered with the shutdown thread will be stopped
196                         stopLifeCycles(true);
197                         
198                         //Stop accepting any more input
199                         stopInput(socket);
200 
201                         // Reply to client
202                         debug("Informing client that we are stopped.");                       
203                         informClient(out, "Stopped\r\n");              
204 
205                         //Stop the output and close the monitor socket
206                         stopOutput(socket);
207                         
208                         debug("Killing JVM");
209                         System.exit(0);
210                     }
211                     else if ("exit".equalsIgnoreCase(cmd))
212                     {
213                         debug("Killing JVM");
214                         System.exit(0);
215                     }
216                     else if ("status".equalsIgnoreCase(cmd))
217                     {
218                         // Reply to client
219                         informClient(out, "OK\r\n");
220                     }
221                 }
222                 catch (Exception e)
223                 {
224                     debug(e);
225                     System.err.println(e.toString());
226                 }
227                 finally
228                 {
229                     close(socket);
230                     socket = null;
231                 }
232             }
233         }
234         
235         public void stopInput (Socket socket)
236         {
237             //Stop accepting any more input
238             close(serverSocket);
239             serverSocket = null;
240             //Shutdown input from client
241             shutdownInput(socket);  
242         }
243         
244         public void stopOutput (Socket socket) throws IOException
245         {
246             socket.shutdownOutput();
247             close(socket);
248             socket = null;                        
249             debug("Shutting down monitor");
250             serverSocket = null;
251         }
252         
253         public void informClient (OutputStream out, String message) throws IOException
254         {
255             out.write(message.getBytes(StandardCharsets.UTF_8));
256             out.flush();
257         }
258 
259         /**
260          * Stop the registered lifecycles, optionally
261          * calling destroy on them.
262          * 
263          * @param destroy
264          */
265         public void stopLifeCycles (boolean destroy)
266         {
267             for (LifeCycle l:_lifeCycles)
268             {
269                 try
270                 {
271                     if (l.isStarted())
272                     {
273                         l.stop();
274                     }
275                     
276                     if ((l instanceof Destroyable) && destroy)
277                         ((Destroyable)l).destroy();
278                 }
279                 catch (Exception e)
280                 {
281                     debug(e);
282                 }
283             }
284         }
285 
286         public void startListenSocket()
287         {
288             if (port < 0)
289             {            
290                 if (DEBUG)
291                     System.err.println("ShutdownMonitor not in use (port < 0): " + port);
292                 return;
293             }
294 
295             try
296             {
297                 serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
298                 if (port == 0)
299                 {
300                     // server assigned port in use
301                     port = serverSocket.getLocalPort();
302                     System.out.printf("STOP.PORT=%d%n",port);
303                 }
304 
305                 if (key == null)
306                 {
307                     // create random key
308                     key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
309                     System.out.printf("STOP.KEY=%s%n",key);
310                 }
311             }
312             catch (Exception e)
313             {
314                 debug(e);
315                 System.err.println("Error binding monitor port " + port + ": " + e.toString());
316                 serverSocket = null;
317             }
318             finally
319             {
320                 // establish the port and key that are in use
321                 debug("STOP.PORT=%d",port);
322                 debug("STOP.KEY=%s",key);
323                 debug("%s",serverSocket);
324             }
325         }
326 
327     }
328     
329     private boolean DEBUG;
330     private int port;
331     private String key;
332     private boolean exitVm;
333     private ServerSocket serverSocket;
334     private Thread thread;
335 
336     /**
337      * Create a ShutdownMonitor using configuration from the System properties.
338      * <p>
339      * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br>
340      * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br>
341      * <p>
342      * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call.
343      */
344     private ShutdownMonitor()
345     {
346         Properties props = System.getProperties();
347 
348         this.DEBUG = props.containsKey("DEBUG");
349 
350         // Use values passed thru via /jetty-start/
351         this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
352         this.key = props.getProperty("STOP.KEY",null);
353         this.exitVm = true;
354     }
355 
356     private void close(ServerSocket server)
357     {
358         if (server == null)
359         {
360             return;
361         }
362 
363         try
364         {
365             server.close();
366         }
367         catch (IOException ignore)
368         {
369             debug(ignore);
370         }
371     }
372 
373     private void close(Socket socket)
374     {
375         if (socket == null)
376         {
377             return;
378         }
379 
380         try
381         {
382             socket.close();
383         }
384         catch (IOException ignore)
385         {
386             debug(ignore);
387         }
388     }
389 
390     
391     private void shutdownInput(Socket socket)
392     {
393         if (socket == null)
394             return;
395         
396         try
397         {
398             socket.shutdownInput();
399         }   
400         catch (IOException ignore)
401         {
402             debug(ignore);
403         }
404     }
405     
406     
407     private void debug(String format, Object... args)
408     {
409         if (DEBUG)
410         {
411             System.err.printf("[ShutdownMonitor] " + format + "%n",args);
412         }
413     }
414 
415     private void debug(Throwable t)
416     {
417         if (DEBUG)
418         {
419             t.printStackTrace(System.err);
420         }
421     }
422 
423     public String getKey()
424     {
425         return key;
426     }
427 
428     public int getPort()
429     {
430         return port;
431     }
432 
433     public ServerSocket getServerSocket()
434     {
435         return serverSocket;
436     }
437 
438     public boolean isExitVm()
439     {
440         return exitVm;
441     }
442 
443 
444     public void setDebug(boolean flag)
445     {
446         this.DEBUG = flag;
447     }
448 
449     /**
450      * @param exitVm
451      */
452     public void setExitVm(boolean exitVm)
453     {
454         synchronized (this)
455         {
456             if (thread != null && thread.isAlive())
457             {
458                 throw new IllegalStateException("ShutdownMonitorThread already started");
459             }
460             this.exitVm = exitVm;
461         }
462     }
463 
464     public void setKey(String key)
465     {
466         synchronized (this)
467         {
468             if (thread != null && thread.isAlive())
469             {
470                 throw new IllegalStateException("ShutdownMonitorThread already started");
471             }
472             this.key = key;
473         }
474     }
475 
476     public void setPort(int port)
477     {
478         synchronized (this)
479         {
480             if (thread != null && thread.isAlive())
481             {
482                 throw new IllegalStateException("ShutdownMonitorThread already started");
483             }
484             this.port = port;
485         }
486     }
487 
488     protected void start() throws Exception
489     {
490         Thread t = null;
491         
492         synchronized (this)
493         {
494             if (thread != null && thread.isAlive())
495             {
496                 if (DEBUG)
497                     System.err.printf("ShutdownMonitorThread already started");
498                 return; // cannot start it again
499             }
500          
501             thread = new Thread(new ShutdownMonitorRunnable());
502             thread.setDaemon(true);
503             thread.setName("ShutdownMonitor");
504             t = thread;
505         }
506          
507         if (t != null)
508             t.start();
509     }
510 
511 
512     protected boolean isAlive ()
513     {
514         boolean result = false;
515         synchronized (this)
516         {
517             result = (thread != null && thread.isAlive());
518         }
519         return result;
520     }
521     
522  
523     @Override
524     public String toString()
525     {
526         return String.format("%s[port=%d]",this.getClass().getName(),port);
527     }
528 }