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 this.DEBUG = System.getProperty("DEBUG") != null;
349
350
351 this.host = System.getProperty("STOP.HOST","127.0.0.1");
352 this.port = Integer.parseInt(System.getProperty("STOP.PORT","-1"));
353 this.key = System.getProperty("STOP.KEY",null);
354 this.exitVm = true;
355 }
356
357 private void close(ServerSocket server)
358 {
359 if (server == null)
360 {
361 return;
362 }
363
364 try
365 {
366 server.close();
367 }
368 catch (IOException ignore)
369 {
370 debug(ignore);
371 }
372 }
373
374 private void close(Socket socket)
375 {
376 if (socket == null)
377 {
378 return;
379 }
380
381 try
382 {
383 socket.close();
384 }
385 catch (IOException ignore)
386 {
387 debug(ignore);
388 }
389 }
390
391
392 private void shutdownInput(Socket socket)
393 {
394 if (socket == null)
395 return;
396
397 try
398 {
399 socket.shutdownInput();
400 }
401 catch (IOException ignore)
402 {
403 debug(ignore);
404 }
405 }
406
407
408 private void debug(String format, Object... args)
409 {
410 if (DEBUG)
411 {
412 System.err.printf("[ShutdownMonitor] " + format + "%n",args);
413 }
414 }
415
416 private void debug(Throwable t)
417 {
418 if (DEBUG)
419 {
420 t.printStackTrace(System.err);
421 }
422 }
423
424 public String getKey()
425 {
426 return key;
427 }
428
429 public int getPort()
430 {
431 return port;
432 }
433
434 public ServerSocket getServerSocket()
435 {
436 return serverSocket;
437 }
438
439 public boolean isExitVm()
440 {
441 return exitVm;
442 }
443
444
445 public void setDebug(boolean flag)
446 {
447 this.DEBUG = flag;
448 }
449
450
451
452
453 public void setExitVm(boolean exitVm)
454 {
455 synchronized (this)
456 {
457 if (thread != null && thread.isAlive())
458 {
459 throw new IllegalStateException("ShutdownMonitorThread already started");
460 }
461 this.exitVm = exitVm;
462 }
463 }
464
465 public void setKey(String key)
466 {
467 synchronized (this)
468 {
469 if (thread != null && thread.isAlive())
470 {
471 throw new IllegalStateException("ShutdownMonitorThread already started");
472 }
473 this.key = key;
474 }
475 }
476
477 public void setPort(int port)
478 {
479 synchronized (this)
480 {
481 if (thread != null && thread.isAlive())
482 {
483 throw new IllegalStateException("ShutdownMonitorThread already started");
484 }
485 this.port = port;
486 }
487 }
488
489 protected void start() throws Exception
490 {
491 Thread t = null;
492
493 synchronized (this)
494 {
495 if (thread != null && thread.isAlive())
496 {
497 if (DEBUG)
498 System.err.printf("ShutdownMonitorThread already started");
499 return;
500 }
501
502 thread = new Thread(new ShutdownMonitorRunnable());
503 thread.setDaemon(true);
504 thread.setName("ShutdownMonitor");
505 t = thread;
506 }
507
508 if (t != null)
509 t.start();
510 }
511
512
513 protected boolean isAlive ()
514 {
515 boolean result = false;
516 synchronized (this)
517 {
518 result = (thread != null && thread.isAlive());
519 }
520 return result;
521 }
522
523
524 @Override
525 public String toString()
526 {
527 return String.format("%s[port=%d]",this.getClass().getName(),port);
528 }
529 }