Skip to content

♻️ Simplify session manager architecture#4144

Draft
thomas-lebeau wants to merge 8 commits intothomas.lebeau/asyc-session-manager-with-telemetry-merge-mainfrom
thomas.lebeau/session-manager-simplification
Draft

♻️ Simplify session manager architecture#4144
thomas-lebeau wants to merge 8 commits intothomas.lebeau/asyc-session-manager-with-telemetry-merge-mainfrom
thomas.lebeau/session-manager-simplification

Conversation

@thomas-lebeau
Copy link
Collaborator

Motivation

The session manager has accumulated complexity over time that makes it harder to maintain and debug. This PR simplifies the architecture by:

  1. Replacing polling with event-driven sync - Instead of polling every second to detect cross-tab session changes, we now use CookieStore API and localStorage storage events for instant notification
  2. Replacing custom lock mechanism with Web Locks API - The complex cookie-based lock with 100 retries has been replaced with the native Web Locks API
  3. Removing SessionContext layer - Products now work directly with SessionState instead of going through an intermediate SessionContext transformation

Changes

Architecture Changes

flowchart TB
    subgraph Before["Before"]
        direction TB
        SS1[SessionState] --> |"buildSessionContext()"| SC1[SessionContext]
        SC1 --> |"findSession()"| RSM1[RumSession]
        Poll1[("⏱️ 1s polling")] --> |"watchSession()"| SS1
        Lock1[("🔒 Cookie lock\n100 retries × 10ms")] --> SS1
    end
    
    subgraph After["After"]
        direction TB
        SS2[SessionState] --> |"findSessionState()"| RSM2[RumSession]
        Events[("📡 Storage events\nCookieStore / localStorage")] --> |"instant sync"| SS2
        Lock2[("🔒 Web Locks API\nnative mutex")] --> SS2
    end
Loading

Key Changes

Component Before After
Cross-tab sync 1s polling interval Event-driven (CookieStore API, storage events) + fallback polling
Locking Cookie-based lock with retries (~136 lines) Web Locks API (~73 lines)
Session access SessionStateSessionContext<T>RumSession SessionStateRumSession
Type safety Generic SessionManager<TrackingType> Non-generic SessionManager

Files Modified

Core package:

  • storageObservable.ts - New file with cookie and localStorage observables (moved from rum-core)
  • sessionStore.ts - Added event-driven sync subscriptions
  • sessionStoreOperations.ts - Replaced custom lock with Web Locks API
  • sessionManager.ts - Removed SessionContext, simplified to expose SessionState directly

RUM/Logs packages:

  • rumSessionManager.ts - Updated to work directly with SessionState
  • logsSessionManager.ts - Updated to work directly with SessionState

Test instructions

  1. Run unit tests: yarn test:unit
  2. Run e2e tests: yarn test:e2e
  3. Manual testing:
    • Open app in 2+ tabs
    • Verify session sync when activity in one tab
    • Verify stopSession() propagates to all tabs
    • Test consent grant/revoke across tabs

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated documentation and/or relevant AGENTS.md file

- Add createCookieObservable to core/browser/storageObservable.ts
- Add createLocalStorageObservable using window storage event for cross-tab sync
- Export from core index.ts
- Delete cookieObservable.ts and cookieObservable.spec.ts from rum-core
- Update ciVisibilityContext imports to use @datadog/browser-core
- Use storage observables for instant cross-tab notification
- Cookie persistence uses CookieStore API (with polling fallback)
- LocalStorage uses window storage event
- Keep polling as fallback for same-tab changes
Simplify session store operations by replacing the complex cookie-based
lock mechanism with the native Web Locks API. This removes ~60 lines of
retry/timeout logic while providing better cross-tab synchronization.

Key changes:
- Use navigator.locks.request() for exclusive access
- Queue operations when locks are enabled, process sequentially
- Fall back to synchronous execution when locks unavailable
- Strip legacy 'lock' field from sessions for backwards compatibility
Simplify session management by removing the intermediate SessionContext
type and having products (RUM, Logs) work directly with SessionState.

Key changes:
- Remove SessionContext interface and buildSessionContext() function
- Rename findSession() -> findSessionState() returning SessionState
- SessionManager no longer generic (no TrackingType parameter)
- RumSessionManager/LogsSessionManager access tracking type via productKey
- Session history entries updated in place when sessionState changes
- Add Web Locks mocks to rumSessionManager/logsSessionManager tests
@cit-pr-commenter-54b7da
Copy link

cit-pr-commenter-54b7da bot commented Feb 4, 2026

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 168.27 KiB 167.83 KiB -443 B -0.26%
Rum Profiler 4.32 KiB 4.33 KiB +1 B +0.02%
Rum Recorder 24.54 KiB 24.54 KiB +1 B +0.00%
Logs 56.47 KiB 56.51 KiB +41 B +0.07%
Flagging 944 B 944 B 0 B 0.00%
Rum Slim 125.15 KiB 124.75 KiB -410 B -0.32%
Worker 23.63 KiB 23.63 KiB 0 B 0.00%
🚀 CPU Performance
Action Name Base CPU Time (ms) Local CPU Time (ms) 𝚫%
RUM - add global context 0.0043 0.0064 +48.84%
RUM - add action 0.0139 0.0203 +46.04%
RUM - add error 0.0128 0.0166 +29.69%
RUM - add timing 0.0031 0.0041 +32.26%
RUM - start view 0.0033 0.0054 +63.64%
RUM - start/stop session replay recording 0.0007 0.001 +42.86%
Logs - log message 0.016 0.0238 +48.75%
🧠 Memory Performance
Action Name Base Memory Consumption Local Memory Consumption 𝚫
RUM - add global context 28.80 KiB 33.26 KiB +4.47 KiB
RUM - add action 54.75 KiB 227.74 KiB +172.99 KiB
RUM - add timing 27.90 KiB 28.64 KiB +756 B
RUM - add error 58.70 KiB 220.79 KiB +162.10 KiB
RUM - start/stop session replay recording 26.69 KiB 39.86 KiB +13.17 KiB
RUM - start view 429.60 KiB 539.55 KiB +109.95 KiB
Logs - log message 49.86 KiB 220.28 KiB +170.42 KiB

🔗 RealWorld

@thomas-lebeau thomas-lebeau changed the base branch from main to thomas.lebeau/asyc-session-manager-with-telemetry-merge-main February 4, 2026 09:23
@datadog-datadog-prod-us1
Copy link

datadog-datadog-prod-us1 bot commented Feb 4, 2026

⚠️ Tests

Fix all issues with Cursor

⚠️ Warnings

🧪 29 Tests failed

logs entry post start API usages account should call clearContext from Chrome Headless 139.0.0.0 (Linux 0.0.0) (Datadog) (Fix with Cursor)
Error: Expected spy clearContext to have been called once. It was called 0 times.
    at <Jasmine>
    at UserContext.<anonymous> (/go/src/github.com/DataDog/browser-sdk/packages/logs/src/boot/logsPublicApi.spec.ts:240:45 <- /tmp/_karma_webpack_882784/commons.js:160241:53)
    at <Jasmine>
logs entry post start API usages account should call removeContextProperty from Chrome Headless 139.0.0.0 (Linux 0.0.0) (Datadog) (Fix with Cursor)
Error: Expected spy removeContextProperty to have been called once. It was called 0 times.
    at <Jasmine>
    at UserContext.<anonymous> (/go/src/github.com/DataDog/browser-sdk/packages/logs/src/boot/logsPublicApi.spec.ts:234:54 <- /tmp/_karma_webpack_882784/commons.js:160236:62)
    at <Jasmine>
logs entry post start API usages account should call setContext from Chrome Headless 139.0.0.0 (Linux 0.0.0) (Datadog) (Fix with Cursor)
Error: Expected spy setContext to have been called once. It was called 0 times.
    at <Jasmine>
    at UserContext.<anonymous> (/go/src/github.com/DataDog/browser-sdk/packages/logs/src/boot/logsPublicApi.spec.ts:222:43 <- /tmp/_karma_webpack_882784/commons.js:160226:51)
    at <Jasmine>
View all

ℹ️ Info

❄️ No new flaky tests detected

🎯 Code Coverage
Patch Coverage: 82.14%
Overall Coverage: 77.32% (+0.05%)

View detailed report

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 6814784 | Docs | Datadog PR Page | Was this helpful? Give us feedback!

Deduplicate the identical ~20-line mock that was copy-pasted across
sessionManager, logsSessionManager, and rumSessionManager spec files
into a single reusable helper in core/test/emulate/mockWebLocks.ts.
The operationQueue/isProcessing/processNextOperation pattern manually
reimplements what navigator.locks.request with exclusive mode already
provides natively — requests are automatically queued by the browser.

Replace with a direct navigator.locks.request call and simplify tests
by using the shared mockWebLocksForSyncExecution helper, removing the
polling-based waitForAfterCallback.
- Extract computeSessionReplayState helper to replace nested ternary
- Inline SESSION_CONTEXT_TIMEOUT_DELAY (stale alias for SESSION_TIME_OUT_DELAY)
- Fix comment referencing removed "cookie lock" → "Web Lock"
- Remove unnecessary Operations export from sessionStoreOperations
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