Commit Graph

268 Commits

Author SHA1 Message Date
Administrator
c3381d360f perf(config): split history out of the hot config file — the real 38.5MB main-thread thrash (v3.3.99)
The v3.3.98 instrument exposed the actual cause of the 1-2s UI button lag, and it
was none of the read-path suspects. electron-config.json had grown to 38.5MB and
was being load()/structuredClone()/JSON.stringify()'d ~137 times in a 73s window
(140-592ms each) on the Electron main thread — roughly 47% main-thread occupancy.
That is the lag: a button click lands while the thread is mid-clone of a 38.5MB
object. The 1MB read-ahead could not touch it.

The bulk is history, not the queue. Each batch-done appended the upload-manager
summary verbatim, including the full per-file result list (per-hoster URLs), into
config.history; default historyRetention='all' never prunes, so it grew unbounded
(75 batches ≈ 38.5MB). The "queue=undefined" in the perf log was a logging bug
(reading .length on the pendingQueue object), not an empty queue. Writes drove the
storm: queue-persistence (save-global-settings) did two loads + one 38.5MB
serialize per call, and _atomicWrite nulls the cache so the next load is a full
38.5MB reparse; even cache hits structuredCloned the whole 38.5MB. The per-job
upload path makes zero config calls, so pending=1280 was never the driver.

Fix — move history into its own file so it is never parsed/cloned/serialized on a
config op (a 5-agent investigation + adversarial review chose this over three
read-side half-fixes; it is the only change that removes the clone AND the
serialize AND the post-write reparse at once):

- History lives in electron-history.json. _migrateHistory() runs once at init
  (packaged only) and is fail-safe: it writes history.json (tmp → fsync → rename),
  re-reads and verifies the entry count, and keeps a permanent
  electron-config.json.pre-history-split.bak BEFORE the config is ever allowed to
  drop its history. If verification fails it leaves history in the config (retry
  next launch). _loadImpl returns history:[] once migrated, so the cached object is
  tiny (cheap clones); the config file shrinks to ~KB on the first save (cheap
  reparse) and _serializeForDisk writes ~KB (cheap serialize).
  loadHistory/appendHistory/pruneHistory/clearHistory go through history.json on
  their own write-queue with a no-clobber guard; the legacy config path stays as a
  fallback when migration did not run.
- Validated against the real 185MB / 30000-entry bench fixture: migrate 1.5s once,
  every entry preserved + .bak kept; load() 631ms cold once → 0.1ms after the first
  save strips the file (185MB → 2.1KB); loadHistory() still returns all 30000.

Dropped per review (one-variable + risk): loadShallow (moot after the split),
cache-repopulate (its gate can never fire), and a per-batch resolution cache (stale
account pools → the rotation/byse failover-regression class — the one thing that
could silently corrupt uploads).

Instrumentation (the user asked to measure everything; all additive, threshold-
gated, MHU_PERF=0 disables):
- ipcMain.handle/.on are centrally wrapped to log `ipc <channel> wall=Xms sync=Yms`
  over 50ms — the button-press→response latency — hardened (Promise.resolve(p)
  .finally + try/catch'd logging) so a logging failure can never break IPC.
- A 100ms main-process drift monitor logs `main-longtask blocked=Xms lastIpc=… gc=…`
  for any single main-thread turn over 100ms (catches GC, fs scans, serialize that
  the IPC timing structurally cannot see).
- config-store perf lines gain via=<caller> and wqDepth=, and the queue= logging
  bug is fixed (now reads pendingQueue.queueJobs.length).

405 tests pass (9 new migration tests: preserve-count, round-trip, save() never
loses history, crash-window fallback, idempotency). Clean Electron boot, no repo
pollution (migration is packaged-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:37:12 +02:00
Administrator
121eac5f14 perf(uploads): 1MB read-ahead to absorb read-bursts + instrument the config-persist/load path (v3.3.98)
v3.3.97 (UV_THREADPOOL_SIZE 64→8) was a decisive win — mean event-loop-delay at
70 active uploads dropped 200ms→~11ms (18×), rss 577→287MB, renderer healthy in
14/15 windows. But the user reports it is still not perfectly smooth. A focused
multi-agent investigation plus an adversarial review localized the residual to
TWO distinct, separately-measured spike sources:

1. Read-bursts. In the tail windows the file-read histogram inverts: FSReqCallback
   climbs to 66-70 against threadpool=8 (~8.75× queue depth) while SimpleWriteWrap
   (socket writes) collapses to 4-24 and mean delay rises to 30-42ms. GC is ruled
   out (gcMax ≤27ms in every window). The clean inversion at a stable active=70 /
   pending=1287 shows the reads are causal, not a symptom of a block elsewhere.

2. A suspected synchronous config-persist stall. save() → load() reparses the whole
   electron-config.json — which now carries the 1287-job pending queue nested in
   globalSettings plus full history — on every persist (because _atomicWrite nulls
   the read cache), then _serializeForDisk JSON.stringify(…, null, 2) of all of it.
   One tail sample (max 1021ms, heap spiking to 142MB) fits a large synchronous
   structuredClone+stringify, but it is a single confounded point, so this build
   only INSTRUMENTS the path rather than asserting the cause.

This release ships one behavioral change (kept to a single variable so the next
log attributes cleanly) plus measurement:

- highWaterMark 256KB→1MB in all five streaming read loops (lib/hosters.js,
  doodstream/voe/vidmoly CHUNK_SIZE consts, and the inline value in
  clouddrop-upload.js:108 — NOT the 16MB server chunk at clouddrop-upload.js:12).
  UV_THREADPOOL_SIZE stays 8. This deepens each stream's read-ahead cushion from
  ~0.43s to ~1.7s at the per-stream rate, so a stream tolerates the threadpool
  queue without starving its socket write, and cuts read-completion callbacks and
  per-chunk Buffer allocations ~4×. Byte-correctness is unaffected: Content-Length
  is preamble+fileSize+epilogue, independent of chunk size, and the chunk size
  never touches the multipart boundaries. Fully reversible; a dedicated read-
  concurrency semaphore is held in reserve if 1MB does not clear the bursts.

- config-store.js now times load() (the full reparse, which the account-failed
  handler also hits per failure) and the _commit serialize, logging
  `config-load …` / `config-serialize wall=…ms bytes=… hist=… queue=…` when the
  synchronous work exceeds 20ms. load() is split into a timing wrapper + _loadImpl;
  the timer is a no-op until main.js wires configStore.setPerfLog → logInfo.

The renderer batch-drain fix for the one observed 243ms longtask is intentionally
deferred: that jank is downstream of the main-thread read-burst flooding IPC, so
fix #1 should make it self-heal; bundling it would confound the measurement and
touch the progress hot path. All 397 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:55:34 +02:00
Administrator
a5b835f76a perf(uploads): threadpool 64→8 + GC/heap instrumentation (decisive high-concurrency lag build, v3.3.97)
The v3.3.96 eventloop-delay logs from a real 70-connection run pinpointed the
high-concurrency lag to the file-read path. At a CONSTANT active-job count the
process flips between two clean regimes:

  HEALTHY  ELD ~11ms,  rss 268-308MB:  SimpleWriteWrap ≈ active, FSReqCallback ≈ 0-1
  BLOCKED  ELD 49-217ms, rss 540-610MB: FSReqCallback ≈ active (62-71 reads in
                                        flight), SimpleWriteWrap ≈ 0-4

Both the ELD spike and the rss balloon track FSReqCallback (libuv-threadpool file
reads) exactly — not crypto, not the renderer, not GC alone. All five uploaders
read identically via fs.createReadStream({highWaterMark: 256KB}); byse/doodstream/
voe run through the generic uploadFile in hosters.js (no dedicated module).

This build is both a candidate fix and a discriminator, per advisor review:

- UV_THREADPOOL_SIZE 64→8 (main.js:1). One reversible line, NOT an upload cap —
  70 uploads still run. 8 concurrent 256KB reads sustain ~100MB/s, far above the
  41MB/s aggregate, so it cannot bottleneck throughput even on the slow VM disk.
  Strong suspicion that tp=64 made it worse: it removed the natural read-
  serialization (default 4 threads) and let all 70 streams' reads fire at once,
  flooding the loop with completion callbacks in lock-step bursts.

- ELD line now also logs heap=heapUsed ext=external ab=arrayBuffers and
  gc=/gcTotal=/gcMax=ms (PerformanceObserver entryTypes:['gc'], reset per window).
  The 70×256KB ≈ 18MB of read buffers cannot account for the ~300MB rss swing —
  that is heap/object churn, so GC must be measured directly.

Decision rule for the next real-run log:
  - ELD drops with tp=8           → read over-parallelism confirmed (keep 8 or add
                                     a dedicated read-semaphore).
  - ELD high + GC pauses align    → heap churn, hunt the allocator.
  - ELD high + GC flat            → causation was reversed, pivot.

No upload-behavior change; the concurrency cap the user explicitly rejected is
untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:09:46 +02:00
Administrator
59dbd4b41c release: v3.3.96 2026-06-21 16:48:21 +02:00
Administrator
54dbba223d fix(diag): log the full eventloop-delay payload + harden tray icon load
The v3.3.94 measurement computed the main-process event-loop delay, CPU%, per-hoster connection distribution and transient error counts, but logged only '[INFO ] perf': logInfo(ctx, msg) treats a string first arg as the whole message and discards the second, so the payload was thrown away (confirmed in the user's upload-debug.log). Pass the line as a single argument so the real numbers reach the log.

Also: assets/ was missing from the electron-builder files list, so app_icon.ico was never packaged into the asar — new Tray() threw an unhandled rejection on every startup and left no tray icon. Package the icons and wrap createTray in try/catch with a nativeImage fallback so a missing/invalid icon can never crash tray creation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 16:47:48 +02:00
Administrator
7f7da8a619 release: v3.3.95 2026-06-21 16:33:51 +02:00
Administrator
58db913275 release: v3.3.94 2026-06-21 16:24:24 +02:00
Administrator
ad87e36a8f release: v3.3.93 2026-06-21 05:05:38 +02:00
Administrator
10854a8f43 release: v3.3.92 2026-06-21 04:21:01 +02:00
Administrator
f63ca53f2f release: v3.3.91 2026-06-21 04:11:21 +02:00
Administrator
b85efcfe3d release: v3.3.90 2026-06-21 03:45:30 +02:00
Administrator
ea14d11ee2 release: v3.3.89 2026-06-21 03:06:44 +02:00
Administrator
6dcc98f52d release: v3.3.88 2026-06-21 02:30:40 +02:00
Administrator
c37c8e3906 release: v3.3.87 2026-06-21 01:49:23 +02:00
Administrator
3f8854693a release: v3.3.86 2026-06-19 19:39:15 +02:00
Administrator
70e7f2a9fd release: v3.3.85 2026-06-19 18:45:22 +02:00
Administrator
b423357a2c release: v3.3.84 2026-06-19 17:48:08 +02:00
Administrator
7a025be645 release: v3.3.83 2026-06-19 16:22:29 +02:00
Administrator
2efbc355b3 release: v3.3.82 2026-06-19 14:53:48 +02:00
Administrator
e216c95b61 release: v3.3.81 2026-06-19 06:52:31 +02:00
Administrator
f7c8d308fc release: v3.3.80 2026-06-19 04:36:20 +02:00
Administrator
96d6dfe880 release: v3.3.79 2026-06-19 02:32:33 +02:00
Administrator
abb1621a60 release: v3.3.78 2026-06-19 02:20:33 +02:00
Administrator
f81f864314 release: v3.3.77 2026-06-19 01:56:55 +02:00
Administrator
6f8c2dcb38 release: v3.3.76 2026-06-17 13:42:19 +02:00
Administrator
9a933a9feb release: v3.3.75 2026-06-17 03:34:01 +02:00
Administrator
950f103f99 release: v3.3.74 2026-06-17 03:22:31 +02:00
Administrator
b032dd6b3f release: v3.3.73 2026-06-16 00:44:39 +02:00
Administrator
5d04ddced3 release: v3.3.72 2026-06-15 21:45:28 +02:00
Administrator
1e3f2ca51a release: v3.3.71 2026-06-15 01:32:55 +02:00
Administrator
867609c6a9 release: v3.3.70 2026-06-15 01:13:43 +02:00
Administrator
e2b0fab446 release: v3.3.69 2026-06-15 01:05:16 +02:00
Administrator
68c4007c82 release: v3.3.68 2026-06-15 00:47:09 +02:00
Administrator
cafe777377 release: v3.3.67 2026-06-15 00:31:11 +02:00
Administrator
221eb55380 release: v3.3.65 2026-06-10 23:16:56 +02:00
Administrator
3573c6860a release: v3.3.64 2026-06-10 16:53:05 +02:00
Administrator
8e03212554 release: v3.3.63 2026-06-10 15:42:37 +02:00
Administrator
99a2c2fe96 release: v3.3.62 2026-06-10 00:08:26 +02:00
Administrator
8300d13817 release: v3.3.61 2026-06-09 23:41:05 +02:00
Administrator
d67cfc0b51 release: v3.3.60 2026-06-09 21:56:05 +02:00
Administrator
f323f434ce release: v3.3.59 2026-06-09 20:40:36 +02:00
Administrator
7749699830 release: v3.3.58 2026-06-09 06:02:16 +02:00
Administrator
b93617ace9 release: v3.3.57 2026-06-09 05:03:05 +02:00
Administrator
05fae3209d release: v3.3.56 2026-06-09 04:58:22 +02:00
Administrator
f0fb5f881f release: v3.3.55 2026-06-08 23:04:14 +02:00
Administrator
127807d62a release: v3.3.54 2026-06-08 22:03:41 +02:00
Administrator
ddf2710fc6 release: v3.3.53 2026-06-08 21:28:34 +02:00
Administrator
f0608dcda1 release: v3.3.52 2026-06-08 21:19:19 +02:00
Administrator
d159ac484a release: v3.3.51 2026-06-08 19:22:54 +02:00
Administrator
169817f707 release: v3.3.50 2026-06-08 14:20:16 +02:00