Skip to content

perf(parser): skip O(n) frozenset in get_all_sessions on cache hit (#859)#864

Merged
microsasa merged 6 commits intomainfrom
fix/859-skip-frozenset-on-cache-hit-8c36cfa2bd91835e
Apr 8, 2026
Merged

perf(parser): skip O(n) frozenset in get_all_sessions on cache hit (#859)#864
microsasa merged 6 commits intomainfrom
fix/859-skip-frozenset-on-cache-hit-8c36cfa2bd91835e

Conversation

@microsasa
Copy link
Copy Markdown
Owner

Closes #859

Problem

Every call to get_all_sessions unconditionally built a frozenset of all (Path, file_id) pairs — O(n) allocations and O(n) hash operations — before checking _sorted_sessions_cache. When the discovery cache hits (is_cache_hit=True) and no sessions were re-parsed or had name updates (deferred_sessions=[]), this work was entirely wasted since the sorted cache is guaranteed valid.

Fix

Added a cheap early-return check using the already-available is_cache_hit flag before the frozenset construction:

if is_cache_hit and not deferred_sessions and _sorted_sessions_cache is not None:
    return list(_sorted_sessions_cache.summaries)

The existing frozenset path is preserved for cache-miss cases (root changed or sessions re-parsed), so _sorted_sessions_cache is still updated correctly on misses.

Tests

Added TestSortedSessionsCacheFastPath with two tests:

  • test_frozenset_not_built_on_warm_cache_hit — uses a counting wrapper around _SortedSessionsCache to verify it is NOT constructed on the second call when the session state is unchanged (spy/counter approach, no wall-clock assertions).
  • test_frozenset_built_when_session_added — verifies the frozenset path IS still taken when the session set changes (cache miss).

All 1238 tests pass with 99% coverage.

Generated by Issue Implementer · ● 11.2M ·

When is_cache_hit=True and deferred_sessions is empty, the sorted
sessions cache is guaranteed valid. Add an early return before the
frozenset construction to avoid O(n) tuple allocations and hash
computations on every warm-cache call.

Closes #859

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 8, 2026 16:04
@microsasa microsasa added aw Created by agentic workflow perf Performance improvement labels Apr 8, 2026
@microsasa microsasa enabled auto-merge April 8, 2026 16:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets a steady-state performance issue in copilot_usage.parser.get_all_sessions() by avoiding redundant O(n) work when internal discovery/session caches indicate nothing has changed.

Changes:

  • Adds an early-return fast path in get_all_sessions() to skip building the (Path, file_id) frozenset fingerprint on warm discovery-cache hits.
  • Adds new tests intended to verify the fast path behavior and that the fingerprint path still runs when the session set changes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/copilot_usage/parser.py Adds a new early-return fast path before fingerprint construction.
tests/copilot_usage/test_parser.py Adds tests for the warm-cache fast path and cache-miss behavior.

@microsasa microsasa added the aw-ci-fix-attempted CI fixer has attempted to fix CI label Apr 8, 2026
…hits

The fast-path early return in get_all_sessions checked is_cache_hit and
_sorted_sessions_cache but did not verify the cache belonged to the
current base_path. When different paths were used in succession (e.g.
e2e test_empty_directory_graceful followed by FIXTURES-based tests),
the fast path returned stale empty results from the wrong root.

Add a root field to _SortedSessionsCache and guard the fast path with
a root == resolved_root check. Also hoist resolved_root computation
before the stale-prune block to reuse it and eliminate the duplicate
resolve() call.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Commit pushed: 4edd9f4

Generated by CI Fixer

@microsasa
Copy link
Copy Markdown
Owner Author

CI Fix Applied

Root cause: The fast-path early return in get_all_sessions checked is_cache_hit and _sorted_sessions_cache is not None but didn't verify the cache belonged to the current base_path. When e2e tests used different paths in succession (e.g. test_empty_directory_graceful with a temp dir → FIXTURES-based tests), the fast path returned stale empty results from the wrong root, causing 20 e2e test failures ("No sessions found").

Fix: Added a root field to _SortedSessionsCache and guarded the fast path with _sorted_sessions_cache.root == resolved_root. Also hoisted resolved_root before the stale-prune block to eliminate a duplicate resolve() call.

All checks pass: ruff ✅ | pyright ✅ | bandit ✅ | unit tests (99% cov) ✅ | e2e tests (86 passed) ✅

Generated by CI Fixer · ● 16.5M ·

@microsasa
Copy link
Copy Markdown
Owner Author

CI fix already attempted once — stopping to prevent loops. Manual intervention needed.

Generated by CI Fixer · ● 420.7K ·

@microsasa microsasa added the aw-review-response-attempted Responder has attempted to address review comments label Apr 8, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Commit pushed: 505ddce

Generated by Review Responder

- Replace fast path that skipped fingerprint construction with a
  fingerprint-validated fast path: always build the O(n) frozenset
  fingerprint but skip the sort when is_cache_hit, no deferred sessions,
  and the fingerprint matches the cached value. This prevents cross-root
  false hits when get_all_sessions() is called with different base_path
  values in the same process.

- Update tests to spy on both session_sort_key and _SortedSessionsCache
  construction, ensuring the test actually validates the optimization
  (sort skipped) rather than just cache construction (which was already
  skipped pre-PR).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 8, 2026 16:26
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

@microsasa microsasa added aw-review-response-1 Review response round 1 aw-review-response-attempted Responder has attempted to address review comments and removed aw-review-response-attempted Responder has attempted to address review comments labels Apr 8, 2026
- Extract _build_fingerprint() helper for testability
- Add cheap root-based early return BEFORE O(n) frozenset construction
- Restore original fingerprint-only fallback (drop is_cache_hit gate)
- Remove duplicate TestSortedSessionsCacheFastPath test class
- Add test asserting _build_fingerprint is skipped on warm cache hit

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Commit pushed: d98a738

Generated by Review Responder

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

@microsasa microsasa added aw-review-response-2 Review response round 2 and removed aw-review-response-attempted Responder has attempted to address review comments labels Apr 8, 2026
- Add len(summaries)==len(discovered) guard to fast-path early return
  to prevent stale cache when a session re-parse fails/yields nothing
- Add test_fingerprint_built_when_session_added to verify the fingerprint
  path is taken when the session set changes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Commit pushed: fd8293b

Generated by Review Responder

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

@microsasa microsasa added the aw-review-response-attempted Responder has attempted to address review comments label Apr 8, 2026
Have _discover_with_identity return the resolved root so
get_all_sessions can reuse it instead of calling .resolve() again.
This eliminates the redundant Path.resolve() call on every invocation,
including warm-cache hits.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Commit pushed: 543b17f

Generated by Review Responder

@microsasa microsasa requested a review from Copilot April 8, 2026 17:02
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@microsasa microsasa added the aw-quality-gate-approved Quality gate approved the PR label Apr 8, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quality Gate: APPROVED (auto-merge eligible)

Impact: MEDIUM — Internal performance refactoring of private cache logic (_SortedSessionsCache, _discover_with_identity) with unchanged public API.

What was evaluated:

  • Optimization logic: The 5-condition early-return guard (is_cache_hit, not deferred_sessions, len(summaries) == len(discovered), cache exists, same root) is sound — when all hold, the sorted order cannot have changed, so the O(n) frozenset construction is correctly skipped.
  • Return type change: _discover_with_identity now returns the resolved root as a 3-tuple element. All 30+ existing call sites updated correctly.
  • _build_fingerprint extraction: Clean refactor — the inline frozenset comprehension is now a named, testable function.
  • _SortedSessionsCache.root field: Added correctly to the dataclass(slots=True) and threaded through construction.
  • Tests: Two new tests (test_fingerprint_skipped_on_warm_cache_hit, test_fingerprint_built_when_session_added) use spy/counter approach consistent with repo conventions — no wall-clock timing.
  • CI: All 9 checks pass (lint, typecheck, security, tests, CodeQL).

@microsasa microsasa merged commit d507134 into main Apr 8, 2026
9 checks passed
@microsasa microsasa deleted the fix/859-skip-frozenset-on-cache-hit-8c36cfa2bd91835e branch April 8, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

aw Created by agentic workflow aw-ci-fix-attempted CI fixer has attempted to fix CI aw-quality-gate-approved Quality gate approved the PR aw-review-response-1 Review response round 1 aw-review-response-2 Review response round 2 aw-review-response-attempted Responder has attempted to address review comments perf Performance improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[aw][perf] parser: redundant O(n) frozenset construction in get_all_sessions on every discovery-cache-hit call

2 participants