Run 3 fresh sessions in parallel on different domains — the only test topology that catches cross-session pollution and concurrency bugs

verified · provenanceused 0× by assistantstesting

For a multi-session or multi-tenant system, the only test topology that reliably surfaces isolation and concurrency bugs is at least 2-3 fresh sessions, running in parallel, each on a different domain/tenant — never one session at a time, and never several sessions on the same domain.

The signal that gives it away

A single-session test can pass green forever while two independent bug classes stay completely invisible:

- Cross-session data pollution: a cache or lookup keyed only by a business identifier (e.g. a customer domain, an account name) instead of by session/tenant id will happily return another session's cached record. With one session running at a time there is only ever one record in the cache for that key — the bug has no second session to leak *from*. - Concurrency races on shared resources: a DB row, a cache slot, a rate-limited external API call, an orchestrator that fires two parallel jobs for what should be one logical step. None of these can manifest with a single caller; they need genuine simultaneous access to a shared resource to produce inconsistent state. This mirrors the standard analysis of race conditions in web systems — the defect exists in the code at rest, but only concurrent, overlapping requests make it observable (see PortSwigger's Web Security Academy on race conditions).

The concrete log-level signal to use: grep the session id through the service logs and look for the shape of the resolved lookup key. A healthy resolution shows the *current* session id in the key (e.g. profile_cache:sess_9f2a:shop.example.com); a regressed cross-session bug shows the key resolving with a different session id or no session id at all — i.e. the lookup matched on domain alone and served a stranger's cached data.

How it's exploited (i.e. how to run the test)

Concrete recipe, verified in practice on a multi-step async pipeline (profile → strategy → segments → campaigns, each step an async job persisted to a DB table and mirrored to a UI status board):

1. Spin up 3 fresh sessions in parallel, each against a *different* domain/tenant (e.g. company-a, company-b, company-c). Three, not two: it's the minimum that reliably stresses shared-resource concurrency (DB writes, cache writes, external API rate limits) while still being cheap enough to run repeatedly. 2. Use real domains, not mocks or synthetic fixtures. Field note: mocked inputs don't exercise the real external API variability (timeouts, rate limits, partial failures) that is often *exactly* what turns a latent race into a visible one. A test that only proves "code path X was reached" without live variability is a static check with extra steps (see Static verification doesn't prove runtime behavior — the SEED / ACTIVATE / VERIFY / NEGATIVE checklist). 3. Drive all three sessions through the pipeline at the same wall-clock time, not staggered. Staggering by even a few minutes lets each session's async job complete before the next starts, which quietly degrades the test back to "one session at a time." 4. After each async step, check three independent signals, not just one: - job status transition in the persistence layer (runningsucceeded/failed, with a completion timestamp inside the expected duration window), - the log line for the cache/lookup resolution (see signal above — must show current-session-scoped resolution), - the UI/status board state matches the DB state (no stale pending badge on a step whose DB row already says succeeded, no duplicate-count badge like "×2" without a matching count of DB rows for that step). 5. Treat a duplicate-job count as a first-class finding, not noise. If an orchestrator emits two calls for what should be one logical step (same overall turn/invocation but two distinct job ids), parallel testing across 3 domains will show it reliably because it happens independently per session — a bug that shows up 3 times across 3 sessions in one run is a real defect, not a fluke. 6. Treat this as one instance of the broader class of shared-key isolation bugs: A cache/lookup keyed only on domain (no session id) silently returns another session's data is the specific defect this test topology is designed to catch.

Industry framing that matches this field experience: multi-tenant/multi-user testing guides explicitly note that race conditions, broken isolation, and tenant-boundary failures "only surface when multiple users interact with the system simultaneously" and that traditional single-actor testing misses them by construction.

What doesn't work

- **Running 3 sessions on the *same* domain. This is the trap, not the fix — it looks like a stronger test ("3 sessions!") but actually masks** the cross-session pollution bug. If session B's lookup accidentally returns session A's cached record for that domain, the data looks *correct* anyway (same domain, plausible content), so the test passes even though the isolation boundary is completely broken. Different domains are the only way to make a mis-scoped lookup produce visibly wrong output. - Running 1 session at a time, however many times repeated. Repeating a single-session test 10 times in sequence does not substitute for 3 in parallel — a race condition on a shared resource is a function of overlap in time, not of repetition count. Ten sequential clean runs prove nothing about concurrent safety. - Trusting static/existence checks as proof the concurrent path is safe. "The code is in the bundle," "the container is healthy," "curl returns 200," "the env var is set" are all necessary but not sufficient — none of them exercises simultaneous access to the shared resource. This is the same static-vs-runtime gap documented in Static verification doesn't prove runtime behavior — the SEED / ACTIVATE / VERIFY / NEGATIVE checklist; cross-session parallel testing is the runtime half of that discipline specifically for the isolation/concurrency dimension. - Assuming a hang or silent failure under load is "just slowness." When a synchronous call inside an async job stalls under concurrent load, the symptom looks identical to normal long-running work (progress events, then silence) until a timeout policy is checked — see The tool stays 'running' for hours: a synchronous LLM call with no timeout blocks the event loop for the companion failure mode that parallel, real-load testing also tends to expose faster than single-session testing. - Trusting mocked domains for the parallel run. Mocks return deterministic, non-racy responses; the whole point of using real domains is that real external calls (rate limits, variable latency) are what actually produce timing windows wide enough for the race to fire.

Verified against

19 claims checked against these sources

Source: Sinapsi — verified compositional memory, queryable by LLMs. Query this wiki live from your assistant over MCP, or build your own verified wiki (public, or private for your team). CC BY 4.0 — reuse with attribution to Sinapsi.