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