feat: debounced partial spans — delay emission, cancel on fast finish - #38
Merged
PabloPardoGarcia merged 1 commit intoJul 29, 2026
Conversation
… (GLA2-244) partial_spans_delay (env GLASSFLOW_PARTIAL_SPANS_DELAY, clamped [0, 60]s, default 0 = the GLA2-195 emit-immediately behavior with no scheduler thread at all): a pending snapshot is held for N seconds and emitted only if its span is STILL OPEN — a span finishing first costs zero network. Most agent spans live milliseconds, so a small delay cuts pending volume drastically while the live view stays useful. Mechanics: one shared daemon scheduler (deadline heap + key->snapshot registry under a condition variable; cancelled heap entries discarded lazily), NOT a timer thread per span. The snapshot is still built at on_start and held — content set during the delay can never leak onto a pending — and a delayed pending is byte-identical to an immediate one: zero wire/backend/UI impact. Cancellation rides the processor's already-received on_end. Fork children re-arm with an empty registry (module-level register_at_fork over a weak set, the heartbeat sender's pattern). Lifecycle: shutdown() drops not-yet-due pendings (their final spans are being flushed at that moment). force_flush() deliberately does NOT drop — flush happens mid-operation, and killing scheduled pendings there would silently disable liveness for spans that stay open; deviation from the ticket's prose, consistent with its ACs, documented in code. Tests use injected clocks and bounded Event waits — no sleeps.
PabloPardoGarcia
merged commit Jul 29, 2026
368e678
into
pablo/gla2-195-pending-spans
7 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements GLA2-244, stacked on #37 (merge that first; this PR's base is its branch).
What
init(partial_spans=True, partial_spans_delay=N)(envGLASSFLOW_PARTIAL_SPANS_DELAY, clamped[0, 60]s): pending snapshots are held N seconds and emitted only if the span is still open — a span finishing within the delay produces no pending on the wire at all. Default0is byte-for-byte the #37 behavior, with no scheduler thread even created.This is the project's volume escape valve: most agent spans live milliseconds, so a few seconds of delay cuts pending traffic ~95% while anything worth watching live (open longer than the delay) still appears.
Design
(trace_id, span_id) → snapshotregistry under a condition variable.scheduleis O(log n),cancelis O(1) (heap entries for cancelled keys discarded lazily) — hot-path safe._emit, cancellation rides the processor's already-receivedon_end, and the snapshot is still built aton_startand held — content set during the delay (set_input…) can never leak onto a pending. A delayed pending is byte-identical to an immediate one: zero wire/backend/UI changes.register_at_forkover a weak set; children re-arm with an empty registry — parent spans aren't the child's).shutdown()drops not-yet-due pendings (their final spans are flushing at that moment; anything emitted would be instantly superseded). One documented deviation from the ticket's prose (consistent with its ACs):force_flush()does not drop — flush happens mid-operation, and killing scheduled pendings there would silently disable liveness for spans that stay open.Tests
TDD, 9 new: config default/env/clamp, scheduler pure logic with an injected clock (not-due, emit-once, cancel, shutdown-drops, post-shutdown no-op), one real-thread smoke test via a bounded
Event.wait, and end-to-end throughinit()(fast span → zero pendings on the wire;delay=0→ #37 behavior). No sleeps anywhere. Full suite 165 passed; ruff + format + mypy --strict clean.Per-kind delay (roots immediate, leaves delayed) is noted in the ticket as the future refinement; the scheduler supports it without wire changes.