rpc/mod.rs::handle_connection awaits handle_line inside its select loop, so while one request is being served that client gets no responses and no events — the window shows "Connecting…" and stops updating.
Found while adding the Sneedchat room list, which takes ~15s (Tor + the site's proof-of-work gate). That one is worked around by returning a cached answer and doing the real read in a background task, but the general problem stands: any slow handler does this. Matrix' searchMatrixRooms across several homeservers, fetchThread, backfill, and media fetches are all candidates.
Done looks like: requests are handled concurrently — spawn per request, write responses through a channel, and share subscriptions — so one slow call can no longer stop the socket. Request ids already exist, so out-of-order responses are fine.
`rpc/mod.rs::handle_connection` awaits `handle_line` inside its select loop, so while one request is being served that client gets no responses and **no events** — the window shows "Connecting…" and stops updating.
Found while adding the Sneedchat room list, which takes ~15s (Tor + the site's proof-of-work gate). That one is worked around by returning a cached answer and doing the real read in a background task, but the general problem stands: any slow handler does this. Matrix' `searchMatrixRooms` across several homeservers, `fetchThread`, `backfill`, and media fetches are all candidates.
**Done looks like:** requests are handled concurrently — spawn per request, write responses through a channel, and share `subscriptions` — so one slow call can no longer stop the socket. Request ids already exist, so out-of-order responses are fine.
Finished. Requests are handled concurrently: each becomes a task, and answers come back through a channel the connection loop owns, so the loop is never blocked on anything but its own writes. Responses can now arrive out of order, which was always allowed — every response carries the id of the request it answers, and the client already matches on it.
Three things the change needed:
Subscriptions became shared (Arc<Mutex<HashSet>>), since subscribe is written from inside a task while the event arm reads it. A std mutex, held for a set insert and never across an await.
32 requests in flight per client, so a client asking for ten thousand things queues instead of making the daemon hold ten thousand futures. Well above what a window does — a busy startup is a few dozen.
A panicking handler is caught. It used to take the connection down with it; now it would instead leave one request unanswered forever, which is a promise nothing settles. It answers with an error instead.
Also shutdown now waits 100ms before notifying, because its own "ok" is still on its way to the loop — a daemon that exits first answers the request to stop by dropping the socket, which a client cannot tell apart from a crash.
Measured live with a 4.6s Matrix directory search in flight (aimed partly at an unroutable server to make it slow): listBuffers and listAccounts both answered at 255ms, and a presenceChange event arrived at 282ms — during the slow call. All three would have waited the full 4.6s before.
One gap worth stating: there is no automated regression test for this. Exercising it needs a real socket and a fully built AppState, and I judged the harness heavier than the change; the verification above is manual.
nobilis cdfbfae
Finished. Requests are handled concurrently: each becomes a task, and answers come back through a channel the connection loop owns, so the loop is never blocked on anything but its own writes. Responses can now arrive out of order, which was always allowed — every response carries the id of the request it answers, and the client already matches on it.
Three things the change needed:
- **Subscriptions became shared** (`Arc<Mutex<HashSet>>`), since `subscribe` is written from inside a task while the event arm reads it. A std mutex, held for a set insert and never across an await.
- **32 requests in flight per client**, so a client asking for ten thousand things queues instead of making the daemon hold ten thousand futures. Well above what a window does — a busy startup is a few dozen.
- **A panicking handler is caught.** It used to take the connection down with it; now it would instead leave one request unanswered forever, which is a promise nothing settles. It answers with an error instead.
Also `shutdown` now waits 100ms before notifying, because its own "ok" is still on its way to the loop — a daemon that exits first answers the request to stop by dropping the socket, which a client cannot tell apart from a crash.
**Measured live** with a 4.6s Matrix directory search in flight (aimed partly at an unroutable server to make it slow): `listBuffers` and `listAccounts` both answered at **255ms**, and a `presenceChange` event arrived at **282ms** — during the slow call. All three would have waited the full 4.6s before.
One gap worth stating: there is no automated regression test for this. Exercising it needs a real socket and a fully built AppState, and I judged the harness heavier than the change; the verification above is manual.
nobilis cdfbfae
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
rpc/mod.rs::handle_connectionawaitshandle_lineinside its select loop, so while one request is being served that client gets no responses and no events — the window shows "Connecting…" and stops updating.Found while adding the Sneedchat room list, which takes ~15s (Tor + the site's proof-of-work gate). That one is worked around by returning a cached answer and doing the real read in a background task, but the general problem stands: any slow handler does this. Matrix'
searchMatrixRoomsacross several homeservers,fetchThread,backfill, and media fetches are all candidates.Done looks like: requests are handled concurrently — spawn per request, write responses through a channel, and share
subscriptions— so one slow call can no longer stop the socket. Request ids already exist, so out-of-order responses are fine.Finished. Requests are handled concurrently: each becomes a task, and answers come back through a channel the connection loop owns, so the loop is never blocked on anything but its own writes. Responses can now arrive out of order, which was always allowed — every response carries the id of the request it answers, and the client already matches on it.
Three things the change needed:
Arc<Mutex<HashSet>>), sincesubscribeis written from inside a task while the event arm reads it. A std mutex, held for a set insert and never across an await.Also
shutdownnow waits 100ms before notifying, because its own "ok" is still on its way to the loop — a daemon that exits first answers the request to stop by dropping the socket, which a client cannot tell apart from a crash.Measured live with a 4.6s Matrix directory search in flight (aimed partly at an unroutable server to make it slow):
listBuffersandlistAccountsboth answered at 255ms, and apresenceChangeevent arrived at 282ms — during the slow call. All three would have waited the full 4.6s before.One gap worth stating: there is no automated regression test for this. Exercising it needs a real socket and a fully built AppState, and I judged the harness heavier than the change; the verification above is manual.
nobilis cdfbfae