1

I have a JAX-RS Application (running Jersey 2 on Tomcat) that relies on a heavyweight connection to HBase. I want to initialize and reuse that connection throughout my application for multiple resources. I have set up a Binder that binds the connection as a Singleton, and used the @Inject annotaton to inject that connection into my resource. However, since the injection doesn't occur until the first call of the service, the connection isn't initialized until then.

The application:

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        super(MyResource.class);
        register(new HbaseBinder());
    }
}

The Binder:

public class HbaseBinder extends AbstractBinder {

    @Override
    protected void configure() {
        bindAsContract(HbaseConnection.class).in(Singleton.class);
        bind(new HbaseConnection()).to(HbaseConnection.class);
    }
}

The Injection:

@Path("/myResource")
public class MyResource {

    @Inject
    private HbaseConnection hbaseConnection;

    ...
}

The HBase Connection:

@Singleton
public class HbaseConnection {    
    public Connection getConnection() throws IOException {
        ...
    }
    ...
}

What I would like to do is initialize the Singleton at application deployment time so that it's ready to go on the first call to the service. What is the proper way to do this?

Brian Schrameck
  • 588
  • 1
  • 5
  • 22
  • I would imagine it would initialize when you instantiate it. Is it because the entire Jersey app doesn't load until the first request, or just the connection doesn't load until the first request? – Paul Samsotha Feb 11 '16 at 16:37
  • I know the application gets loaded because I've been able to add a ServletContextListener and it gets executed at deployment time. But the HBase connection doesn't get instantiated until injection time, which isn't until the first call to the myResource endpoint. – Brian Schrameck Feb 11 '16 at 17:53
  • Try and use [Immediate Scope](http://stackoverflow.com/a/28123656/2587435) – Paul Samsotha Feb 11 '16 at 17:56
  • Also I am not sure if the invocation of a ServletContextListener is any indication that the Jersey runtime is started. You can check simply by adding a log/print in your ResourceConfig constructor. If it's not loading, you can just set the load-on-startup for the Jersey servlet. You would need to do this in the web.xml – Paul Samsotha Feb 11 '16 at 18:33
  • Immediate Scope is an overkill, although, it would work. Also, you may not like the threads that HK2 leaves behind when the app gets undeployed (unfortunately, as far as it was tested, this feature/behavior is still present in HK2). Nevermind, it should be enough to configure HK2 as I'm explaining bellow to get the desired eager singleton initialization. – Stepan Vavra Feb 11 '16 at 22:52

2 Answers2

1

Thank you for all of the comments and answers. A combination of the above were required.

Part of the issue was that I was using the the @PostConstruct to call the HbaseConnection.getConnection() method had an unchecked exception. Once I got rid of that, and switch to the Immediate scope, the class seems to be loaded appropriately. Here's my final solution:

public class MyApplication extends ResourceConfig {

    @Inject
    public MyApplication(ServiceLocator locator) {
        super(MyResource.class);
        register(new HbaseBinder());
        ServiceLocatorUtilities.enableImmediateScope(locator);
    }
}

@Path("/myResource")
public class MyResource {    

    @Inject
    private HbaseConnection hbaseConnection;

    ...
}

public class HbaseBinder extends AbstractBinder {

    @Override
    protected void configure() {
        bindAsContract(HbaseConnection.class).in(Immediate.class);
    }
}

@Immediate
public class HbaseConnection {

    @PostConstruct
    public void postConstruct() {
        // Call getConnection(), wrapped in try/catch.
    }

    public Connection getConnection() throws IOException {
        // Get the connection.
    }

    @PreDestroy
    public void preDestroy() {
        // Call cleanup(), wrapped in try/catch.
    }

    public void cleanup() throws IOException {
        // Close/cleanup the connection
    }
}

Now my only problem is that it looks like some Threads/ThreadLocals are being left around on undeployment, but this has to be bugs in the libraries that I'm using as I have no control over their lifecycle.

12-Feb-2016 14:01:45.054 INFO [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.startup.HostConfig.undeploy Undeploying context [/my-resource]
12-Feb-2016 14:01:46.129 WARNING [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [my-resource] appears to have started a thread named [ImmediateThread-1455303641406] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 sun.misc.Unsafe.park(Native Method)
 java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
 java.util.concurrent.SynchronousQueue$TransferQueue.awaitFulfill(SynchronousQueue.java:764)
 java.util.concurrent.SynchronousQueue$TransferQueue.transfer(SynchronousQueue.java:695)
 java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:941)
 java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1066)
 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 java.lang.Thread.run(Thread.java:745)
12-Feb-2016 14:01:46.130 WARNING [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [my-resource] appears to have started a thread named [Thread-5] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 sun.net.dns.ResolverConfigurationImpl.notifyAddrChange0(Native Method)
 sun.net.dns.ResolverConfigurationImpl$AddressChangeListener.run(ResolverConfigurationImpl.java:144)
12-Feb-2016 14:01:46.134 SEVERE [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [my-resource] created a ThreadLocal with key of type [org.apache.htrace.core.Tracer.ThreadLocalContext] (value [org.apache.htrace.core.Tracer$ThreadLocalContext@2c25bbe0]) and a value of type [org.apache.htrace.core.Tracer.ThreadContext] (value [org.apache.htrace.core.Tracer$ThreadContext@787153ae]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
12-Feb-2016 14:01:46.135 SEVERE [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [my-resource] created a ThreadLocal with key of type [org.apache.hadoop.io.Text$1] (value [org.apache.hadoop.io.Text$1@1badb836]) and a value of type [sun.nio.cs.UTF_8.Encoder] (value [sun.nio.cs.UTF_8$Encoder@13652d32]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
12-Feb-2016 14:01:46.136 SEVERE [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [my-resource] created a ThreadLocal with key of type [org.apache.hadoop.hdfs.DFSUtil$1] (value [org.apache.hadoop.hdfs.DFSUtil$1@6080a3db]) and a value of type [java.util.Random] (value [java.util.Random@169acbf5]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
12-Feb-2016 14:03:02.383 INFO [Thread-7] org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading Illegal access: this web application instance has been stopped already. Could not load [org.apache.hadoop.util.ShutdownHookManager$2]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
 java.lang.IllegalStateException: Illegal access: this web application instance has been stopped already. Could not load [org.apache.hadoop.util.ShutdownHookManager$2]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
    at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1353)
    at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForClassLoading(WebappClassLoaderBase.java:1341)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1206)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1167)
    at org.apache.hadoop.util.ShutdownHookManager.getShutdownHooksInOrder(ShutdownHookManager.java:124)
    at org.apache.hadoop.util.ShutdownHookManager$1.run(ShutdownHookManager.java:52)

12-Feb-2016 14:03:02.383 INFO [Thread-4] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-apr-8080"]
12-Feb-2016 14:03:02.449 INFO [Thread-4] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["ajp-apr-8009"]
12-Feb-2016 14:03:02.500 INFO [Thread-4] org.apache.catalina.core.StandardService.stopInternal Stopping service Catalina
12-Feb-2016 14:03:02.530 INFO [Thread-4] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-apr-8080"]
12-Feb-2016 14:03:02.581 INFO [Thread-4] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["ajp-apr-8009"]
Brian Schrameck
  • 588
  • 1
  • 5
  • 22
  • As I said in the comment above, immediate scope leaves threads behind. If you wanted, you could take a look at it and contribute to hk2 with an improvement. :) It's being tracked at https://java.net/jira/browse/JERSEY-2291 (at least from the jersey side). – Stepan Vavra Feb 12 '16 at 21:35
  • @peeskillet I don't get your point. Brian says that threads are left around on undeployment and I'm saying, there's not a simple solution for this and also I'm encouraging him to contribute to hk2. And yes, this is the reason why it wasn't enabled by default in Jersey.. – Stepan Vavra Feb 14 '16 at 07:13
  • @peeskillet who are you anyway? Are you involved in the JAX-RS community or Jersey development? – Stepan Vavra Feb 14 '16 at 07:15
0

Your HbaseConnection gets initialized twice.

  • at first, it initializes in your binder (provided your MyApplication class is registered as init-param in your web.xml)
  • and then, it initializes once again when HK2 detects @Singleton annotation (or when it proceeds the bindAsContract instruction)

As a result, to make your example work, you need to:

  1. remove @Singleton annotation from your HbaseConnection class.
  2. remove line bindAsContract(HbaseConnection.class).in(Singleton.class); from your HBaseBinder class

The initialization of MyApplication happens during the servlet container startup (thus the construction of HbaseConnection) which is what you want, right?

It's you, who's providing the instance (in the binder), not HK2. That's why you don't want to instruct it by using @Singleton to create the instance.

The binder should be:

public class HbaseBinder extends AbstractBinder {

    @Override
    protected void configure() {
        // just bind is ok
        bind(new HbaseConnection()).to(HbaseConnection.class);
    }
}

And the HBase Connection:

// do not annotate with `@Singleton`!
public class HbaseConnection {    
    public Connection getConnection() throws IOException {
        ...
    }
    ...
}
Stepan Vavra
  • 3,884
  • 5
  • 29
  • 40
  • This works, but what if I want to use the PreDestroy annotation to clean up the connection before container shutdown? It seems that my PreDestroy is not called unless the HbaseConnection is annotated with Singleton. – Brian Schrameck Feb 12 '16 at 17:29