Skip to content

fix(fleet): stream history event logs instead of materializing full lists - #438

Merged
Jason Robert (jrob5756) merged 2 commits into
mainfrom
fix/436-fleet-history-memory-fix
Aug 14, 2026
Merged

fix(fleet): stream history event logs instead of materializing full lists#438
Jason Robert (jrob5756) merged 2 commits into
mainfrom
fix/436-fleet-history-memory-fix

Conversation

@jrob5756

Copy link
Copy Markdown
Collaborator

Summary

  • _read_full_log is now a generator that yields events one at a time instead of building a full list.
  • _scan_history_events accepts an Iterable and makes a single forward pass, so it can consume the streaming generator.
  • Bounds Fleet History memory usage to O(1) (one raw line + one parsed event dict) regardless of retained event log size, instead of loading the entire log into memory before scanning.

Closes#436

Test plan

  • uv run pytest tests/test_fleet/test_history.py -q — 36 passed

Jason Robertand others added 2 commits August 14, 2026 12:25
…ists
_read_full_log now yields events one at a time and _scan_history_events
accepts an Iterable, so a large retained event log is read in O(1)
memory (one line + one parsed dict at a time) instead of loading every
event into a list before scanning. Fixes a memory blowup when building
Fleet history entries from large logs.
…e in #438 review
Blocking:
- Fix _read_full_log's docstring: memory is bounded by the largest
single line (not O(1)) since `for raw_line in f` reads a whole line
before yielding; a 5 MiB single-line log is still materialized whole.
- Correct the false "sole consumer drains to completion" claim, which
the same commit's own tests contradict by consuming the generator
directly and abandoning it early.
- Rename test_corrupt_log_raises_only_on_exhaustion to
test_corrupt_log_raises_on_first_next and assert the real (stronger)
guarantee via next() instead of list(), and correct
_CorruptEventLogError's docstring to match (no drain-dependent
timing exists for an all-garbage log).
Recommendations applied:
- Widen the per-line parse guard to catch RecursionError so one
hostile line can't silently discard an otherwise-valid log.
- Count and log skipped unparseable lines so partial corruption is no
longer silent.
- Split build_history_entries' blanket except into an expected
OSError/_CorruptEventLogError branch (warning) and an unexpected
Exception branch (error), so a real defect in _scan_history_events
isn't hidden at warning level forever.
- Fix the stale "in the tail" wording on total_tokens (History reads
the whole log, not a tail).
- Narrow the json-module monkeypatch to json.loads via patch.object
(stops breaking on an unrelated json.* addition to history.py).
- Drop the redundant `not isinstance(result, list | Sequence)`
assertion (list is already a Sequence; the isgenerator assertion
subsumes it) and its now-unused import.
- Remove a dead _write_log call and a redundant local module import
in existing tests.
- Add a spy test pinning that _build_entry actually streams into
_scan_history_events rather than materializing a list first.
- Add coverage for a log with one parseable line among garbage (must
not be treated as corrupt) and for the deferred-OSError contract on
a missing path.
- Narrow test_scan_accepts_a_one_shot_iterator's docstring/assertions
to what it actually pins (single-pass consumption via a yield
counter, not mere non-indexing).
- Add a CHANGELOG entry and extend AGENTS.md's fleet/history.py bullet
to document the single-pass generator contract.
Skipped (see review response): wrapping _read_full_log in
contextlib.closing at the _build_entry call site, and the matching
`assert inspect.isgenerator(gen)` treatment in the lazy-parse test as
originally worded -- contextlib.closing requires _SupportsClose,
which the deliberately-kept Iterator return annotation doesn't
satisfy, and `uv run ty check src` confirms the resulting type error.
Docstring restructuring into Google-style Yields/Raises sections was
also skipped as disproportionate to this pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jrob5756
Jason Robert (jrob5756) marked this pull request as ready for review August 14, 2026 16:47
@jrob5756
Jason Robert (jrob5756) merged commit 2a5cf86 into mainAug 14, 2026
11 checks passed
Sign up for freeto 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.

fleet: history._read_full_log holds the whole parsed log in memory, contradicting its own docstring

1 participant

@jrob5756