Skip to content

📝 CodeRabbit Chat: Implement requested code changes#131

Open
coderabbitai[bot] wants to merge 1 commit into
issue-26-use-deterministic-synchronisation-in-routine-heartbeat-rsfrom
coderabbitai/chat/93d9d37
Open

📝 CodeRabbit Chat: Implement requested code changes#131
coderabbitai[bot] wants to merge 1 commit into
issue-26-use-deterministic-synchronisation-in-routine-heartbeat-rsfrom
coderabbitai/chat/93d9d37

Conversation

@coderabbitai
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot commented Apr 8, 2026

Code changes was requested by @leynos.

The following files were modified:

  • src/agent/routine_engine.rs
  • tests/e2e_traces.rs
  • tests/e2e_traces/routine_cooldown.rs
  • tests/e2e_traces/routine_cron.rs
  • tests/e2e_traces/routine_event.rs
  • tests/e2e_traces/routine_system_event.rs
  • tests/support/routine_sync.rs
  • tests/support/routines.rs

Summary by Sourcery

Introduce libsql-gated test-only synchronization utilities for RoutineEngine and adjust routine engine API accordingly.

New Features:

  • Add a libsql-gated test helper module providing deterministic synchronisation for routine execution and persistence in end-to-end trace tests.

Enhancements:

  • Expose RoutineEngine's running count as a clonable atomic counter for improved test synchronisation flexibility.
  • Scope the new routine synchronisation helpers to only the e2e_traces test binary to avoid dead code warnings.

@coderabbitai coderabbitai Bot requested a review from leynos April 8, 2026 12:04
@coderabbitai
Copy link
Copy Markdown
Contributor Author

coderabbitai Bot commented Apr 8, 2026

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f8014373-948f-40d3-8f54-c1c9982d051b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the scope: agent Agent core (agent loop, router, scheduler) label Apr 8, 2026
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 8, 2026

Reviewer's Guide

Refactors test-only synchronization helpers for RoutineEngine by moving them into a dedicated, feature-gated module, adjusts RoutineEngine’s test API to expose the underlying atomic counter, and wires the new helpers into the e2e traces test suite while keeping production code and non-libsql tests unaffected.

Sequence diagram for e2e trace test synchronisation using RoutineEngine.running_count

sequenceDiagram
    actor Tester
    participant E2ETracesTest as E2ETracesTest
    participant RoutineEngine as RoutineEngine
    participant RoutineSync as RoutineSyncModule

    Tester->>E2ETracesTest: run_test_case()
    E2ETracesTest->>RoutineEngine: start_routine()
    RoutineEngine-->>RoutineEngine: increment running_count AtomicUsize

    E2ETracesTest->>RoutineSync: wait_until_idle(engine)
    RoutineSync->>RoutineEngine: running_count()
    RoutineEngine-->>RoutineSync: Arc<AtomicUsize>

    loop poll until zero
        RoutineSync-->>RoutineSync: read atomic value
        alt running_count > 0
            RoutineSync-->>RoutineSync: sleep/backoff
        else running_count == 0
            RoutineSync-->>E2ETracesTest: routines_idle()
        end
    end

    E2ETracesTest-->>Tester: assertions_passed
Loading

Class diagram for RoutineEngine test-only API and sync helpers

classDiagram
    class RoutineEngine {
        +running_count() Arc~AtomicUsize~
    }

    class AtomicUsize {
        +load(ordering)
        +store(value, ordering)
        +fetch_add(value, ordering)
        +fetch_sub(value, ordering)
    }

    class RoutineSyncModule {
        <<module>>
        +wait_for_running_count(engine, expected_count)
        +wait_until_idle(engine)
    }

    class E2ETracesTestsModule {
        <<module>>
        +routine_cooldown_tests()
        +routine_cron_tests()
        +routine_event_tests()
        +routine_system_event_tests()
    }

    RoutineEngine --> AtomicUsize : uses
    RoutineSyncModule --> RoutineEngine : test_synchronisation
    E2ETracesTestsModule --> RoutineSyncModule : uses
    E2ETracesTestsModule --> RoutineEngine : drives_routines
Loading

File-Level Changes

Change Details Files
Expose the RoutineEngine running counter for tests as a cloneable atomic and gate it behind the libsql feature.
  • Change running_count accessor to return an Arc instead of a raw usize snapshot.
  • Add a cfg(feature = "libsql") guard around the RoutineEngine impl that exposes running_count for tests.
  • Update documentation on running_count to clarify it returns a clone of the atomic counter intended only for test synchronization.
src/agent/routine_engine.rs
Move deterministic routine-engine sync helpers into a dedicated, feature-gated test support module and wire it into the e2e traces test binary.
  • Remove wait_for_idle and wait_for_persisted_run helpers from the general test support routines module to avoid dead_code in unrelated tests.
  • Introduce tests/support/routine_sync.rs with libsql-gated implementations of wait_for_idle and wait_for_persisted_run that use the new atomic-based running_count API and a bounded retry mechanism.
  • Import the new routine_sync module from tests/e2e_traces.rs under cfg(feature = "libsql") so only the relevant test binary compiles these helpers.
tests/support/routines.rs
tests/support/routine_sync.rs
tests/e2e_traces.rs
Minor formatting and consistency cleanups in e2e trace tests.
  • Normalize trailing newlines and module declarations in routine_* e2e tests without altering behavior.
  • Ensure workspace_coverage module declaration formatting is consistent.
tests/e2e_traces/routine_cooldown.rs
tests/e2e_traces/routine_cron.rs
tests/e2e_traces/routine_event.rs
tests/e2e_traces/routine_system_event.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions Bot added size: M 50-199 changed lines risk: medium Business logic, config, or moderate-risk modules contributor: new First-time contributor labels Apr 8, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In wait_for_persisted_run, you now have two termination conditions (timeout and max_attempts), which can conflict with the timeout parameter’s intent; consider either deriving max_attempts from timeout/poll_interval or relying on a single condition so callers’ timeouts behave predictably.
  • Changing RoutineEngine::running_count to return Arc<AtomicUsize> and gating it behind cfg(feature = "libsql") alters its public surface even though it’s test-oriented; consider either returning &AtomicUsize (avoiding an extra clone) and/or keeping the method available behind a test-only cfg to reduce surprises for non-libsql consumers.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `wait_for_persisted_run`, you now have two termination conditions (timeout and `max_attempts`), which can conflict with the `timeout` parameter’s intent; consider either deriving `max_attempts` from `timeout`/`poll_interval` or relying on a single condition so callers’ timeouts behave predictably.
- Changing `RoutineEngine::running_count` to return `Arc<AtomicUsize>` and gating it behind `cfg(feature = "libsql")` alters its public surface even though it’s test-oriented; consider either returning `&AtomicUsize` (avoiding an extra clone) and/or keeping the method available behind a test-only cfg to reduce surprises for non-libsql consumers.

## Individual Comments

### Comment 1
<location path="tests/support/routine_sync.rs" line_range="65-50" />
<code_context>
-/// * `routine_id` - The ID of the routine to check for runs
-/// * `timeout` - Maximum duration to wait for persistence
-#[allow(dead_code)]
-pub async fn wait_for_persisted_run(db: &Arc<dyn Database>, routine_id: Uuid, timeout: Duration) {
-    let start = std::time::Instant::now();
-    let poll_interval = Duration::from_millis(10);
-
-    loop {
-        let runs = db
-            .list_routine_runs(routine_id, 10)
-            .await
-            .expect("list_routine_runs should not fail");
-
-        if !runs.is_empty() {
-            return;
-        }
-
-        if start.elapsed() >= timeout {
-            panic!(
-                "Timeout waiting for routine run to be persisted (routine_id: {}, elapsed: {:?})",
-                routine_id,
-                start.elapsed()
-            );
-        }
-
-        tokio::time::sleep(poll_interval).await;
-    }
-}
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid having both `timeout` and a hard-coded `max_attempts` to prevent surprising behaviour in long-running tests

Because `max_attempts` is tied to a fixed 5s window while `timeout` is configurable, callers using a longer `timeout` (e.g. 15s in slow CI) would still hit the `max_attempts` condition and panic after ~5s. To keep behavior aligned with the requested `timeout`, either derive `max_attempts` from `timeout` and `poll_interval` (e.g. `timeout / poll_interval`) or remove `max_attempts` and rely solely on `timeout`.

Suggested implementation:

```rust
    // Derive max_attempts from the configured timeout so the loop's upper bound
    // is consistent with the requested maximum wait duration.
    let max_attempts = (timeout.as_millis() / poll_interval.as_millis())
        .max(1) as u32;

```

Depending on the exact current implementation, you may also need to:

1. Ensure `timeout` and `poll_interval` are both `Duration` values already in scope at the point where `max_attempts` is defined.
2. If `max_attempts` is typed as something other than `u32` (e.g. `usize`), adjust the cast to match:
   - For `usize`: `as usize` instead of `as u32`.
3. If the doc comment or tests mention a fixed 5s window / 500 attempts, update them to reflect that the limit is now derived from `timeout` (e.g. "waits up to `timeout` for persistence").
4. If there are multiple helpers in `tests/support/routine_sync.rs` that define a hard-coded `max_attempts` (for idle or persistence helpers), apply the same pattern to each one so all of them respect the configured `timeout`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

);
}

tokio::time::sleep(poll_interval).await;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Avoid having both timeout and a hard-coded max_attempts to prevent surprising behaviour in long-running tests

Because max_attempts is tied to a fixed 5s window while timeout is configurable, callers using a longer timeout (e.g. 15s in slow CI) would still hit the max_attempts condition and panic after ~5s. To keep behavior aligned with the requested timeout, either derive max_attempts from timeout and poll_interval (e.g. timeout / poll_interval) or remove max_attempts and rely solely on timeout.

Suggested implementation:

    // Derive max_attempts from the configured timeout so the loop's upper bound
    // is consistent with the requested maximum wait duration.
    let max_attempts = (timeout.as_millis() / poll_interval.as_millis())
        .max(1) as u32;

Depending on the exact current implementation, you may also need to:

  1. Ensure timeout and poll_interval are both Duration values already in scope at the point where max_attempts is defined.
  2. If max_attempts is typed as something other than u32 (e.g. usize), adjust the cast to match:
    • For usize: as usize instead of as u32.
  3. If the doc comment or tests mention a fixed 5s window / 500 attempts, update them to reflect that the limit is now derived from timeout (e.g. "waits up to timeout for persistence").
  4. If there are multiple helpers in tests/support/routine_sync.rs that define a hard-coded max_attempts (for idle or persistence helpers), apply the same pattern to each one so all of them respect the configured timeout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: new First-time contributor risk: medium Business logic, config, or moderate-risk modules scope: agent Agent core (agent loop, router, scheduler) size: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant