aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl/shutdowntimeout/ShutdownTimeout.java
blob: 17fa961f88aa4558ee03d7cad5673182dc70d889 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
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();
    }
}