/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.shutdowntimeout; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.launchutils.*; import org.osgi.framework.*; /** * In order to shut down Jitsi, we kill the Felix system bundle. However, this * sometimes doesn't work for reason of running non-daemon threads (such as the * Java Sound event dispatcher). This results in having instances of Jitsi * running in the background. * * We use this shutdown timeout bundle in order to fix this problem. When our * stop method is called, we assume that a shutdown is executed and start a 15 * seconds daemon thread. If the application is still running once these 15 * seconds expire, we System.exit() the application. * * @author Emil Ivov * @author Lyubomir Marinov */ public class ShutdownTimeout implements BundleActivator { private static final Logger logger = Logger.getLogger(ShutdownTimeout.class); /** * The system property which can be used to set custom timeout. */ private static final String SHUTDOWN_TIMEOUT_PNAME = "org.jitsi.shutdown.SHUTDOWN_TIMEOUT"; /** * The number of milliseconds that we wait before we force a shutdown. */ private static final long SHUTDOWN_TIMEOUT_DEFAULT = 5000;//ms /** * The code that we exit with if the application is not down in 15 seconds. */ private static final int SYSTEM_EXIT_CODE = 500; /** * Runs in a daemon thread started by {@link #stop(BundleContext)} and * forcibly terminates the currently running Java virtual machine after * {@link #SHUTDOWN_TIMEOUT_DEFAULT} (or {@link #SHUTDOWN_TIMEOUT_PNAME}) * milliseconds. */ private static void runInShutdownTimeoutThread() { long shutdownTimeout = SHUTDOWN_TIMEOUT_DEFAULT; // Check for a custom value specified through a System property. try { String s = System.getProperty(SHUTDOWN_TIMEOUT_PNAME); if ((s != null) && (s.length() > 0)) { long l = Long.valueOf(s); // Make sure custom is not 0 to prevent waiting forever. if (l > 0) shutdownTimeout = l; } } catch(Throwable t) { } if (logger.isTraceEnabled()) { logger.trace( "Starting shutdown countdown of " + shutdownTimeout + "ms."); } try { Thread.sleep(shutdownTimeout); } catch (InterruptedException ex) { if (logger.isDebugEnabled()) logger.debug("Interrupted shutdown timer."); return; } /* * We are going to forcibly terminate the currently running Java virtual * machine so it will not run DeleteOnExitHook. But the currently * running Java virtual machine is still going to terminate because of * our intention, not because it has crashed. Make sure that we delete * any files registered for deletion when Runtime.halt(int) is to be * invoked. */ try { DeleteOnHaltHook.runHooks(); } catch (Throwable t) { logger.warn("Failed to delete files on halt.", t); } logger.error("Failed to gently shutdown. Forcing exit."); Runtime.getRuntime().halt(SYSTEM_EXIT_CODE); } /** * Dummy impl of the bundle activator start method. * * @param context unused * @throws Exception if this method throws an exception (which won't happen) */ public void start(BundleContext context) throws Exception { if (logger.isDebugEnabled()) logger.debug("Starting the ShutdownTimeout service."); } /** * Called when this bundle is stopped so the Framework can perform the * bundle-specific activities necessary to stop the bundle. * * @param context The execution context of the bundle being stopped. * @throws Exception If this method throws an exception, the bundle is still * marked as stopped, and the Framework will remove the bundle's listeners, * unregister all services registered by the bundle, and release all * services used by the bundle. */ public void stop(BundleContext context) throws Exception { Thread shutdownTimeoutThread = new Thread() { @Override public void run() { runInShutdownTimeoutThread(); } }; shutdownTimeoutThread.setDaemon(true); shutdownTimeoutThread.setName(ShutdownTimeout.class.getName()); shutdownTimeoutThread.start(); } }