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.List;
32 import java.util.Properties;
33 import java.util.Set;
34 import java.util.concurrent.CopyOnWriteArrayList;
35 import java.util.concurrent.CopyOnWriteArraySet;
36
37 import org.eclipse.jetty.util.component.Destroyable;
38 import org.eclipse.jetty.util.component.LifeCycle;
39 import org.eclipse.jetty.util.thread.ShutdownThread;
40
41
42
43
44
45
46
47
48
49
50
51 public class ShutdownMonitor
52 {
53 private final Set<LifeCycle> _lifeCycles = new CopyOnWriteArraySet<LifeCycle>();
54
55
56 static class Holder
57 {
58 static ShutdownMonitor instance = new ShutdownMonitor();
59 }
60
61 public static ShutdownMonitor getInstance()
62 {
63 return Holder.instance;
64 }
65
66
67 public static synchronized void register(LifeCycle... lifeCycles)
68 {
69 getInstance()._lifeCycles.addAll(Arrays.asList(lifeCycles));
70 }
71
72
73
74 public static synchronized void deregister(LifeCycle lifeCycle)
75 {
76 getInstance()._lifeCycles.remove(lifeCycle);
77 }
78
79
80 public static synchronized boolean isRegistered(LifeCycle lifeCycle)
81 {
82 return getInstance()._lifeCycles.contains(lifeCycle);
83 }
84
85
86
87
88
89
90
91
92
93
94 private class ShutdownMonitorRunnable implements Runnable
95 {
96 public ShutdownMonitorRunnable()
97 {
98 startListenSocket();
99 }
100
101 @Override
102 public void run()
103 {
104 if (serverSocket == null)
105 {
106 return;
107 }
108
109 while (serverSocket != null)
110 {
111 Socket socket = null;
112 try
113 {
114 socket = serverSocket.accept();
115
116 LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
117 String receivedKey = lin.readLine();
118 if (!key.equals(receivedKey))
119 {
120 System.err.println("Ignoring command with incorrect key");
121 continue;
122 }
123
124 OutputStream out = socket.getOutputStream();
125
126 String cmd = lin.readLine();
127 debug("command=%s",cmd);
128 if ("stop".equalsIgnoreCase(cmd))
129 {
130
131 debug("Issuing stop...");
132
133 for (LifeCycle l:_lifeCycles)
134 {
135 try
136 {
137 if (l.isStarted() && ShutdownThread.isRegistered(l))
138 {
139 l.stop();
140 }
141
142 if ((l instanceof Destroyable) && exitVm)
143 ((Destroyable)l).destroy();
144 }
145 catch (Exception e)
146 {
147 debug(e);
148 }
149 }
150
151
152 stopInput(socket);
153
154
155 debug("Informing client that we are stopped.");
156 informClient(out, "Stopped\r\n");
157
158
159 stopOutput(socket);
160
161 if (exitVm)
162 {
163
164 debug("Killing JVM");
165 System.exit(0);
166 }
167 }
168 else if ("forcestop".equalsIgnoreCase(cmd))
169 {
170 debug("Issuing force stop...");
171
172
173 stopLifeCycles(exitVm);
174
175
176 stopInput(socket);
177
178
179 debug("Informing client that we are stopped.");
180 informClient(out, "Stopped\r\n");
181
182
183 stopOutput(socket);
184
185
186 if (exitVm)
187 {
188
189 debug("Killing JVM");
190 System.exit(0);
191 }
192 }
193 else if ("stopexit".equalsIgnoreCase(cmd))
194 {
195 debug("Issuing stop and exit...");
196
197 stopLifeCycles(true);
198
199
200 stopInput(socket);
201
202
203 debug("Informing client that we are stopped.");
204 informClient(out, "Stopped\r\n");
205
206
207 stopOutput(socket);
208
209 debug("Killing JVM");
210 System.exit(0);
211 }
212 else if ("exit".equalsIgnoreCase(cmd))
213 {
214 debug("Killing JVM");
215 System.exit(0);
216 }
217 else if ("status".equalsIgnoreCase(cmd))
218 {
219
220 informClient(out, "OK\r\n");
221 }
222 }
223 catch (Exception e)
224 {
225 debug(e);
226 System.err.println(e.toString());
227 }
228 finally
229 {
230 close(socket);
231 socket = null;
232 }
233 }
234 }
235
236 public void stopInput (Socket socket)
237 {
238
239 close(serverSocket);
240 serverSocket = null;
241
242 shutdownInput(socket);
243 }
244
245 public void stopOutput (Socket socket) throws IOException
246 {
247 socket.shutdownOutput();
248 close(socket);
249 socket = null;
250 debug("Shutting down monitor");
251 serverSocket = null;
252 }
253
254 public void informClient (OutputStream out, String message) throws IOException
255 {
256 out.write(message.getBytes(StandardCharsets.UTF_8));
257 out.flush();
258 }
259
260
261
262
263
264
265
266 public void stopLifeCycles (boolean destroy)
267 {
268 for (LifeCycle l:_lifeCycles)
269 {
270 try
271 {
272 if (l.isStarted())
273 {
274 l.stop();
275 }
276
277 if ((l instanceof Destroyable) && destroy)
278 ((Destroyable)l).destroy();
279 }
280 catch (Exception e)
281 {
282 debug(e);
283 }
284 }
285 }
286
287 public void startListenSocket()
288 {
289 if (port < 0)
290 {
291 if (DEBUG)
292 System.err.println("ShutdownMonitor not in use (port < 0): " + port);
293 return;
294 }
295
296 try
297 {
298 serverSocket = new ServerSocket();
299 serverSocket.setReuseAddress(true);
300 serverSocket.bind(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), port), 1);
301 if (port == 0)
302 {
303
304 port = serverSocket.getLocalPort();
305 System.out.printf("STOP.PORT=%d%n",port);
306 }
307
308 if (key == null)
309 {
310
311 key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
312 System.out.printf("STOP.KEY=%s%n",key);
313 }
314 }
315 catch (Exception e)
316 {
317 debug(e);
318 System.err.println("Error binding monitor port " + port + ": " + e.toString());
319 serverSocket = null;
320 }
321 finally
322 {
323
324 debug("STOP.PORT=%d",port);
325 debug("STOP.KEY=%s",key);
326 debug("%s",serverSocket);
327 }
328 }
329
330 }
331
332 private boolean DEBUG;
333 private int port;
334 private String key;
335 private boolean exitVm;
336 private ServerSocket serverSocket;
337 private Thread thread;
338
339
340
341
342
343
344
345
346
347 private ShutdownMonitor()
348 {
349 Properties props = System.getProperties();
350
351 this.DEBUG = props.containsKey("DEBUG");
352
353
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 }