Repository navigation
fix(scores): send pending scores immediately on flush() - #2025
Conversation
The async experiment runner called the blocking flush() at the end of the run, which froze the caller's event loop while scores and spans were sent. Co-authored-by: hassieb.pakzad <hassieb.pakzad@clickhouse.com>
…e score consumer Only the span processor read these variables, so scores kept the 15 / 1s defaults when batching was configured through the environment. Co-authored-by: hassieb.pakzad <hassieb.pakzad@clickhouse.com>
flush() waited on the score queue while the consumer kept collecting until flush_interval ran out, so a single queued score made flush() block for up to the full interval. flush() now wakes live consumers so the pending batch is sent right away. Co-authored-by: hassieb.pakzad <hassieb.pakzad@clickhouse.com>
A flush() racing shutdown() could put its wake signal on the queue after the consumer had already exited. Nothing acknowledged it, so Queue.join() never returned. flush() now waits on the queue's condition and stops when every consumer has stopped. Co-authored-by: hassieb.pakzad <hassieb.pakzad@clickhouse.com>
|
@claude review |
There was a problem hiding this comment.
Claude Code Review
No review was started: this request came from a bot account. Manual reviews can only be requested by someone with write access to this repository. Ask a maintainer to comment @claude review, or have your automation post the comment from a user account with write access.
Tip: disable this comment in your organization's Code Review settings.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 87c50c24f4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
What does this PR do?
flush()with a single queued score blocked for most of the score consumer'sflush_interval, and longer against a slow server. The async experiment runner also called that blockingflush()on the event loop. This PR fixes both, with no public API changes:flush()wakes the score consumer. Before waiting on the score queue,flush()puts a wake signal on it, so the consumer stops collecting and sends the pending batch right away. This reuses the signalpause()already sent, renamed to_WAKE_SIGNAL, and addsScoreConsumer.wake().flush()no longer hangs when consumers stop under it. Ifflush()runs concurrently withshutdown(), the consumer can exit before it takes the wake signal. Nothing acknowledges the signal, soQueue.join()would never return.flush()now waits on the queue'sall_tasks_donecondition, with a 0.1 s timeout as a backstop, and stops waiting once no consumer is alive (logged at debug). The wake-up still comes from thetask_done()notify, so normal latency doesn't change._run_experiment_asyncflushes off the loop withawait asyncio.to_thread(self.flush).LANGFUSE_FLUSH_AT/LANGFUSE_FLUSH_INTERVAL. Previously only the span processor read them, so scores stayed at 15 / 1 s when batching was configured through the environment. Constructor arguments still take precedence. Defaults are unchanged when nothing is set.Measured (local fake server; one score, then
flush(), in a loop)685dc884)flush_interval=5LANGFUSE_FLUSH_INTERVAL=5_run_experiment_async(5 items) awaited on the caller's loop, max loop gapThe public
run_experiment()called from inside a running loop still blocks that loop for the whole run, becauserun_async_safelyjoins a thread. Fixing that needs a public async entry point and is out of scope here.Type of change
Verification
New tests were written first; six of them failed before their fix:
test_reads_flush_at_from_env: got one batch[a, b, c]instead of[a, b], [c]test_reads_flush_interval_from_env: waited 1.0 s, expected under 0.5 stest_wake_sends_pending_batch_before_flush_interval:wakedid not existtest_flush_sends_pending_score_without_waiting_for_flush_interval: 5.0 s, expected under 1 stest_final_flush_does_not_block_event_loop: loop gap 0.50 s, expected under 0.25 stest_flush_returns_when_consumer_stops_after_liveness_check:flush()still blocked after 2 s. The test stops the consumer between the liveness check and the wake signal, and acknowledges the orphaned signal itself so a hang can't block teardown.test_constructor_flush_settings_win_over_envandtest_flush_after_shutdown_returnspass on main as well. They guard behavior that this change could break.Skipped:
tests/e2eandtests/live_providerneed a Langfuse server and provider keys. The behavior is covered by unit tests using a mock transport.Checklist
code_review.md..env.templateif needed. (Not needed: the env vars are already documented, and the score consumer now matches those docs.)The PR appears safe to merge, with a non-blocking gap in flush latency when the score queue is full.
Summary
This PR makes
flush()wake the score consumer and moves the experiment runner's final flush off the event loop.Acknowledged behavior: hassiebp explicitly describes returning when consumers stop as intentional. This prevents an orphaned wake signal from hanging
flush()during concurrentshutdown().Diagram
sequenceDiagram participant E as Async experiment participant F as Flush worker participant Q as Score queue participant C as Score consumer E->>F: Run flush off the event loop F->>Q: Add wake signal Q->>C: Scores, then wake signal C->>C: Send pending batch C->>Q: Acknowledge completed work F->>Q: Wait for completion or stopped consumer F-->>E: Flush returnsReviews (1) · Last reviewed commit: "fix(scores): stop waiting in flush() onc..." · Reviewed by Greptile