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

@xdustinface xdustinface commented May 17, 2026

Copy link
Copy Markdown
Collaborator

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

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

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.

@coderabbitai coderabbitai Bot left a comment

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.

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

codecov Bot commented May 17, 2026

Copy link
Copy Markdown

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

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor
✅ 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.

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