Skip to content

Fix: #506 - VaadinSessionScopedContext was no longer active#507

Open
OriginalFelix wants to merge 15 commits into
vaadin:mainfrom
OriginalFelix:OriginalFelix-VaadinSessionScopedContext
Open

Fix: #506 - VaadinSessionScopedContext was no longer active#507
OriginalFelix wants to merge 15 commits into
vaadin:mainfrom
OriginalFelix:OriginalFelix-VaadinSessionScopedContext

Conversation

@OriginalFelix
Copy link
Copy Markdown

Description

In a Multi-Threaded Application 1 View with 10 Threads for Example, with threads containing the UI and VaadinSession, the Threads could access the CDI-Scopes before the changes breaking changes in the isActive method.
These changes were reverted and instead the findContextualStorage Method was adapted to dynamically take the lock, if not already running on a UI-Thread. This simplifies the adaptation for the customers, due to them not having to change every single line of code where they accessing the VaadinSessionScoped beans.

Fixes #506

Type of change

  • Bugfix
  • Feature

Checklist

  • I have read the contribution guide: https://vaadin.com/docs/latest/guide/contributing/overview/
  • I have added a description following the guideline.
  • The issue is created in the corresponding repository and I have referenced it.
  • I have added tests to ensure my change is effective and works as intended.
  • New and existing tests are passing locally with my change.
  • I have performed self-review and corrected misspellings.

…n inside any other thread than the UI-Thread.
@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented May 21, 2026

CLA assistant check
All committers have signed the CLA.

@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented May 21, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Felix Grebe seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Copy Markdown
Contributor

@mcollovati mcollovati left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a couple of comments. Overall looks good; I'm just a bit sad that we couldn't move the detection to the VaadinExtension class to execute it eagerly.

@mcollovati
Copy link
Copy Markdown
Contributor

Maybe a stupid idea, but what about programmatically defining an observer for ServiceInitEvent in VaadinExtension?

        var serviceScopedContext = new VaadinServiceScopedContext(beanManager);
        afterBeanDiscovery.<ServiceInitEvent>addObserverMethod()
                    .observedType(ServiceInitEvent.class)
                    .notifyWith(instance -> {
                        sessionContext.onServiceInit(instance.getEvent());
                    });

This could be added to VaadinExtension.addContexts(); the event is fired before any VaadinSession is created, so it should be safe. VaadinSessionScopedContext.onServiceInit(...) could have a simplified version of shouldUseStrictChecks, since at this point we are sure VaadinService and VaadinContext are not null

…nPolicy to VaadinExtension. And added Tests for Policies.
@OriginalFelix OriginalFelix force-pushed the OriginalFelix-VaadinSessionScopedContext branch from c663bea to ee8590c Compare May 22, 2026 09:54
Comment thread vaadin-cdi/src/main/java/com/vaadin/cdi/VaadinExtension.java Outdated
return session != null && session.hasLock();
final VaadinSession session = VaadinSession.getCurrent();
if (VaadinSessionScopedContext.getActivationPolicy() == null && session != null) {
activationPolicy.compareAndSet(null, determineVaadinSessionScopeActivationPolicy(session.getService()));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use a static field to store the policy?
Also, why we should call determineVaadinSessionScopeActivationPolicy again? The VaadinService init event should always be fired before any VaadinSession gets created.
Can you elaborate on this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcollovati yes, the problem here is that the Event is only fired, when the CDI-Plugin is deployed to a Servlet Container, but not inside the Unit-Tests. Maybe i'm missing something there?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so it's mostly an issue in how the tests are constructed, right?
If so, let me check.
The other question is if we should assume a single VaadinService or multiple ones (even if I guess this is an extremely rare if not inexistent scenario)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.
Is it possible to have multiple VaadinServices in the same war?

Copy link
Copy Markdown
Contributor

@mcollovati mcollovati May 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially, yes, by having multiple VaadinServlets. Actually I don't remember if this configuration is supported or not.
Anyway, I'll propose you another approach that should handle also that case (sorry for being so picky 😜)

The idea is to follow the same pattern applied to get AppShellRegistry (the following code is not tested, I just put down the ide):

  1. Define a VaadinSessionActivationPolicyHolder class with a VaadinSessionScopeActivationPolicy.Policy get(VaadinService vaadinService) method that resembles AppShellRegistry.getInstance(): get VaadinContext from service and store the policy in a private wrapper class
class VaadinSessionActivationPolicyHolder {

    public static VaadinSessionScopeActivationPolicy.Policy get(
            VaadinService vaadinService) {
        VaadinContext context = vaadinService.getContext();
        synchronized (context) { // NOSONAR
            PolicyWrapper attribute = context.getAttribute(PolicyWrapper.class);
            if (attribute == null) {
                VaadinSessionScopeActivationPolicy.Policy policy = determinePolicy(
                        vaadinService);
                attribute = new PolicyWrapper(policy);
                context.setAttribute(attribute);
            }
            return attribute.policy;
        }
    }

    private static VaadinSessionScopeActivationPolicy.Policy determinePolicy(
            VaadinService vaadinService) {
        // current logic
    }

    private record PolicyWrapper(VaadinSessionScopeActivationPolicy.Policy policy) {
    }
}
  1. In VaadinServiceScopedContext add a ServiceInitEvent observer and call VaadinSessionActivationPolicyHolder.get(...). This way we will have the policy stored for every VaadinService that is created.

  2. In VaadinSessionScopedContext.isActive simply call VaadinSessionActivationPolicyHolder.get(session.getService()) and use that value

How does this sound?

EDIT: we need to carefully verify the synchronized stuff

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we can mimic/reuse what's done in VaadinServiceScopedContext.getContextualStorage to get the key

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would work

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'll adapt it

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thank you!
I guess we can remove some redundant null checks from VaadinSessionScopedContext if VaadinSessionActivationPolicyHolder already handles them. And most likely we can drop getActivationPolicy as well.
Also, determinePolicy is never called with a null parameter, so that's another check that can be removed.

I wonder if VaadinSessionActivationPolicyHolder could be handled observice ServiceInitEvent and ServiceDestroyEvent events. But that's not a blocker.

Comment thread vaadin-cdi/src/main/java/com/vaadin/cdi/VaadinExtension.java Outdated
Comment thread vaadin-cdi/src/main/java/com/vaadin/cdi/VaadinExtension.java Outdated
@mcollovati
Copy link
Copy Markdown
Contributor

Got the following error on shutdown

17:05:20,697 ERROR [com.vaadin.flow.server.DefaultErrorHandler] (ServerService Thread Pool -- 82) Unexpected error: ServletConfig has not been initialized: java.lang.IllegalStateException: ServletConfig has not been initialized
        at jakarta.servlet.api@6.0.0//jakarta.servlet.GenericServlet.getServletName(GenericServlet.java:244)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.flow.server.VaadinServletService.getServiceName(VaadinServletService.java:199)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.cdi.util.VaadinSessionActivationPolicyHolder.get(VaadinSessionActivationPolicyHolder.java:34)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.cdi.util.VaadinSessionActivationPolicyHolder.get(VaadinSessionActivationPolicyHolder.java:47)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.cdi.context.VaadinSessionScopedContext.getActivationPolicy(VaadinSessionScopedContext.java:151)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.cdi.context.VaadinSessionScopedContext.isActive(VaadinSessionScopedContext.java:110)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.cdi.context.ContextWrapper.isActive(ContextWrapper.java:60)
        at org.jboss.weld.core@5.1.6.Final//org.jboss.weld.manager.BeanManagerImpl.internalGetContext(BeanManagerImpl.java:681)
        at org.jboss.weld.core@5.1.6.Final//org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:652)
        at org.jboss.weld.core@5.1.6.Final//org.jboss.weld.util.ForwardingBeanManager.getContext(ForwardingBeanManager.java:178)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.cdi.util.ContextUtils.isContextActive(ContextUtils.java:59)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.cdi.util.ContextUtils.isContextActive(ContextUtils.java:45)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.cdi.context.VaadinSessionScopedContext.guessContextIsUndeployed(VaadinSessionScopedContext.java:138)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.cdi.CdiVaadinServletService$CdiVaadinServiceDelegate.sessionDestroy(CdiVaadinServletService.java:232)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.flow.server.VaadinService.lambda$fireSessionDestroy$6(VaadinService.java:896)
        at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024)
        at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735)
        at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.flow.server.VaadinService.lambda$fireSessionDestroy$9c853e43$1(VaadinService.java:894)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.flow.server.VaadinService.runPendingAccessTasks(VaadinService.java:2340)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.flow.server.VaadinSession.unlock(VaadinSession.java:790)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.flow.server.VaadinService.ensureAccessQueuePurged(VaadinService.java:2304)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.flow.server.VaadinService.accessSession(VaadinService.java:2271)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.flow.server.VaadinSession.access(VaadinSession.java:1095)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.flow.server.VaadinService.fireSessionDestroy(VaadinService.java:865)
        at deployment.cdi-vs-scope-reproducer-1.0-SNAPSHOT.war//com.vaadin.flow.server.VaadinSession.valueUnbound(VaadinSession.java:220)
        at io.undertow.servlet@2.3.23.Final//io.undertow.servlet.core.SessionListenerBridge.attributeRemoved(SessionListenerBridge.java:145)

@mcollovati
Copy link
Copy Markdown
Contributor

mcollovati commented May 22, 2026

I guess this only needs the service name to be computed outside the destroy listener.

EDIT: nope. That is only one point; get method needs to be fixed as well

@mcollovati
Copy link
Copy Markdown
Contributor

mcollovati commented May 22, 2026

I did a quick test making VaadinSessionActivationPolicyHolder an application scope bean, handling the cache in service init and destroy, and having get only perform lookup (with the addition of a return if cache is empty guard).
This seems to work fine. But feel free to handle it differently.

EDIT: return if cache is empty: that's actually a bit stupid since it could miserably fail if there are more than one VaadinService

@OriginalFelix
Copy link
Copy Markdown
Author

The last one was actually tricky! the ServletName requires the Servlet to be Initialized... The solution was to check for the State of the VaadinService and Cache in name into a final String - So it's being stored once it's initialized....

@mcollovati
Copy link
Copy Markdown
Contributor

The last one was actually tricky! the ServletName requires the Servlet to be Initialized... The solution was to check for the State of the VaadinService and Cache in name into a final String - So it's being stored once it's initialized....

Well done!
The alternative could be to cache the service name in CdiVaadinServletService, but of course it would fail with other implementations.

I'll make a final review on Monday, but I think we are close to having this ready.
Thanks for your patience! 🏅

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 this pull request may close these issues.

Commit 0d3aeeb causes many neccessary changes to use the VaadinSessionScoped Context again

4 participants