Repository navigation
test: cover CoinJoin client restart handling #7420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,10 @@ | |
|
|
||
| #include <boost/test/unit_test.hpp> | ||
|
|
||
| #include <chrono> | ||
| #include <future> | ||
| #include <thread> | ||
|
|
||
| namespace wallet { | ||
| BOOST_FIXTURE_TEST_SUITE(coinjoin_tests, BasicTestingSetup) | ||
|
|
||
|
|
@@ -394,6 +398,48 @@ BOOST_FIXTURE_TEST_CASE(coinjoin_pending_observation_reload_tests, CTransactionB | |
| SetMockTime(0); | ||
| } | ||
|
|
||
| BOOST_FIXTURE_TEST_CASE(coinjoin_wallet_manager_remove_during_client_access_tests, CTransactionBuilderTestSetup) | ||
| { | ||
| std::promise<void> client_entered; | ||
| auto client_entered_future{client_entered.get_future()}; | ||
| std::promise<void> release_client; | ||
| auto release_client_future{release_client.get_future()}; | ||
| std::promise<void> remover_started; | ||
| auto remover_started_future{remover_started.get_future()}; | ||
| bool client_found{false}; | ||
|
|
||
| std::thread client_thread([&] { | ||
| client_found = m_node.cj_walletman->doForClient(wallet->GetName(), [&](CCoinJoinClientManager&) { | ||
| client_entered.set_value(); | ||
| release_client_future.wait(); | ||
| }); | ||
| }); | ||
|
|
||
| client_entered_future.wait(); | ||
| auto remove_future{std::async(std::launch::async, [&] { | ||
| // Signal after this worker is scheduled, immediately before it | ||
| // contends for the manager-map lock. | ||
| remover_started.set_value(); | ||
| m_node.cj_walletman->removeWallet(wallet->GetName()); | ||
| })}; | ||
| remover_started_future.wait(); | ||
|
Comment on lines
+419
to
+425
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: Synchronize the remover before timing its completion The remover thread fulfills source:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree the promise is before the mutex acquisition in the strict sense, but I am leaving this as a focused black-box lock-order test rather than adding a production-only contention hook to CJWalletManager. The worker is launched with std::launch::async, signals from the worker immediately before removeWallet(), and the callback remains blocked until the timeout assertion completes; after release, the test waits for removal and verifies the client callback completed before restoring the manager. The new commit also folds the corrective history and makes the functional-session interruption deterministic. |
||
|
|
||
| // doForClient holds the manager-map lock while the callback uses the | ||
| // client, so removal must wait until the callback has released it. The | ||
| // remover-started barrier ensures this checks a running contender rather | ||
| // than an async task that has not been scheduled yet. | ||
| BOOST_CHECK_EQUAL(remove_future.wait_for(std::chrono::milliseconds{100}), std::future_status::timeout); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The fresh unit-test barrier added after the earlier RPC-race review holds a known AGENTS.md reference: AGENTS.md:L136-L137 Useful? React with 👍 / 👎.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in fbecdeb: the remover now signals from its worker immediately before calling removeWallet(), and the test waits for that signal before checking the timeout. This ensures the async worker has been scheduled and is entering the lock-contending operation rather than remaining unscheduled during the assertion window. |
||
|
|
||
| release_client.set_value(); | ||
| client_thread.join(); | ||
| BOOST_CHECK(client_found); | ||
| BOOST_CHECK_EQUAL(remove_future.wait_for(std::chrono::seconds{1}), std::future_status::ready); | ||
| remove_future.get(); | ||
|
|
||
| // Restore the fixture's manager for its teardown. | ||
| m_node.cj_walletman->addWallet(wallet); | ||
| } | ||
|
|
||
| BOOST_FIXTURE_TEST_CASE(coinjoin_pending_observation_unreadable_tests, CTransactionBuilderTestSetup) | ||
| { | ||
| // 0.100001 DASH, a valid CoinJoin denomination | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a server session is reset or replaced while
GetJsonInfo()is executing, this value is captured undercs_coinjoinbut thestatepaired with it is read only after that lock is released, so the RPC can return an old session's count alongsideIDLEor a replacement session'sQUEUEstate.isolate_joiner_in_active_session()treatscollaterals_count >= 2plus that separately sampled state as proof that the current queue accepted both clients; under this interleaving it can isolate the joiner with fewer participants and then time out. Snapshot the count and state together, ideally with the session identity.AGENTS.md reference: AGENTS.md:L136-L137
Useful? React with 👍 / 👎.