Skip to content

feat(cubestore): cap concurrent websocket connections per user - #11667

Merged
waralexrom merged 4 commits into
masterfrom
cubestore-ws-connections-per-tenant-cap
Aug 27, 2026
Merged

feat(cubestore): cap concurrent websocket connections per user#11667
waralexrom merged 4 commits into
masterfrom
cubestore-ws-connections-per-tenant-cap

Conversation

@waralexrom

Copy link
Copy Markdown
Member

Summary

Cube Store accepts an unbounded number of concurrent WebSocket connections per authenticated user. A client that opens connections faster than it closes them consumes the whole process file descriptor table, and once that is full the node cannot accept connections for any other user on it either. This adds an optional per-user cap.

At the limit the user's oldest connection is closed to admit the new one, rather than the new one being refused. A client that still needs the closed connection reconnects; one that had forgotten about it simply loses it. Refusing instead would leave a user whose slots are all held by forgotten connections unable to open a working one at all, since nothing would ever release them.

Disabled by default (0), so behaviour is unchanged unless a limit is set.

Changes

  • CUBESTORE_MAX_WS_CONNECTIONS_PER_USER — new config option, default 0 (disabled), documented in the environment variable reference.
  • WsConnectionCounter — live connections per authenticated user, keyed by a monotonic id so the first entry is the oldest. Unauthenticated connections and a disabled cap stay untracked; users whose last connection goes away are removed from the map, so a churn of one-off users cannot grow it.
  • WsConnectionGuard — taken at the /ws upgrade and released from Drop, so the slot comes back on every exit path: a close frame, a transport error, a panic, a cancelled task, or a connection that dies before the upgrade completes.
  • Eviction goes through a CancellationToken the connection task selects on, next to the two branches it already had. The task sends a close frame before it leaves, so the client sees a clean close rather than a reset.
  • The victim's entry is removed by the admitting call rather than left to the victim's own later drop, so the limit never overshoots; the monotonic id is what keeps those two removals from taking each other's entry.

The counter is per process, so a user connecting to several nodes may hold up to the limit on each. Called out in the docs, along with the advice to set the limit comfortably above what a client legitimately keeps open at once — below that, working connections get recycled.

Changed behaviour at the limit

Before After
New connection over the limit accepted, no limit exists accepted
User's oldest connection closed with a close frame
Descriptors held by one user unbounded at most the limit, per node

Testing

cargo test -p cubestore --lib http:: — 16 passed. That includes the existing WebSocket tests (ws_test, ws_process_id_header_test, query_test, inline_tables_query_test, and the message-size tests), which run through the acquire/release path with the cap disabled, plus five new unit tests:

  • eviction picks the oldest connection, leaves the newer ones alone, keeps following insertion order, and does not overshoot the limit;
  • the victim's own later drop does not remove another connection's entry;
  • one user at its cap does not affect another;
  • users whose last connection goes away are forgotten, so the map does not grow;
  • releasing a slot after the counter lock was poisoned does not abort the process. This one was verified to fail without the fix: a panicking destructor during unwinding is not recoverable, so Drop recovers a poisoned lock instead of unwrapping it.

cargo check -p cubestore --all-targets is clean, no new warnings.

Design note

The counter lock is a std::sync::Mutex rather than a tokio one behind util::lock::acquire_lock, because entries are removed from a destructor and a destructor cannot .await. Releasing anywhere else — explicitly at the end of the connection loop, or from a spawned task — would leak the slot whenever the connection ends abnormally, and a cap whose slots only ever leak eventually admits nothing. Nothing awaits inside the critical section, which is what makes the synchronous lock correct here; that reasoning is recorded next to the field.

Adds CUBESTORE_MAX_WS_CONNECTIONS_PER_USER, an optional per-node cap on how
many concurrent WebSocket connections one authenticated user may hold. A
client that opens connections faster than it closes them can otherwise consume
the whole process file descriptor table, which affects every other user of
that node.

At the limit the user's oldest connection is closed to admit the new one,
rather than the new one being refused: a client that still needs the closed
connection reconnects, while one that had forgotten about it simply loses it.
It is closed with a close frame, so the client sees a clean close and can
reconnect on its own terms.

A slot is taken at the websocket upgrade and released by a guard held for the
lifetime of the connection task, so it comes back on any exit path: a close
frame, a transport error, a panic, a cancelled task, or a connection that dies
before the upgrade completes. Live connections are keyed by a monotonic id, so
an evicting admission and the victim's own later drop cannot remove each
other's entry, and the limit never overshoots.

The limit defaults to 0, which disables the cap and leaves behaviour unchanged.
@waralexrom
waralexrom requested review from a team and keydunov as code owners August 27, 2026 14:44
@mintlify

mintlify Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
cubed3 🟢 Ready View Preview Aug 27, 2026, 2:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@github-actions github-actions Bot added cube store Issues relating to Cube Store rust Pull requests that update Rust code labels Aug 27, 2026
@claude

claude Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @waralexrom's task in 2m 5s —— View job


Review complete — 1 medium-high, 1 medium, 3 low. Solid design; the eviction path can hang and the cap is inert without auth.

Full review
  • Read the diff (git diff origin/master...HEAD)
  • Review http/mod.rs — counter, guard, eviction, select loop
  • Review config/mod.rs and the env-var docs
  • Check for prior review threads (none) and post findings

What's good

The guard design is the right shape for this problem. Releasing from Drop rather than at the end of the connection loop is what makes the cap survive panics, cancelled tasks, and upgrades that never complete — the PR description reasons about this correctly, and the std::sync::Mutex choice follows from it (a destructor can't .await). Two details are easy to get wrong and are right here:

  • The victim's entry is removed by the admitting call, so the limit never overshoots (http/mod.rs:209-215).
  • Entries are keyed by a monotonic id, so the victim's later drop can't remove a different connection's slot (http/mod.rs:255) — and there's a test that would catch a regression.

Recovering the poisoned lock instead of unwrapping (lock_users, http/mod.rs:166-170) is the kind of thing that only shows up in production as an abort; good that it's both handled and tested. Empty user buckets are removed, so one-off user churn can't grow the map. Docs are clear about the per-node semantics and about setting the limit above legitimate concurrency.

Findings

# Severity Where Issue
1 Medium-high http/mod.rs:395 web_socket.close().await drives the full close handshake with no timeout — a silent peer parks the task with the fd still open, and since the counter entry is already gone, that client can hold unbounded live connections while the cap reads as satisfied. inline
2 Medium http/mod.rs:379 sql_query_context.user is None for unauthenticated requests, so the cap is entirely inert on deployments without credentials — the same fd exhaustion is reachable there. Worth a startup warning, a fallback bucket, or at least an explicit docs note. inline
3 Low http/mod.rs:387-397 tokio::select! picks branches at random, so an evicted connection with a busy response_rx can keep sending responses for a while before it notices the cancellation. Harmless, but biased; with the eviction branch first would make "evicted means evicted" deterministic and cost nothing.
4 Low http/mod.rs:394 Queries in flight on the evicted connection are dropped with only a close frame — no HttpCommand::Error naming them. The client sees a clean close but can't distinguish "server recycled me" from "network blip", which is the difference between reporting a config problem and silently retrying. Consider a close reason string (you already have the pattern with MESSAGE_TOO_BIG_CLOSE_CODE).
5 Low tests The five unit tests cover the counter thoroughly, but nothing exercises eviction end-to-end over a real socket. The existing ws_test harness is right there and runs with the cap disabled; a variant with max_ws_connections_per_user: 1 asserting the first client receives a close frame when a second connects would cover the select! branch, the close path, and the guard-in-task wiring — none of which the unit tests touch. This is also where finding #1 would have surfaced.

Smaller notes

  • WsConnectionCounter::count (http/mod.rs:235) is only used by tests; not wrong to expose, just noting it's public API for no consumer.
  • next_id is fetched outside the lock, so under concurrent acquire the id order can differ slightly from lock-acquisition order. Irrelevant in practice — "oldest" is approximate either way — mentioning it only so the invariant is written down somewhere.
  • Docs table (environment-variables.mdx) renders fine and the placement is alphabetically correct.
  • I did not run cargo check/cargo test for cubestore here (build time in this environment); the PR reports both clean, and nothing in the diff looks like it would fail to compile.
· branch `cubestore-ws-connections-per-tenant-cap`

Comment thread rust/cubestore/cubestore/src/http/mod.rs Outdated
.and_then(move |tx: mpsc::Sender<(mpsc::Sender<Arc<HttpMessage>>, SqlQueryContext, HttpMessage)>, sql_query_context: SqlQueryContext, ws_connections: Arc<WsConnectionCounter>, ws: Ws| async move {
let tx_to_move = tx.clone();
let sql_query_context = sql_query_context.clone();
let connection_guard = ws_connections.acquire(sql_query_context.user.as_deref());

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.

sql_query_context.user is None whenever the request carries no auth header — authorize returns Ok(None) in that case even when SqlAuthService reports no password for the user (see authorize, ~L918-940). So on a Cube Store deployment without credentials configured (the common single-tenant setup), every WebSocket connection is untracked and the cap is a no-op regardless of what CUBESTORE_MAX_WS_CONNECTIONS_PER_USER is set to.

That's a defensible scope — you can't attribute an anonymous connection to a tenant — but the stated motivation is fd exhaustion, which is just as reachable without auth. Two things worth considering:

  1. Log once at startup (or on the first untracked connection) when the limit is set but connections arrive unauthenticated, so an operator who set the variable doesn't believe they're protected when they aren't.
  2. Either a global cap or an anonymous bucket as a fallback would actually close the hole; a per-node total is cheap to add on top of the same counter.

At minimum the docs entry should say explicitly that unauthenticated connections are exempt — "a single authenticated user" implies it but a reader setting the variable for fd protection will likely miss it.

Sending the close frame flushes, so a peer whose receive window is closed would
park the connection task indefinitely with the descriptor still held. That
matters more than a stray task: the entry leaves the counter when the
connection is evicted rather than when its task ends, so the cap would read as
satisfied while the process held more connections than it allows. The send is
now bounded, and the bound is deliberately short — the steady-state overshoot
is the eviction rate times this timeout, and a close frame is a handful of
bytes, so a peer that cannot take them in two seconds is not going to.

The frame now carries close code 1013 ("Try Again Later") and a reason, so a
client can tell being recycled from a transport failure and reconnect rather
than report an error.

Adds an end-to-end test: with a cap of one, a second connection for the same
authenticated user evicts the first, which receives that close code and reason,
while a different user connects unaffected. It covers the parts the unit tests
cannot reach — the select branch, the close path and the guard living in the
connection task. The read is bounded, so a regression in the eviction path
fails the test rather than hanging it.
@claude

claude Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @waralexrom's task in 2m 7s —— View job


Re-review of c001380 — the fd-exhaustion finding is fixed. 1 medium (carried), 2 low remain.

Full review
  • Diff the new commit (git show c001380)
  • Re-check the eviction close path against the earlier finding
  • Review the new end-to-end test
  • List existing review threads, resolve my own that no longer apply
  • Post the updated review

What the new commit fixes

c001380 addresses the two findings that mattered most from the previous pass:

  • Bounded close (was medium-high). web_socket.close() is replaced by send(Message::close_with(...)) under a 2s tokio::time::timeout (http/mod.rs:411-423). This is the better fix, not just the timeout I suggested: send only start-sends and flushes rather than driving the full handshake, so it no longer waits on the victim to reply at all — the only thing that can block is a peer whose receive window is closed, which the timeout now covers. The EVICTED_CLOSE_TIMEOUT doc comment (http/mod.rs:61-68) states the resulting bound precisely — steady-state overshoot is eviction rate × timeout — which is the right way to write down a deliberately-leaky invariant. I've resolved that thread.
  • Named reason (was low). Close code 1013 "Try Again Later" plus a reason string (http/mod.rs:55-59, 412-415). 1013 is the correct code here — it tells a client to reconnect rather than surface an error, which is exactly the semantics of eviction.
  • End-to-end coverage (was low). ws_connection_evicted_test (http/mod.rs:2277-2353) covers what the unit tests structurally could not: the select! branch, the close path, and the guard living inside the connection task. Two details I'd call out as done right — the read is wrapped in a timeout so a regression in the eviction path fails rather than hangs the suite, and the tenant-b connection at the end asserts the cross-user isolation over a real socket, not just against the counter. Port 53036 doesn't collide with the five other WS tests in the file.

Also worth noting: Message in the test module resolves to tungstenite's (explicit use shadows the super::* glob that brings in warp's), so Message::Close(Some(frame)) matches as intended.

Still open

# Severity Where Issue
1 Medium http/mod.rs:395 Carried over, unresolved. sql_query_context.user is None whenever no Authorization header is present — I re-read authorize (http/mod.rs:946-968) to confirm: credentials is None, so both the authenticated and unauthenticated arms return Ok(None). On a deployment without credentials, the cap is inert regardless of what the variable is set to. Existing thread — at minimum the docs entry should say unauthenticated connections are exempt. Fix this →
2 Low http/mod.rs:403 tokio::select! picks branches at random, so an evicted connection with a busy response_rx can keep serving responses before it notices the cancellation. Adding biased; with the eviction branch first makes "evicted means evicted" deterministic at no cost — and it also tightens the overshoot bound the new EVICTED_CLOSE_TIMEOUT comment reasons about, which currently assumes the branch is taken promptly.
3 Low http/mod.rs:406, 419 Style nit only: the eviction arm mixes bare error! (imported at the top) with fully-qualified log::warn!. Elsewhere the file uses the bare macros.

Smaller notes

  • The send-then-break path never waits for the client's close reply before dropping the socket. Strictly, RFC 6455 has the initiator wait; in practice every client treats a received close frame plus FIN as a clean close, and waiting is precisely what the fix removed. Correct tradeoff, just recording it.
  • If the timeout fires mid-flush, the frame is dropped and the peer sees a bare FIN — the degraded case is exactly the old behaviour, which is the right floor.
  • WsConnectionCounter::count (http/mod.rs:235) still has no non-test consumer.
  • I did not run cargo check/cargo test for cubestore here (build time in this environment). The PR reports both clean; nothing in the new diff looks like it would fail to compile.
· branch [`cubestore-ws-connections-per-tenant-cap`](https://github.com/cube-js/cube/tree/cubestore-ws-connections-per-tenant-cap)

The counter's critical section is a map lookup and an insert with nothing
awaited inside it — `acquire` is not an `async fn`, so nothing can be — which
is the shape the `tokio::sync::Mutex` docs point at a standard-library lock
for. That makes a stall unreachable by reasoning about the callers; taking the
lock with a bound makes it unreachable without needing that reasoning at all.

Switches to parking_lot for `try_lock_for`. It also removes poisoning, which
had to be recovered by hand before: there is nothing meaningful to recover for
a map of live connections, and a poisoned unwrap in the destructor would have
aborted the process. parking_lot is already in the tree at this version, so the
lockfile gains one dependency edge and no crate.

Both callers fail open, because the cap is a safety net rather than a
correctness invariant: an unavailable counter admits the connection untracked
instead of refusing or stalling it, and a finished connection leaves its entry
behind to be evicted — that entry is the oldest one of its user by
construction, and cancelling a finished connection's token is a no-op. Both
paths log, so a bound that is ever reached is visible instead of quietly
weakening the cap.

The bound is 100ms rather than something tighter because a thread holding the
lock can be descheduled for milliseconds under CPU pressure, and giving up then
would stop counting connections exactly when the cap matters most.
@claude

claude Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @waralexrom's task in 2m 0s —— View job


Re-review of 2da8e4e — the lock change is sound. 1 low finding on it, plus 2 carried over.

Full review
  • Read the new commit's diff (git show 2da8e4e)
  • Re-check the counter, guard, and the two new fail-open paths in full
  • Verify the Cargo.lock claim and the new test
  • List existing review threads, dedupe, resolve stale ones (none newly stale)
  • Post updated review

What the new commit does

Replaces std::sync::Mutex + hand-rolled poison recovery with parking_lot::Mutex and try_lock_for(100ms) at every take, both callers failing open. Three things I checked and can confirm:

  • The Cargo.lock claim is accurate. The diff is exactly one line — a dependency edge on cubestore, no new [[package]] entry — so parking_lot 0.12 was already resolved in the tree. No new crate, no version bump.
  • Fail-open in acquire is genuinely safe (http/mod.rs:236-249): the returned guard has user: None, so its Drop returns early and can't remove someone else's entry. The id fetched before the lock is discarded, which just leaves a gap in the monotonic sequence — harmless, ordering is all that's used.
  • Dropping poisoning is the right call, and it's a strict improvement over the previous commit's recovery: parking_lot has no poison state at all, so the abort-in-destructor hazard the old lock_users worked around stops existing rather than being handled.

The 100ms bound is well-reasoned in the doc comment (http/mod.rs:71-79) — it's a descheduling allowance, not a contention allowance, and both timeout paths log::error! so a bound that's ever reached is visible instead of silently weakening the cap. That's the right way to ship a fail-open safety net.

New finding

# Severity Where Issue
1 Low http/mod.rs:302-304 The Drop timeout comment asserts the stranded entry "is the oldest one of that user by construction" — it isn't. Connections don't finish in admission order, so a mid-list entry can be stranded, and the next admission then evicts a live connection while the stale entry keeps a slot until churn reaches it. Self-healing and effectively unreachable, so no code change needed; the comment (and the commit message, which repeats the claim) shouldn't state an invariant the type doesn't have. inline

Still open from earlier passes

# Severity Where Issue
2 Medium http/mod.rs:437 Re-affirmed, not re-posted. sql_query_context.user is None when no Authorization header is present, so the cap is inert on deployments without credentials. I re-checked the docs entry in this pass — it still says "a single authenticated user" without stating that unauthenticated connections are exempt. Existing thread · Fix this →
3 Low http/mod.rs:445 tokio::select! still picks branches at random; biased; with the eviction arm first would make "evicted means evicted" deterministic and tighten the overshoot bound EVICTED_CLOSE_TIMEOUT reasons about.

Smaller notes

  • acquire_gives_up_on_a_stuck_lock_instead_of_waiting_on_it (http/mod.rs:1621) is a good test — it holds the lock from the main thread and probes from another, so it asserts the fail-open path rather than reentrancy. Two nits: the assert message has a stray run of spaces mid-sentence ("...admits the connection untracked...", L1635), and it costs a real 100ms of suite time. Nothing tests the Drop timeout path, which is where finding ReferenceError: self is not defined - Issue with whatwg-fetch when using the core lib with Node.js #1 lives.
  • count (http/mod.rs:277) now returns 0 on lock timeout, conflating "unknown" with "none". Test-only consumer, so it doesn't matter today — just noting the collapse is silent.
  • If the 100ms bound were ever actually reached, try_lock_for blocks a tokio worker thread for that long rather than yielding. That's the accepted cost of releasing from a destructor, and it's now bounded where before it wasn't — recording it rather than objecting.
  • No threads became stale in this pass, so nothing was resolved. The one resolved thread (the unbounded close()) was already resolved in the prior pass.
  • I did not run cargo check/cargo test for cubestore here (build time in this environment). The PR reports both clean; nothing in the new diff looks like it would fail to compile — try_lock_for is available on parking_lot::Mutex with default features.
· branch [`cubestore-ws-connections-per-tenant-cap`](https://github.com/cube-js/cube/tree/cubestore-ws-connections-per-tenant-cap)

Comment on lines +302 to +304
// The entry stays behind, but it is the oldest one of that user by
// construction, so the next admission evicts it: cancelling an
// already finished connection's token is a no-op.

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.

The "oldest by construction" claim doesn't hold. Connections don't finish in the order they were admitted: with ids 3, 4, 5 live for one user, id 5 can be the one whose drop hits this branch, and its stale entry then sits behind 3 and 4 in the BTreeMap.

The consequence is slightly worse than "evicted on the next admission": the next admission picks 3 — a live, working connection — and closes it, while the stale entry keeps consuming a slot. It only clears once churn makes it the oldest, so until then the user's effective cap is limit - 1 and one legitimate connection is recycled early for nothing.

Self-healing and effectively unreachable, so I'd leave the code alone — but the comment shouldn't assert an invariant the type doesn't have. Something like:

Suggested change
// The entry stays behind, but it is the oldest one of that user by
// construction, so the next admission evicts it: cancelling an
// already finished connection's token is a no-op.
// The entry stays behind. It is not necessarily that user's oldest
// — connections do not finish in admission order — so until churn
// makes it the oldest it holds a slot, and the admission that would
// have taken it evicts a live connection instead. Cancelling a
// finished connection's token is a no-op, so it clears harmlessly.

Same wording appears in the commit message ("that entry is the oldest one of its user by construction"), worth fixing there too if you amend.

…e taken

The bound exists to make a stall impossible without reasoning about the
callers, not to be tight, and the failure it guards against is one-sided: a
thread holding the lock can be descheduled for a long time under CPU pressure,
and giving up then would quietly stop counting connections exactly when the cap
matters most. A second is far past anything a map lookup and an insert can
take, and giving up is logged either way.
@claude

claude Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @waralexrom's task in 1m 23s —— View job


Re-review of 0396d03 — comment-and-constant change only, no correctness impact. 0 new findings, 2 carried over (1 medium, 1 low), 2 nits.

Full review
  • Read the new commit's diff (git show 0396d03)
  • Re-check the lock-timeout callers against the new bound
  • Check the stuck-lock test's cost
  • List existing review threads, dedupe / resolve stale (none newly stale)
  • Post updated review

What the commit does

COUNTER_LOCK_TIMEOUT goes from 100ms to 1s, with the doc comment rewritten to say why. That's the whole diff — 8 insertions, 7 deletions, one of them the constant. Both call sites (acquire, http/mod.rs:238; Drop, http/mod.rs:300) already fail open and already log, so nothing about the behaviour changes except how long an unreachable path waits before giving up.

The reasoning holds. The bound was never protecting against contention — the critical section is a HashMap lookup plus a BTreeMap insert, with no .await in it — so it only ever fires when the holder is descheduled, and firing means the cap silently stops counting. Under CPU pressure, which is when descheduling is long, is also when a connection-leaking client hurts most; a bound tight enough to trip there would disable the cap exactly then. Erring generous is the right direction for a fail-open safety net, and the log lines mean a bound that is reached shows up rather than degrading quietly.

Two nits on the new value, neither worth blocking:

  • The test now costs 1s of suite wall-clock. acquire_gives_up_on_a_stuck_lock_instead_of_waiting_on_it (http/mod.rs:1623) deliberately holds the lock, so the probe thread waits the full timeout — that went from 100ms to a full second in a --lib suite. Threading the timeout through as a field (or a #[cfg(test)] override) would let the test assert the fail-open path in milliseconds. Given it's one test, living with it is defensible; noting it because a second is the kind of thing that accretes.
  • The blocked thread is a tokio worker. try_lock_for parks the calling thread, and in Drop that thread is a runtime worker — so the worst case is now one worker unavailable for a second rather than 100ms. That's the accepted cost of releasing from a destructor and it's still bounded where originally it wasn't. Recording the shape of the tradeoff, not objecting to it: the same CPU-pressure scenario the comment invokes as the reason to wait longer is also the one where holding a worker hurts. If the ordering ever matters more than the counting, the alternative is try_lock() in Drop with a fallback that re-queues the removal — more machinery than this is worth today.

Still open from earlier passes

Both threads are on code untouched by this commit; re-affirmed, not re-posted.

# Severity Where Issue
1 Medium http/mod.rs:438 sql_query_context.user is None when no Authorization header is present, so the cap is inert on deployments without credentials. The docs entry still says "a single authenticated user" without stating that unauthenticated connections are exempt. Existing thread · Fix this →
2 Low http/mod.rs:302-304 The Drop timeout comment still asserts the stranded entry "is the oldest one of that user by construction" — connections don't finish in admission order, so a mid-list entry can be stranded. Comment-only; the code is fine. Existing thread
3 Low http/mod.rs:445 tokio::select! still picks branches at random; biased; with the eviction arm first would make "evicted means evicted" deterministic and tighten the overshoot bound EVICTED_CLOSE_TIMEOUT reasons about.

Smaller notes

  • The stray run of spaces in the stuck-lock test's assert message ("...admits the connection untracked...", http/mod.rs:1635) is still there.
  • count (http/mod.rs:280) still collapses "lock unavailable" into 0, and still has no non-test consumer.
  • No threads became stale in this pass, so nothing was resolved.
  • I did not run cargo check/cargo test for cubestore here (build time in this environment). A one-line constant change to an existing Duration can't fail to compile.

Overall: the feature is in good shape. Nothing in this commit changes my view of it, and the two carried findings are both small — one docs sentence and one comment.

· branch [`cubestore-ws-connections-per-tenant-cap`](https://github.com/cube-js/cube/tree/cubestore-ws-connections-per-tenant-cap)

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 58.38%. Comparing base (ab48039) to head (0396d03).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #11667      +/-   ##
==========================================
+ Coverage   58.20%   58.38%   +0.18%     
==========================================
  Files         237      237              
  Lines       18852    18853       +1     
  Branches     3825     3830       +5     
==========================================
+ Hits        10973    11008      +35     
+ Misses       7323     7283      -40     
- Partials      556      562       +6     
Flag Coverage Δ
cube-backend 58.38% <ø> (+0.18%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@waralexrom
waralexrom merged commit 111eb3e into master Aug 27, 2026
60 checks passed
@waralexrom
waralexrom deleted the cubestore-ws-connections-per-tenant-cap branch August 27, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cube store Issues relating to Cube Store rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants