Skip to content

Honor CDI scope of @ServerEndpoint beans in CdiComponentProvider#962

Open
renatsaf wants to merge 1 commit into
eclipse-ee4j:2.3.xfrom
renatsaf:issue-961-cdi-scope
Open

Honor CDI scope of @ServerEndpoint beans in CdiComponentProvider#962
renatsaf wants to merge 1 commit into
eclipse-ee4j:2.3.xfrom
renatsaf:issue-961-cdi-scope

Conversation

@renatsaf

Copy link
Copy Markdown

Problem

CdiComponentProvider.create() always produces a fresh endpoint instance per WebSocket connection through an InjectionTarget, ignoring the bean's CDI scope. For a @ServerEndpoint that is also @ApplicationScoped (or jakarta.inject.@Singleton), the per-connection endpoint object is therefore a different instance than the contextual bean that CDI routes events and other injection points to.

The practical symptom (reported downstream in glassfish#25316): an endpoint stores Sessions in a shared collection in @OnOpen, but a @Observes broadcaster injected elsewhere sees an empty collection — the observer runs on the contextual bean, the sessions live on a throwaway per-connection object. The same pattern with an EJB @Singleton works, because isApplicable() returns false for EJB annotations and EjbComponentProvider resolves the single instance via JNDI.

Fix

In create(), resolve the managed Bean for the endpoint class. When it is normal-scoped (@ApplicationScoped, @SessionScoped, …) or @Singleton, return the shared contextual reference from BeanManager.getReference() so lifecycle callbacks, injected collaborators and event observers all operate on the same instance.

@Dependent (and non-bean) endpoints keep the existing per-connection InjectionTarget behavior. Shared references are intentionally not registered in cdiBeanToContext, so closing one connection does not tear down a bean still in use by others — the CDI container owns their lifecycle. Per-connection (dependent) instances are still cleaned up in destroy() exactly as before.

Behavioral note

For a normal-scoped endpoint, getReference() returns a CDI client proxy; Tyrus invokes the @OnOpen/@OnMessage callbacks through it, which is the intended, spec-compliant behavior. This is a behavior change only for code that (incorrectly) relied on getting per-connection instances of a scoped endpoint; @Dependent is bug-for-bug unchanged.

Tests

Adds CdiComponentProviderTest (a lightweight Proxy-based fake BeanManager, no new test dependencies beyond JUnit) covering:

  • @ApplicationScoped → same shared instance across connections
  • @Singleton (pseudo-scope) → same shared instance across connections
  • @Dependent → a new instance per connection

Fixes #961

CdiComponentProvider.create() always produced a fresh endpoint instance
per WebSocket connection via InjectionTarget, ignoring the bean's CDI
scope. For an @ApplicationScoped or @singleton @serverendpoint this meant
the per-connection endpoint object differed from the contextual bean seen
by CDI event observers and other injection points, so state set in a
lifecycle callback (e.g. a Set<Session> populated in @onopen) was invisible
to observers that broadcast to it.

Resolve the managed bean for the endpoint class and, when it is
normal-scoped (@ApplicationScoped, @SessionScoped, ...) or @singleton,
return the shared contextual reference from BeanManager.getReference() so
callbacks, injected collaborators and observers all act on the same
instance. Dependent-scoped (and non-bean) endpoints keep the previous
per-connection behavior and are still cleaned up in destroy(); shared
references are intentionally not registered for per-connection destruction.

Adds a regression test covering @ApplicationScoped, @singleton and
@dependent endpoints.

Fixes eclipse-ee4j#961

Signed-off-by: Renat R. Safiullin <renatsaf@gmail.com>
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.

1 participant