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.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
42
43
44
45
46
47
48
49
50 public class ShutdownMonitor
51 {
52 private final Set<LifeCycle> _lifeCycles = new CopyOnWriteArraySet<LifeCycle>();
53
54
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
87
88
89
90
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))
128 {
129
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
151 stopInput(socket);
152
153
154 debug("Informing client that we are stopped.");
155 informClient(out, "Stopped\r\n");
156
157
158 stopOutput(socket);
159
160 if (exitVm)
161 {
162
163 debug("Killing JVM");
164 System.exit(0);
165 }
166 }
167 else if ("forcestop".equalsIgnoreCase(cmd))
168 {
169 debug("Issuing force stop...");
170
171
172 stopLifeCycles(exitVm);
173
174
175 stopInput(socket);
176
177
178 debug("Informing client that we are stopped.");
179 informClient(out, "Stopped\r\n");
180
181
182 stopOutput(socket);
183
184
185 if (exitVm)
186 {
187
188 debug("Killing JVM");
189 System.exit(0);
190 }
191 }
192 else if ("stopexit".equalsIgnoreCase(cmd))
193 {
194 debug("Issuing stop and exit...");
195
196 stopLifeCycles(true);
197
198
199 stopInput(socket);
200
201
202 debug("Informing client that we are stopped.");
203 informClient(out, "Stopped\r\n");
204
205
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
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
238 close(serverSocket);
239 serverSocket = null;
240
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
261
262
263
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
301 port = serverSocket.getLocalPort();
302 System.out.printf("STOP.PORT=%d%n",port);
303 }
304
305 if (key == null)
306 {
307
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
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
338
339
340
341
342
343
344 private ShutdownMonitor()
345 {
346 Properties props = System.getProperties();
347
348 this.DEBUG = props.containsKey("DEBUG");
349
350
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
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;
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 }