Skip to content

perf(tray): parse hgs output off the GUI thread (event data plane M1) - #367

Merged
ufna merged 2 commits into
mainfrom
work-ba0ed9fd
Oct 27, 2026
Merged

ufna merged 2 commits into
mainfrom
work-ba0ed9fd

Conversation

@ufna

@ufna ufna commented Oct 27, 2026

Copy link
Copy Markdown
Owner

First milestone of the event data plane (epic zerus-604d, task zerus-604d.1). Owner direction 2026-10-27: an event-driven data channel is mandatory, and parsing and similar processing must run outside the GUI thread.

What changes

  • ProcessRunner preparation stage. Every child result passes one serial thread, hgs-prepare, before reaching its owner, in completion order. run(request, owner, prepare, apply) and post(owner, prepare, apply) shape values there; the owner's thread only applies them.
  • All parsing of hgs, connector and network output is off the GUI thread. This covers all ~50 HgsClient replies (listings, inspections, git-status, task, swarm, worktrees, receipts and the direct-QProcess flows), the connector panels, the speech client, the update feed and startup fleet discovery. The task parse pool and the per-lookup re-parse of the 8 MiB worktree snapshot blob are removed.
  • Enforcement.
    • JsonParse is the tray's only JSON parser. A parse on the GUI thread of a GUI application counts as a violation, and every Qt suite runs with ZERUS_GUI_THREAD_CHECKS=fatal.
    • check-source.py rejects QJsonDocument::fromJson outside JsonParse.cpp. It lists the files that may still call JsonParse::local() (small values the tray wrote itself) and how often; the list only shrinks and is empty at the end of M2 (owner: «согласен, оставляем исключения до M2»).
  • SettingsJson parses QSettings JSON values once per change. It warms the large keys on the preparation thread at startup and records what writers stored, so the per-poll read-mark reads no longer re-parse settings.
  • IdentityHash remembers per-row SHA-256 identities. In a 2026-10-27 Mac sample these took 3.9 % of the GUI thread, through OpenSSL digest setup on each call.

Design and baseline: docs/superpowers/specs/2026-10-27-event-data-plane-design.md. API v1, the hgs CLI, status semantics and on-disk settings are unchanged.

Verification (arch, local)

  • ctest (Release, offscreen, ZERUS_GUI_THREAD_CHECKS=fatal): 83/83 suites passed; JUnit shows 3000 test functions executed, 0 failed, 23 skipped (platform-only).
  • New tests: test_jsonparse (violation detection, worker parses, local limit, HgsClient listing and inspection parsed off the GUI thread) and three test_processrunner cases (preparation thread, completion order across prepared and plain results, destroyed owner drops a prepared value).
  • python3 -m unittest discover -s tests -p 'test_source_*.py': 26 tests OK, including the new JSON rule tests. Privacy guard: 0 findings on the changed files.
  • The first fatal run found real GUI-thread parses (requestProjects, the startup discovery, synchronous draft-store reads, the download-cache index), and they are fixed here.

What remains on the GUI thread after each listing (unread passes, rebuild, organization serialization) is M2 (zerus-604d.2).

🤖 Generated with Claude Code

ufna and others added 2 commits October 27, 2026 16:41
Owner direction 2026-10-27: data parsing and similar processing run outside
the GUI thread. First milestone of the event data plane (zerus-604d.1).

- ProcessRunner passes every result through one serial preparation thread,
  hgs-prepare, keeping completion order. run(request, owner, prepare, apply)
  and post(owner, prepare, apply) shape values there; the owner's thread only
  applies them.
- Every HgsClient reply, the connector panels, the speech client, the update
  feed and startup fleet discovery are parsed in prepare steps. The task
  parse pool and the 8 MiB worktree snapshot re-parse per lookup are gone.
- JsonParse is the tray's only JSON parser and reports a parse on the GUI
  thread; every Qt suite runs with ZERUS_GUI_THREAD_CHECKS=fatal.
  check-source.py rejects QJsonDocument::fromJson elsewhere and limits
  JsonParse::local() to a listed, shrink-only set of small local values
  (owner: "согласен, оставляем исключения до M2").
- SettingsJson parses QSettings JSON values once per change, warms the large
  keys at startup and records what writers stored, so per-poll read-mark
  reads no longer re-parse settings.
- IdentityHash remembers per-row SHA-256 identities, 3.9 % of the Mac GUI
  thread in a 2026-10-27 sample.

Design: docs/superpowers/specs/2026-10-27-event-data-plane-design.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ufna
ufna merged commit 16ca223 into main Oct 27, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant