Skip to content

Replace NavigationManagerSession session-scope with ThreadLocal #108

Description

@marioserrano09

Part of #107.

Problem

NavigationManagerSession (platform/core/navigation/.../NavigationManagerSession.java) is @Scope("session"): a single shared slot (page, pageParams, runLaterQueue) per HTTP session, used to hand off the initial page to whichever ZKNavigationManager desktop bean is constructed next.

When multiple tabs/iframes are opened concurrently in the same session (e.g. a host shell embedding several /page-embed/** iframes at once), each request's setPageLater(page, params) overwrites the previous pending page/params before the corresponding desktop's @PostConstruct (ZKNavigationManager.init()) consumes it. Result: wrong page shown in an iframe, duplicated pages, or a requested page silently dropped. Same race applies to runLaterQueue.

Key insight

The only real callers (PageNavigationController.navigate() / PageEmbedController) resolve the ModelAndView through ChainableUrlBasedViewResolver → InternalResourceView, which renders via RequestDispatcher.forward() to the .zul view. A servlet forward is synchronous, same thread — ZK creates the Desktop and runs ZKNavigationManager.init() (and later ZKNavigationComposer.doAfterCompose(), which drains runLaterQueue) within that same forwarded request, on the same thread that called setPageLater/runLater.

Session scope is therefore unnecessary and actively harmful under concurrency. No token/correlation mechanism is needed — a ThreadLocal naturally isolates each concurrent request (each tab/iframe bootstrap) without touching PageNavigationController/PageEmbedController at all. This supersedes and closes #111 (token propagation is no longer needed).

Change

  • Replace the @Scope("session") Spring bean with a plain ThreadLocal<NavigationManagerSession> holder (getInstance() lazily creates a thread-local instance; keep the class name and public method signatures — setPage, updateNavManager, runLater, executeQueue, getPage, getPageParams — unchanged to preserve the public API).
  • Add NavigationManagerSession.clear() to remove the thread-local entry.
  • Register a servlet Filter (or equivalent request-lifecycle hook) that calls clear() in a finally after each request, so pooled threads don't retain a stale instance.
  • Document the new contract on the class Javadoc: setPageLater/runLater must be called on the same thread that will render/forward into the target ZK desktop (i.e. before an HTTP forward, never before a redirect — a redirect is a new request on a possibly different thread and the thread-local won't survive it). No current caller in the repo violates this; flag it for future callers.

Affected files

  • platform/core/navigation/src/main/java/tools/dynamia/navigation/NavigationManagerSession.java
  • platform/core/navigation/src/main/java/tools/dynamia/navigation/NavigationManager.java (Javadoc for setPageLater/runLater)
  • platform/ui/zk/src/main/java/tools/dynamia/zk/navigation/ZKNavigationManager.java (init(), unchanged logic, just confirm still same-thread)
  • new: request-lifecycle filter to clear the ThreadLocal (likely in platform/core/web or platform/ui/zk-starter)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions