Multi-Hoster-Upload/tasks/todo.md
Administrator e0f789f56f docs(tasks): high-concurrency lag audit — renderer measured-refuted, sync-fs was the blocker (v3.3.90)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 03:44:41 +02:00

3.6 KiB
Raw Permalink Blame History

High-concurrency lag audit (v3.3.90) — "lag ist immernoch da, ich vermute ab X gleichzeitig muss er alle Zeilen gebündelt updaten"

Method: 44-agent high-concurrency audit of the full upload→IPC→render path + Blink benchmark of the renderer queue table (Playwright/Chromium = same Blink engine), targeting the user's NEW hypothesis: "with ~100 concurrent uploads the renderer has to update ALL rows bundled rather than cleanly per-row."

The user's hypothesis is MEASURED-REFUTED — the renderer is NOT the bottleneck.

Blink benchmark over scenarios Q=150..1000, M=10 active, 60 ticks each:

  • renderQueueTable virtualizes at ≥200 rows; <200 = change-detecting in-place update.
  • _updateRowInPlace is change-detecting (no forced reflow, no layout reads).
  • median render <1 ms at Q=1000; only ~4/60 renders are full rebuilds even with progress-crossing sorts.
  • progress is coalesced main-side (_progressByJob Map keyed by jobId + 100ms flush → one batch sized by active-job count, ~10/sec); renderer iterates the batch with cheap per-row handleProgress. DOM amplification is ruled out by measurement. "Laggy at ~40% CPU / 8 cores" = ONE core at 100% = main-thread saturation / synchronous blocking, not DOM.

SHIPPED (v3.3.90) — the two real main-thread blockers, both behavior-preserving

  1. lib/clouddrop-upload.js _uploadChunked: was reading each 16 MB chunk with fs.readSync SYNCHRONOUSLY on the main event loop — unique among the 5 uploaders (the other 4 stream async). Each read blocks the WHOLE loop (~59 ms SSD, 30100 ms slow disk) → freezes all progress/IPC/render/other-uploads, scaling with the number of concurrent clouddrop uploads. Fits "laggy when uploading, worse with more concurrent." User uses clouddrop. Fix: fs.openSync/readSync/closeSyncfs.promises.open + await fh.read + await fh.close(). Byte-equivalence verified by SHA-256 over all chunk-boundary cases (full chunk, partial last chunk, 2/3/4-chunk, single byte) before shipping — a chunk-read bug = corrupt upload.
  2. lib/upload-manager.js rotation-retry (944) + suspect-alternate (1075) progressCb: both called _emitProgress (a synchronous emit('progress') + fresh object spread) on EVERY stream chunk (hundreds/sec per job) — they were missing the 250 ms lastEmitTime gate that the primary path (631) has. With many concurrent uploads in rotation/suspect mode that's real main-thread emit amplification. Mirrored the gate exactly: activeEntry mutation stays UNGATED (stats/speed-monitor stay fresh), only the emit is throttled to 4/sec. Behavior-preserving. 397/397 tests pass, eslint clean (1 pre-existing unrelated warning at line 554).

DROPPED (advisor: measured fine, don't chase perception)

  • Lowering the virtual-row threshold below 200: the Blink benchmark shows <200 in-place updates are already sub-ms; no change warranted.

OPEN — discriminator question to the user (do NOT declare victory blind)

With this user's real config (parallelCount 2 × 5 hosters ≈ 10 max concurrent), "100 concurrent" is only reachable if the parallel counts were raised — otherwise "100" is the QUEUE size and only ~10 upload at once. Ask: (a) is the lag specifically during clouddrop uploads? (b) did you raise the per-hoster/global parallel counts above 2? If it's true high concurrency (dozens of simultaneous undici streams funneling decode + progress callbacks through the one main JS thread), that needs a concurrency cap or a worker/child process — NOT a micro-fix. The two shipped fixes are genuine improvements regardless; the answer decides whether a bigger architectural change is the next step.