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