View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.common.utility;
21  
22  import org.waarp.common.future.WaarpFuture;
23  import org.waarp.common.logging.SysErrLogger;
24  import org.waarp.common.logging.WaarpLogger;
25  import org.waarp.common.logging.WaarpLoggerFactory;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.lang.management.ManagementFactory;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Map.Entry;
33  import java.util.Timer;
34  import java.util.TimerTask;
35  
36  /**
37   *
38   */
39  public abstract class WaarpShutdownHook extends Thread {
40    /**
41     * Internal Logger
42     */
43    private static final WaarpLogger logger =
44        WaarpLoggerFactory.getLogger(WaarpShutdownHook.class);
45    /**
46     * Sun property pointing the main class and its arguments. Might not be
47     * defined on non Hotspot VM
48     * implementations.
49     */
50    private static final String SUN_JAVA_COMMAND = "sun.java.command";
51    /**
52     * Thread for ShutdownHook
53     */
54    public static WaarpShutdownHook shutdownHook;
55    /**
56     * Set if the program is in shutdown
57     */
58    private static volatile boolean shutdown;
59    /**
60     * Set if the program will start shutdown process
61     */
62    private static volatile boolean shutdownStarted;
63    /**
64     * Set if the program is in shutdown
65     */
66    private static volatile boolean immediate;
67    /**
68     * Set if the Handler is initialized
69     */
70    private static boolean initialized;
71    /**
72     * Is the shutdown finished
73     */
74    private static boolean isShutdownOver;
75    private static String applArgs;
76    private static volatile boolean shouldRestart;
77    private ShutdownConfiguration shutdownConfiguration;
78  
79    protected WaarpShutdownHook(final ShutdownConfiguration configuration) {
80      if (initialized) {
81        shutdownHook.shutdownConfiguration = configuration;
82        setName("WaarpShutdownHook");
83        setDaemon(true);
84        shutdownHook = this;
85        shutdownConfiguration = configuration;
86        return;
87      }
88      shutdownConfiguration = configuration;
89      setName("WaarpShutdownHook");
90      setDaemon(true);
91      shutdownHook = this;
92      initialized = true;
93    }
94  
95    /**
96     * @return the current ShutdownConfiguration
97     */
98    public final ShutdownConfiguration getShutdownConfiguration() {
99      return shutdownConfiguration;
100   }
101 
102   /**
103    * For Server part
104    */
105   public static void addShutdownHook() {
106     if (shutdownHook != null) {
107       Runtime.getRuntime().addShutdownHook(shutdownHook);
108     }
109   }
110 
111   /**
112    * Says if the Process is currently in shutdown
113    *
114    * @return True if already in shutdown
115    */
116   public static boolean isInShutdown() {
117     return shutdown;
118   }
119 
120   /**
121    * @return True if the Shutdown process will start soon
122    */
123   public static boolean isShutdownStarting() {
124     return shutdownStarted;
125   }
126 
127   /**
128    * To specify that shutdown will soon start
129    */
130   public static void shutdownWillStart() {
131     shutdownStarted = true;
132   }
133 
134   /**
135    * This function is the top function to be called when the process is to be
136    * shutdown.
137    *
138    * @param immediateSet
139    */
140   public static void terminate(final boolean immediateSet) {
141     if (immediateSet) {
142       immediate = true;
143     }
144     if (shutdownHook != null) {
145       removeShutdownHook();
146       terminate();
147       shutdownHook = null;
148     } else {
149       logger.error("No ShutdownHook setup");
150       //FBGEXIT DetectionUtils.SystemExit(1)
151     }
152   }
153 
154   /**
155    * For Server part
156    */
157   public static void removeShutdownHook() {
158     if (shutdownHook != null) {
159       Runtime.getRuntime().removeShutdownHook(shutdownHook);
160     }
161   }
162 
163   /**
164    * Intermediary exit function
165    */
166   private static void terminate() {
167     shutdownStarted = true;
168     shutdown = true;
169     if (isShutdownOver || shutdownHook == null) {
170       shutdown = false;
171       shutdownStarted = false;
172       isShutdownOver = false;
173       initialized = false;
174       return;
175     }
176     if (immediate) {
177       shutdownHook.exitService();
178       // Force exit!
179       try {
180         Thread.sleep(shutdownHook.shutdownConfiguration.timeout / 2);
181       } catch (final InterruptedException e) {//NOSONAR
182         SysErrLogger.FAKE_LOGGER.ignoreLog(e);
183       }
184       if (logger.isDebugEnabled()) {
185         final Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
186         for (final Entry<Thread, StackTraceElement[]> entry : map.entrySet()) {
187           printStackTrace(entry.getKey(), entry.getValue());
188         }
189       }
190       isShutdownOver = true;
191       logger.info("Should restart? {}", isRestart());
192       try {
193         restartApplication();
194       } catch (final IOException e1) {
195         SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
196       }
197       shutdownHook.serviceStopped();
198       SysErrLogger.FAKE_LOGGER.syserr("Halt System");
199       try {
200         Thread.sleep(WaarpNettyUtil.SIMPLE_DELAY_MS);
201       } catch (final InterruptedException e) {//NOSONAR
202         SysErrLogger.FAKE_LOGGER.ignoreLog(e);
203       }
204       //FBGEXIT DetectionUtils.SystemExit(0)
205     } else {
206       shutdownHook.launchFinalExit();
207       immediate = true;
208       shutdownHook.exitService();
209       isShutdownOver = true;
210       shutdownHook.serviceStopped();
211       logger.info("Should restart? {}", isRestart());
212       try {
213         restartApplication();
214       } catch (final IOException e1) {
215         SysErrLogger.FAKE_LOGGER.syserr(e1);
216       }
217       logger.info("Exit System");
218       SysErrLogger.FAKE_LOGGER.syserr("Exit System");
219     }
220     shutdown = false;
221     shutdownStarted = false;
222     isShutdownOver = false;
223     initialized = false;
224   }
225 
226   /**
227    * Real exit function
228    */
229   protected abstract void exitService();
230 
231   /**
232    * Print stack trace
233    *
234    * @param thread
235    * @param stacks
236    */
237   private static void printStackTrace(final Thread thread,
238                                       final StackTraceElement[] stacks) {
239     SysErrLogger.FAKE_LOGGER.syserrNoLn(thread + " : ");
240     for (int i = 0; i < stacks.length - 1; i++) {
241       SysErrLogger.FAKE_LOGGER.syserrNoLn(stacks[i] + " ");
242     }
243     if (stacks.length >= 1) {
244       SysErrLogger.FAKE_LOGGER.syserr(stacks[stacks.length - 1]);
245     } else {
246       SysErrLogger.FAKE_LOGGER.syserr();
247     }
248   }
249 
250   /**
251    * @return True if the shutdown should be followed by a restart
252    */
253   public static boolean isRestart() {
254     return shouldRestart;
255   }
256 
257   /**
258    * Restart the application using the preset applArgs and computing the
259    * jvmArgs. execute the command in a
260    * shutdown hook, to be sure that all the resources have been disposed
261    * before
262    * restarting the application
263    *
264    * @throws IOException
265    */
266   private static void restartApplication() throws IOException {
267     if (shouldRestart) {
268       try {
269         // java binary
270         final String java = System.getProperty("java.home") + "/bin/java";
271         // vm arguments
272         final List<String> vmArguments =
273             ManagementFactory.getRuntimeMXBean().getInputArguments();
274         final StringBuilder vmArgsOneLine = new StringBuilder();
275         for (final String arg : vmArguments) {
276           // if it's the agent argument : we ignore it otherwise the
277           // address of the old application and the new one will be in conflict
278           if (!arg.contains("-agentlib")) {
279             vmArgsOneLine.append(arg).append(' ');
280           }
281         }
282         // init the command to execute, add the vm args
283         final StringBuilder cmd;
284         if (DetectionUtils.isWindows()) {
285           cmd = new StringBuilder('"' + java + "\" " + vmArgsOneLine);
286         } else {
287           cmd = new StringBuilder(java + ' ' + vmArgsOneLine);
288         }
289 
290         if (applArgs == null) {
291           applArgs = getArgs();
292         }
293         if (applArgs == null) {
294           // big issue then
295           SysErrLogger.FAKE_LOGGER.syserr("Cannot restart!");
296           // something went wrong
297           throw new IOException(
298               "Error while trying to restart the " + "application");
299         }
300         cmd.append(applArgs);
301         logger.debug("Should restart with:\n{}", cmd);
302         logger.warn("Should restart");
303         Runtime.getRuntime().exec(cmd.toString()); //NOSONAR
304       } catch (final Throwable e) {
305         // something went wrong
306         throw new IOException("Error while trying to restart the application",
307                               e);
308       }
309     }
310   }
311 
312   private boolean serviceStopped() {
313     if (shutdownConfiguration.serviceFuture != null) {
314       logger.info("Service will be stopped");
315       shutdownConfiguration.serviceFuture.setSuccess();
316       return true;
317     }
318     return false;
319   }
320 
321   /**
322    * Extra call to ensure exit after long delay
323    */
324   public final void launchFinalExit() {
325     if (WaarpSystemUtil.isJunit()) {
326       return;
327     }
328     final Timer timer = new Timer("WaarpFinalExit", true);
329     final ShutdownTimerTask timerTask = new ShutdownTimerTask();
330     timer.schedule(timerTask, shutdownConfiguration.timeout * 4);
331   }
332 
333   /**
334    * Try to return the application arguments (for Oracle VM)
335    *
336    * @return null if it cannot
337    */
338   private static String getArgs() {
339     final String test = System.getProperty(SUN_JAVA_COMMAND);
340     if (ParametersChecker.isNotEmpty(test)) {
341       // compute args directly
342       // program main and program arguments
343       final String[] mainCommand = test.split(" ");
344       // program main is a jar
345       final StringBuilder args = new StringBuilder();
346       if (mainCommand[0].endsWith(".jar")) {
347         // if it's a jar, add -jar mainJar
348         args.append("-jar ").append(new File(mainCommand[0]).getPath());
349       } else {
350         // else it's a .class, add the classpath and mainClass
351         args.append("-cp \"").append(System.getProperty("java.class.path"))
352             .append("\" ").append(mainCommand[0]);
353       }
354       // finally add program arguments
355       for (int i = 1; i < mainCommand.length; i++) {
356         args.append(' ').append(mainCommand[i]);
357       }
358       return args.toString();
359     }
360     return null;
361   }
362 
363   /**
364    * Set the way software should shutdown: with (true) or without restart
365    * (false)
366    *
367    * @param toRestart
368    */
369   public static void setRestart(final boolean toRestart) {
370     shouldRestart = toRestart;
371   }
372 
373   /**
374    * Called to setup main class and args to enable restart
375    *
376    * @param main
377    * @param args
378    */
379   public static void registerMain(final Class<?> main, final String[] args) {
380     if (main == null) {
381       applArgs = getArgs();
382       return;
383     }
384     final String path = ManagementFactory.getRuntimeMXBean().getClassPath();
385     final StringBuilder newArgs = new StringBuilder();
386     if (ParametersChecker.isNotEmpty(path)) {
387       newArgs.append("-cp ").append(path);
388     }
389     newArgs.append(' ').append(main.getName());
390     for (final String arg : args) {
391       newArgs.append(' ').append(arg);
392     }
393     applArgs = newArgs.toString();
394   }
395 
396   @Override
397   public void run() {
398     if (isShutdownOver) {
399       if (shutdownHook != null && shutdownHook.serviceStopped()) {
400         try {
401           Thread.sleep(WaarpNettyUtil.SIMPLE_DELAY_MS);
402         } catch (final InterruptedException e) {//NOSONAR
403           SysErrLogger.FAKE_LOGGER.ignoreLog(e);
404         }
405       }
406       // Already stopped
407       SysErrLogger.FAKE_LOGGER.syserr(
408           "Halt System now - services already stopped -");
409       //FBGEXIT DetectionUtils.SystemExit(0)
410       return;
411     }
412     try {
413       terminate(false);
414     } catch (final Throwable t) {
415       if (shutdownHook != null && shutdownHook.serviceStopped()) {
416         try {
417           Thread.sleep(WaarpNettyUtil.SIMPLE_DELAY_MS);
418         } catch (final InterruptedException e) {//NOSONAR
419           SysErrLogger.FAKE_LOGGER.ignoreLog(e);
420         }
421       }
422     }
423     SysErrLogger.FAKE_LOGGER.syserr("Halt System now");
424     //FBGEXIT DetectionUtils.SystemExit(0)
425   }
426 
427   /**
428    * Class for argument of creation of WaarpShutdownHook
429    */
430   public static class ShutdownConfiguration {
431     public long timeout = 30000; // 30s per default
432     public WaarpFuture serviceFuture; // no service per default
433   }
434 
435   /**
436    * Finalize resources attached to handlers
437    */
438   static class ShutdownTimerTask extends TimerTask {
439     /**
440      * Internal Logger
441      */
442     private static final WaarpLogger logger =
443         WaarpLoggerFactory.getLogger(ShutdownTimerTask.class);
444 
445     /**
446      * Internal constructor
447      */
448     ShutdownTimerTask() {
449     }
450 
451     @Override
452     public void run() {
453       SysErrLogger.FAKE_LOGGER.syserr("Halt System now - time waiting is over");
454       logger.error("System will force EXIT");
455       if (shutdownHook != null && shutdownHook.serviceStopped()) {
456         try {
457           Thread.sleep(WaarpNettyUtil.SIMPLE_DELAY_MS);
458         } catch (final InterruptedException e) {//NOSONAR
459           SysErrLogger.FAKE_LOGGER.ignoreLog(e);
460         }
461       }
462       if (logger.isDebugEnabled()) {
463         final Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
464         for (final Entry<Thread, StackTraceElement[]> entry : map.entrySet()) {
465           printStackTrace(entry.getKey(), entry.getValue());
466         }
467       }
468       //FBGEXIT DetectionUtils.SystemExit(0)
469     }
470   }
471 }