aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_spa_ordering.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-15 09:19:34 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-15 09:19:34 +0200
commit4fa546a759dc9bd2ab77f793e3f2e660acd31bdb (patch)
tree967f46a0ef0f24086587d76231224667908c6ac1 /packages/meshbay-hub/tests/test_spa_ordering.py
parent84b032c65e17267d41e04605e79eea82a6f5a59f (diff)
downloadmeshbay-4fa546a759dc9bd2ab77f793e3f2e660acd31bdb.tar.gz
perf(upload): several chunks in flight, instead of one per round trip
The uploader read a 48 KB slice, sent it, and waited for the node to acknowledge it before reading the next one. That caps throughput at one chunk per round trip regardless of available bandwidth, and it is worse than the arithmetic suggests: the sender is idle for almost the whole time, so SCTP's congestion window never opens either, and the transport stays slow even when the link is not. Measured against the real node over a 100 ms path (netem on loopback): 48 KB chunks, one at a time 0.16 MB/s 48 KB chunks, 32 in flight 3.47 MB/s On loopback with no latency both are ~32 MB/s, which is why nothing here ever caught it: the local end-to-end run cannot see a round-trip problem. transport.uploadFile() now keeps a window of chunks in flight and matches acks by arrival, with the node's own ordering rule as the guard — a DataChannel is ordered and reliable, and the node refuses any chunk that is not the one it expects next. It pauses when the channel's buffered amount gets high, so the progress bar keeps reporting what the node has taken rather than what the browser has queued. Both callers, the Files panel and chat attachments, go through it. The end-to-end harness grew an opt-in benchmark behind MESHBAY_BENCH=1 that removes its own files afterwards, and it taught me something about the harness rather than the code: it took an unsolicited index_sync push for an upload ack, because unlike app.js it had no place to put one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/test_spa_ordering.py')
-rw-r--r--packages/meshbay-hub/tests/test_spa_ordering.py24
1 files changed, 24 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_spa_ordering.py b/packages/meshbay-hub/tests/test_spa_ordering.py
index 9301fcc..1329b74 100644
--- a/packages/meshbay-hub/tests/test_spa_ordering.py
+++ b/packages/meshbay-hub/tests/test_spa_ordering.py
@@ -143,3 +143,27 @@ def test_admin_page_does_not_borrow_the_members_panel_state():
for name in ("doInvite", "adminId", "inviteCode", "setInviteUser"):
assert name not in admin, \
f"AdminPage references {name}, which only exists in MembersPanel"
+
+
+# ── Upload pipelining ───────────────────────────────────────────────────────
+#
+# The upload loop waited for the node to acknowledge each 48 KB chunk before
+# reading the next one, which caps throughput at one chunk per round trip no
+# matter how much bandwidth there is — and keeps SCTP's congestion window shut,
+# so the transport never speeds up either. Measured over a 100 ms path: 0.16 MB/s
+# waiting for every ack, 3.47 MB/s with 32 chunks in flight.
+
+def test_the_uploader_keeps_several_chunks_in_flight():
+ src = TRANSPORT.read_text()
+ body = src[src.index("async uploadFile("):]
+ body = body[:body.index("\n async ", 1)]
+ assert "UPLOAD_WINDOW" in body, "the send window is gone — uploads are serial again"
+ assert "bufferedAmount" in body, (
+ "without backpressure the file lands in the send buffer in seconds and "
+ "the progress bar becomes fiction")
+
+
+def test_no_caller_waits_for_one_chunk_at_a_time():
+ app = APP.read_text()
+ assert "uploadChunk(" not in app, (
+ "a per-chunk await is back in the SPA; use transport.uploadFile()")