Skip to content

refactor(dash-spv): replace run token and running flag with watch#772

Merged
xdustinface merged 1 commit into
v0.42-devfrom
refactor/spv-watch-lifecycle
May 18, 2026
Merged

refactor(dash-spv): replace run token and running flag with watch#772
xdustinface merged 1 commit into
v0.42-devfrom
refactor/spv-watch-lifecycle

Conversation

@xdustinface
Copy link
Copy Markdown
Collaborator

@xdustinface xdustinface commented May 17, 2026

DashSpvClient::run() took an external CancellationToken and the client separately tracked running: Arc<RwLock<bool>>, two mechanisms for one concept. Shutdown also relied on the running flag being observed only on the next 100ms sync_coordinator tick.

Both are now a single Arc<watch::Sender<bool>>: true while running, false once a stop is requested. run() drops its parameter, subscribes before the internal start(), and breaks its loop immediately when the value flips, so stop() is an event rather than a poll. start()/stop() use send_replace, so they are correct with zero receivers (the normal state during run -> stop -> run and when stop() is called without run()). is_running() keeps its signature, reading the watch via borrow().

stop() now flips the state before tearing down the sync coordinator, so a concurrent run() loop wakes and exits before it can lock the coordinator again. This removes the window where a tick could race the shutdown.

Summary by CodeRabbit

  • Breaking Changes

    • Client start no longer accepts an external shutdown token.
    • Running-state check is now synchronous (no await).
  • Changes

    • Shutdown is driven by the client's stop() flow; start/stop can be invoked repeatedly and ctrl‑C shutdown flow updated.
    • Examples and tests updated to use the new lifecycle.
  • Chores

    • Removed tokio-util dependency.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1d0cf2ce-36e0-42bb-8428-6239f17802b3

📥 Commits

Reviewing files that changed from the base of the PR and between acb7e9b and 6d2cdff.

📒 Files selected for processing (16)
  • dash-spv-ffi/Cargo.toml
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/tests/test_client.rs
  • dash-spv/examples/filter_sync.rs
  • dash-spv/examples/simple_sync.rs
  • dash-spv/examples/spv_with_wallet.rs
  • dash-spv/src/client/core.rs
  • dash-spv/src/client/lifecycle.rs
  • dash-spv/src/client/mod.rs
  • dash-spv/src/client/sync_coordinator.rs
  • dash-spv/src/lib.rs
  • dash-spv/src/main.rs
  • dash-spv/tests/dashd_masternode/setup.rs
  • dash-spv/tests/dashd_sync/setup.rs
  • dash-spv/tests/peer_test.rs
  • dash-spv/tests/wallet_integration_test.rs
💤 Files with no reviewable changes (2)
  • dash-spv-ffi/Cargo.toml
  • dash-spv/src/client/mod.rs
✅ Files skipped from review due to trivial changes (2)
  • dash-spv-ffi/tests/test_client.rs
  • dash-spv/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (10)
  • dash-spv/examples/simple_sync.rs
  • dash-spv/src/main.rs
  • dash-spv/tests/peer_test.rs
  • dash-spv/examples/spv_with_wallet.rs
  • dash-spv/src/client/lifecycle.rs
  • dash-spv/src/client/core.rs
  • dash-spv-ffi/src/client.rs
  • dash-spv/tests/dashd_sync/setup.rs
  • dash-spv/tests/dashd_masternode/setup.rs
  • dash-spv/src/client/sync_coordinator.rs

📝 Walkthrough

Walkthrough

The PR migrates the SPV client shutdown from external tokio_util::CancellationToken control to an internal tokio::sync::watch running-state. The running flag moves from async RwLock<bool> to synchronous watch::Sender<bool>, and all call sites, FFI, examples, CLI, and tests are updated to use client.run().await / client.stop().await.

Changes

SPV Client Shutdown Mechanism Refactor

Layer / File(s) Summary
Running state from RwLock to watch::Sender
dash-spv/src/client/core.rs
DashSpvClient::running changes from Arc<RwLock<bool>> to Arc<watch::Sender<bool>>; is_running() becomes a synchronous fn using borrow().
Lifecycle state management with watch-based running flag
dash-spv/src/client/lifecycle.rs
start() marks running only after startup completes via running.send_replace(true); stop() flips running to false immediately via running.send_replace(false) before teardown.
Sync coordinator subscribes to internal running state
dash-spv/src/client/sync_coordinator.rs
DashSpvClient::run() removes the CancellationToken parameter, subscribes to self.running before startup, consumes the initial edge, and waits on ticks or stop_rx.changed() while gating on is_running().
Module declarations and crate-level documentation
dash-spv/src/client/mod.rs, dash-spv/src/lib.rs
Adds pub mod event_handler; and re-exports EventHandler; updates Quick Start docs to call client.run().await without a token.
FFI client binding and dependency updates
dash-spv-ffi/Cargo.toml, dash-spv-ffi/src/client.rs, dash-spv-ffi/tests/test_client.rs
Removes tokio-util dependency and shutdown_token from FFIDashSpvClient; refactors dash_spv_ffi_client_run, dash_spv_ffi_client_stop, and dash_spv_ffi_client_destroy to call client.inner.stop() and wait for the run task instead of cancelling a token.
Examples and CLI shutdown mechanism updates
dash-spv/examples/*, dash-spv/src/main.rs
Examples drop CancellationToken imports and call client.run().await directly; CLI replaces token/select-based ctrl-c handling with a spawned signal task that calls stop() on a cloned client.
Test setup and integration verification
dash-spv/tests/*
Test helpers remove CancellationToken fields/imports; run loops are spawned without tokens and tests call client.stop().await before joining; test_spv_client_run_stop verifies two run/stop cycles on the same client with bounded timeouts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Token no more, the rabbit hops,
Watch-based state now tops the pops,
Internal grace, no cancels loud,
Shutdown flows like fluffy cloud,
Stop and start dance swift and proud!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main refactoring: replacing the external CancellationToken mechanism and RwLock-based running flag with a Tokio watch::Sender for lifecycle management.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/spv-watch-lifecycle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
dash-spv-ffi/tests/test_client.rs (1)

51-57: ⚡ Quick win

Assert lifecycle return codes to avoid silent test passes.

Line 54 through Line 57 currently ignore run/stop results, so this test can pass even if lifecycle behavior regresses.

Proposed test tightening
-            let _result = dash_spv_ffi_client_run(client);
-            let _result = dash_spv_ffi_client_stop(client);
-            let _result = dash_spv_ffi_client_run(client);
-            let _result = dash_spv_ffi_client_stop(client);
+            assert!(!client.is_null());
+            assert_eq!(dash_spv_ffi_client_run(client), FFIErrorCode::Success as i32);
+            assert_eq!(dash_spv_ffi_client_stop(client), FFIErrorCode::Success as i32);
+            assert_eq!(dash_spv_ffi_client_run(client), FFIErrorCode::Success as i32);
+            assert_eq!(dash_spv_ffi_client_stop(client), FFIErrorCode::Success as i32);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dash-spv-ffi/tests/test_client.rs` around lines 51 - 57, The test currently
ignores results from dash_spv_ffi_client_run and dash_spv_ffi_client_stop so
regressions can pass silently; update the test to assert each call's return
indicates success (i.e., check the run and stop return values for
success/non-error) for both the first run/stop pair and the second run/stop pair
using the existing dash_spv_ffi_client_run and dash_spv_ffi_client_stop calls,
and fail the test if any call returns an error code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dash-spv/tests/peer_test.rs`:
- Around line 65-66: The tests currently ignore the Results from
client.stop().await and handle.await which can hide shutdown/join failures;
update each occurrence (calls to client.stop().await and joins like
handle.await) to assert or propagate failures instead of discarding—e.g. replace
the let _ = ... forms with .await.expect("stop failed") or
assert!(handle.await.is_ok(), "task join failed") (or use ? in an async test) so
any error from stop() or the spawned task join fails the test and surfaces the
underlying error.

---

Nitpick comments:
In `@dash-spv-ffi/tests/test_client.rs`:
- Around line 51-57: The test currently ignores results from
dash_spv_ffi_client_run and dash_spv_ffi_client_stop so regressions can pass
silently; update the test to assert each call's return indicates success (i.e.,
check the run and stop return values for success/non-error) for both the first
run/stop pair and the second run/stop pair using the existing
dash_spv_ffi_client_run and dash_spv_ffi_client_stop calls, and fail the test if
any call returns an error code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6f292dd4-4b59-4747-b31e-347be1948073

📥 Commits

Reviewing files that changed from the base of the PR and between cfb01fa and acb7e9b.

📒 Files selected for processing (16)
  • dash-spv-ffi/Cargo.toml
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/tests/test_client.rs
  • dash-spv/examples/filter_sync.rs
  • dash-spv/examples/simple_sync.rs
  • dash-spv/examples/spv_with_wallet.rs
  • dash-spv/src/client/core.rs
  • dash-spv/src/client/lifecycle.rs
  • dash-spv/src/client/mod.rs
  • dash-spv/src/client/sync_coordinator.rs
  • dash-spv/src/lib.rs
  • dash-spv/src/main.rs
  • dash-spv/tests/dashd_masternode/setup.rs
  • dash-spv/tests/dashd_sync/setup.rs
  • dash-spv/tests/peer_test.rs
  • dash-spv/tests/wallet_integration_test.rs
💤 Files with no reviewable changes (2)
  • dash-spv-ffi/Cargo.toml
  • dash-spv/src/client/mod.rs

Comment thread dash-spv/tests/peer_test.rs Outdated
`DashSpvClient::run()` took an external `CancellationToken` and the client separately tracked `running: Arc<RwLock<bool>>`, two mechanisms for one concept. Shutdown also relied on the `running` flag being observed only on the next 100ms `sync_coordinator` tick.

Both are now a single `Arc<watch::Sender<bool>>`: `true` while running, `false` once a stop is requested. `run()` drops its parameter, subscribes before the internal `start()`, and breaks its loop immediately when the value flips, so `stop()` is an event rather than a poll. `start()`/`stop()` use `send_replace`, so they are correct with zero receivers (the normal state during `run -> stop -> run` and when `stop()` is called without `run()`). `is_running()` keeps its signature, reading the watch via `borrow()`.

`stop()` now flips the state before tearing down the sync coordinator, so a concurrent `run()` loop wakes and exits before it can lock the coordinator again. This removes the window where a tick could race the shutdown.
@xdustinface xdustinface force-pushed the refactor/spv-watch-lifecycle branch from acb7e9b to 6d2cdff Compare May 17, 2026 10:55
@codecov
Copy link
Copy Markdown

codecov Bot commented May 17, 2026

Codecov Report

❌ Patch coverage is 74.07407% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.68%. Comparing base (cfb01fa) to head (6d2cdff).
⚠️ Report is 2 commits behind head on v0.42-dev.

Files with missing lines Patch % Lines
dash-spv/src/main.rs 0.00% 6 Missing ⚠️
dash-spv/src/client/lifecycle.rs 85.71% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           v0.42-dev     #772      +/-   ##
=============================================
+ Coverage      72.67%   72.68%   +0.01%     
=============================================
  Files            320      320              
  Lines          70333    70319      -14     
=============================================
+ Hits           51112    51113       +1     
+ Misses         19221    19206      -15     
Flag Coverage Δ
core 76.30% <ø> (ø)
ffi 49.13% <100.00%> (-0.02%) ⬇️
rpc 20.00% <ø> (ø)
spv 89.93% <66.66%> (+0.07%) ⬆️
wallet 71.27% <ø> (ø)
Files with missing lines Coverage Δ
dash-spv-ffi/src/client.rs 57.73% <100.00%> (-0.19%) ⬇️
dash-spv/src/client/core.rs 46.66% <100.00%> (ø)
dash-spv/src/client/mod.rs 100.00% <ø> (ø)
dash-spv/src/client/sync_coordinator.rs 78.49% <100.00%> (+2.15%) ⬆️
dash-spv/src/client/lifecycle.rs 77.89% <85.71%> (+0.05%) ⬆️
dash-spv/src/main.rs 0.00% <0.00%> (ø)

... and 5 files with indirect coverage changes

@xdustinface
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions github-actions Bot added the ready-for-review CodeRabbit has approved this PR label May 18, 2026
@xdustinface xdustinface merged commit 26384af into v0.42-dev May 18, 2026
40 checks passed
@xdustinface xdustinface deleted the refactor/spv-watch-lifecycle branch May 18, 2026 23:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review CodeRabbit has approved this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants