Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: how to change the configuration of ResourceConfig at runtime? #5156

Closed
SkyCrawl opened this issue Sep 12, 2022 · 7 comments · Fixed by #5161
Closed

Question: how to change the configuration of ResourceConfig at runtime? #5156

SkyCrawl opened this issue Sep 12, 2022 · 7 comments · Fixed by #5161
Milestone

Comments

@SkyCrawl
Copy link

What I would like is to be able to change the configuration of my singleton ResourceConfig upon external command. Right now, the configuration is locked after initialization and once that happens, it can not be changed. My ResourceConfig class is as follows:

@Singleton
public class MyApplication extends ResourceConfig {
    public RestApplication() {
        ...; // init (configuration is not locked and can be changed)
    }
    
    public void reload() {
        // called from a resource class
        ...; // reload (configuration is locked and can not be changed)
    }
}

And my web.xml is as follows:

<servlet>
    <servlet-name>REST API</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>cz.example.MyApplication</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>REST API</servlet-name>
    <url-pattern>/rest/*</url-pattern>
</servlet-mapping>

When trying to solve this, I stumbled upon the org.glassfish.jersey.servlet.ServletContainer.reload() method, but couldn't figure out how to obtain the class's instance (injection doesn't work). If I understand correctly, this method will create and initialize a new MyApplication object, and discard the previous one. I think that would achieve what I'm after.

To clarify a little more, I would like to be able to turn monitoring and statistics counting on and off as needed, but perhaps it could be useful for request tracing and other things as well. Originally, my ResourceConfig was not a Singleton and a new instance was created for each request (at least, that was the impression I got), which might also achieve what I'm after, but personally, I consider that needlessly inefficient and wasn't sure about the effects of turning monitoring and statistics on and off (and the correctness of data).

@jansupol
Copy link
Contributor

The key is org.glassfish.jersey.server.spi.ContainerLifecycleListener, please see the Reload example. Does it help?

@SkyCrawl
Copy link
Author

Thank you, I tried to suit the example to my needs:

@Singleton
public class MyApplication extends ResourceConfig {
    private static final MyContainerLifecycleListener LISTENER = new MyContainerLifecycleListener();

    public RestApplication() {
        registerInstances(LISTENER);
    }
    
    public void reload() {
        LISTENER.container.reload();
    }

    private static class MyContainerLifecycleListener implements ContainerLifecycleListener {
        private Container container;

        @Override
        public void onStartup(Container container) {
            this.container = container;
        }

        @Override
        public void onReload(Container container) {
            this.container = container;
        }

        @Override
        public void onShutdown(Container container) {
            this.container = container;
        }
    }
}

but when calling MyApplication.reload() from a Jersey resource class (where the external command is received), I get:

java.lang.IllegalStateException: ServiceLocatorImpl(__HK2_Generated_0,0,510989541) has been shut down
		at org.jvnet.hk2.internal.ServiceLocatorImpl.checkState(ServiceLocatorImpl.java:2383)
		at org.jvnet.hk2.internal.ServiceLocatorImpl.getServiceHandleImpl(ServiceLocatorImpl.java:616)
		at org.jvnet.hk2.internal.ServiceLocatorImpl.getServiceHandle(ServiceLocatorImpl.java:609)
		at org.jvnet.hk2.internal.ServiceLocatorImpl.getServiceHandle(ServiceLocatorImpl.java:627)
		at org.jvnet.hk2.internal.FactoryCreator.getFactoryHandle(FactoryCreator.java:79)
		at org.jvnet.hk2.internal.FactoryCreator.dispose(FactoryCreator.java:149)
		at org.jvnet.hk2.internal.SystemDescriptor.dispose(SystemDescriptor.java:518)
		at org.glassfish.jersey.inject.hk2.RequestContext.lambda$findOrCreate$0(RequestContext.java:60)
		at org.glassfish.jersey.internal.inject.ForeignDescriptorImpl.dispose(ForeignDescriptorImpl.java:63)
		at org.glassfish.jersey.inject.hk2.Hk2RequestScope$Instance.remove(Hk2RequestScope.java:126)
		at java.base/java.lang.Iterable.forEach(Iterable.java:75)
		at org.glassfish.jersey.inject.hk2.Hk2RequestScope$Instance.release(Hk2RequestScope.java:143)
		at org.glassfish.jersey.process.internal.RequestScope.release(RequestScope.java:246)
		at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:267)
		at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234)
		at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:684)
		at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:394)
		at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:346)
		at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:358)
		at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:311)
		at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:205)
		... (Tomcat stack trace)

The external command returns HTTP Status 500 with the above root cause.

I'm running with Jersey 2.36.

Is it necessary to call MyApplication.reload() from an unrelated thread (i.e. not from a Jersey request), or is this a bug?

@jansupol
Copy link
Contributor

The exception is a known issue. It occurs when one instance of the HK2 is shut down, while particular callbacks of Jersey are not shut down yet (race-condition). The new HK2 is started up, and works without the issues, while the callbacks refer to the old HK2 instance, which reports this exception. The exception (intermittent) should not have any impact on the run, once Jersey starts, it should work fine.

If you have a status 500 all the time after the reload, I'd like to see the reproducer.

@SkyCrawl
Copy link
Author

SkyCrawl commented Sep 12, 2022

No, I do not have 500 all the time after reload. Just the requests that try to reload.

Is there a separate GitHub issue for this problem, so I can subscribe to it? I'm not in a hurry to have this functionality working in my project, but when the problem is resolved and a new version is released, I'd like to try again.

@jansupol
Copy link
Contributor

Hm...Would it help just to put that to a try catch block, so that the exception is not propagated to the container that returns 500?

@SkyCrawl
Copy link
Author

Hmm, looks like I was wrong. Each subsequent request (after reload) ends up with 500 and the following exception:

java.lang.IllegalStateException: Request scope has been already shut down

I'm attaching a sample project to reproduce the issue. I run it with JDK 11 and Apache Tomcat 8.5.81 as a Jakarta EE 8 web application. After starting, the following endpoint works:

http://localhost:8080/jersey/rest/hello_world

And after the Jersey container is reloaded by the following (ends up with 500 and ServiceLocatorImpl has been shut down):

http://localhost:8080/jersey/rest/reload

the hello world endpoint stops working (ends up with 500 and Request scope has been already shut down).

P.S.: even if supressing the exception solved the problem, I would prefer not to do that. I'm not in a hurry, prefer to do things thoroughly and deploy them without inherent issues. But thank you for trying to help :).

jersey-5156.zip

@jansupol
Copy link
Contributor

Thank you for the reproducer. The following makes additional requests work again:

   public final void reload() {
        LOG.info("Application: container is about to be reloaded.");
        RestApplication restApplication = new RestApplication();
        restApplication.configure();
        LISTENER.container.reload(restApplication);
        LOG.info("Application: container was reloaded.");
    }

I'll try to dig what's wrong with your solution.

@jansupol jansupol linked a pull request Sep 16, 2022 that will close this issue
@senivam senivam added this to the 2.38 milestone Oct 13, 2022
This was referenced Dec 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants