Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Library shutdown routine that works well in a 'normal' Java application and in a web application

I maintain a JDBC driver that also has an embedded database server mode provided through a native library (accessed through JNA). The shutdown done as part of the unload of the native library itself runs into problem on Windows due to the order of unload of its dependencies. To avoid access violations or other problems, I need to explicitly shutdown the embedded engine before this library is unloaded.

Given the nature of its use, it is hard to determine an appropriate moment to call shutdown, and the only correct way for a normal Java application I see right now is to register a shutdown hook using Runtime.getRuntime().addShutdownHook with a subclass of Thread that implements the shutdown logic.

This works fine for a normal Java application, but for web applications that include my library as part of the application (in the WEB-INF/lib of the WAR), this will cause a memory leak on undeploy as the shutdown hook will maintain a strong reference to my shutdown implementation and to the classloader of the web application.

What would be a suitable and appropriate way to address this? Options I'm looking into right now are:

  • Using java.sql.DriverAction.deregister() to do the cleanup.

    Not suitable as a driver will not be deregistered on a normal application exit.

  • Using java.sql.DriverAction.deregister() to remove the shutdown hook and execute the shutdown logic itself.

    Use of DriverAction is slightly problematic given the driver still supports Java 7, and this class was introduced in JDBC 4.2 (Java 8). This is technically not always the correct use of action (a JDBC driver can also be deregistered while existing connections remain valid and in use), and it is possible that the driver is used (through a javax.sql.DataSource) while the JDBC java.sql.Driver implementation is not registered.

  • Including a javax.servlet.ServletContextListener implementation annotated with @WebListener with the driver that will remove the shutdown hook and execute the shutdown logic itself.

    This option has complications if the driver is deployed to the server as a whole instead of to a specific web application (although those complications can be solved).

Is there a shutdown mechanism in Java I have overlooked that could be suitable for my needs?

like image 322
Mark Rotteveel Avatar asked Aug 04 '18 11:08

Mark Rotteveel


People also ask

What are shutdown hooks in Java?

A shutdown hook is simply an initialized but unstarted thread. When the virtual machine begins its shutdown sequence it will start all registered shutdown hooks in some unspecified order and let them run concurrently.

Which class and method you would use to add a shutdown hook?

We can use java. lang. Runtime. addShutdownHook(Thread t) method to add a shutdown hook in the JVM.

What is shutdown hook in spring?

registerShutdownHook() method in Spring In Java programming language you can create shutdown hooks, where you create a new thread and provide logic that is executed when the JVM is shutting down. Then you can register your thread class instance as a shutdown hook to the VM using Runtime.

How do I shut down JVM?

The JVM shuts down when: user presses ctrl+c on the command prompt. System. exit(int) method is invoked.

How to shutdown computer in Java programming?

To shutdown computer in Java programming, you have to use the command shutdown -s. You can also specify the time in seconds, after which you want to turn off or shutdown the PC, using shutdown -s -t seconds . Where seconds refers to the number of seconds.

What are shutdown hooks in JVM?

Shutdown hooks are called when the application terminates normally (when all threads finish, or when System.exit(0) is called). Also, when the JVM is shutting down due to external causes such as a user requesting a termination (Ctrl+C), a SIGTERM being issued by O/S (normal kill command, without -9),...

How to restart the computer after 5 seconds in Java?

The only change we need to do, from the program given in the section Shutdown Computer after 5 Seconds in Java , is to change the code from shutdown -s -t to shutdown -r -t, to restart the computer after 5 seconds. Here is the complete version of the program.

Can We have more than one shutdown hook in Java?

We can have more than one Shutdown Hooks, but their execution order is not guaranteed. As you might have correctly guessed by the method name of the addShutdownHook method (instead of setShutdownHook), you can register more than one shutdown hook. But the execution order of these multiple hooks is not guaranteed by the JVM.


1 Answers

I tried to figure this out as this seems such an interesting case. I'm posting my findings here, although I feel I might still have misunderstood something, or made some too far-fetched simplifications. Actually, it's also possible that I totally misunderstood your case, and this answer is all useless (if so, I apologize).

What I assembled here is based on two notions:

  • application server global state (I use System.props, but it might not be the best choice - perhaps some temporary files would do better)
  • container-specific global state (which means all classes loaded by the container-specific ClassLoader)

I propose an EmbeddedEngineHandler.loadEmbeddedEngineIfNeeded method that would be called:

  • during your driver registration
  • in your javax.sql.DataSource implementation static initializer (if this whole DataSource-related thing works that way - I know little about it)

If I got it right, you won't need to call Runtime.removeShutdownHook at all.

The main thing that I'm uncertain about here is this - if the driver is deployed globally, would it be registered before any servlet is initialized? If not, then I got it wrong, and this won't work. But maybe inspecting the ClassLoader of EmbeddedEngineHandler could help then?


This is the EmbeddedEngineHandler:

final class EmbeddedEngineHandler {

    private static final String PREFIX = ""; // some ID for your library here
    private static final String IS_SERVLET_CONTEXT = PREFIX + "-is-servlet-context";
    private static final String GLOBAL_ENGINE_LOADED = PREFIX + "-global-engine-loaded";

    private static final String TRUE = "true";

    private static volatile boolean localEngineLoaded = false;

    // LOADING
    static void loadEmbeddedEngineIfNeeded() {
        if (isServletContext()) {
            // handles only engine per container case
            loadEmbeddedEngineInLocalContextIfNeeded();
        } else {
            // handles both normal Java application & global driver cases
            loadEmbeddedEngineInGlobalContextIfNeeded();
        }

    }

    private static void loadEmbeddedEngineInLocalContextIfNeeded() {
        if (!isGlobalEngineLoaded() && !isLocalEngineLoaded()) { // will not load if we have a global driver
            loadEmbeddedEngine();
            markLocalEngineAsLoaded();
        }
    }

    private static void loadEmbeddedEngineInGlobalContextIfNeeded() {
        if (!isGlobalEngineLoaded()) {
            loadEmbeddedEngine();
            markGlobalEngineAsLoaded();
            Runtime.getRuntime().addShutdownHook(new Thread(EmbeddedEngineHandler::unloadEmbeddedEngine));
        }
    }

    private static void loadEmbeddedEngine() {
    }

    static void unloadEmbeddedEngine() {
    }

    // SERVLET CONTEXT (state shared between containers)
    private static boolean isServletContext() {
        return TRUE.equals(System.getProperty(IS_SERVLET_CONTEXT));
    }

    static void markAsServletContext() {
        System.setProperty(IS_SERVLET_CONTEXT, TRUE);
    }

    // GLOBAL ENGINE (state shared between containers)
    private static boolean isGlobalEngineLoaded() {
        return TRUE.equals(System.getProperty(GLOBAL_ENGINE_LOADED));
    }

    private static void markGlobalEngineAsLoaded() {
        System.setProperty(GLOBAL_ENGINE_LOADED, TRUE);
    }

    // LOCAL ENGINE (container-specific state)
    static boolean isLocalEngineLoaded() {
        return localEngineLoaded;
    }

    private static void markLocalEngineAsLoaded() {
        localEngineLoaded = true;
    }
}

and this is the ServletContextListener:

@WebListener
final class YourServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        EmbeddedEngineHandler.markAsServletContext();
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        if (EmbeddedEngineHandler.isLocalEngineLoaded()) {
            EmbeddedEngineHandler.unloadEmbeddedEngine();
        }
    }
}
like image 200
Tomasz Linkowski Avatar answered Oct 14 '22 09:10

Tomasz Linkowski