-
Notifications
You must be signed in to change notification settings - Fork 37
Fix deadlocks in operators related to is_disposed #661
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
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 |
|---|---|---|
|
|
@@ -120,6 +120,26 @@ TEST_CASE("debounce emit only items where timeout reached") | |
| } | ||
| } | ||
|
|
||
|
|
||
| TEST_CASE("debounce is not deadlocking is_disposed") | ||
| { | ||
| std::optional<rpp::dynamic_observer<int>> observer{}; | ||
|
|
||
| rpp::schedulers::test_scheduler scheduler{}; | ||
|
|
||
| rpp::source::create<int>([&observer](auto&& obs) { | ||
| observer = std::forward<decltype(obs)>(obs).as_dynamic(); | ||
| observer->on_next(1); | ||
| }) | ||
| | rpp::operators::debounce(std::chrono::seconds{1}, scheduler) | ||
| | rpp::ops::subscribe([&observer](int) { | ||
| CHECK(observer); | ||
| CHECK(!observer->is_disposed()); | ||
| }); | ||
| scheduler.time_advance(std::chrono::seconds{1}); | ||
| } | ||
|
Comment on lines
+124
to
+140
Contributor
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. 🛠️ Refactor suggestion Good addition of a test case for deadlock prevention, but consider enhancing it. The new test case "debounce is not deadlocking is_disposed" is a valuable addition to ensure that the debounce operator doesn't cause deadlocks when checking the disposed state. However, consider the following enhancements to make the test more robust and informative:
Here's a suggested improvement: TEST_CASE("debounce is not deadlocking is_disposed")
{
// This test ensures that checking is_disposed() within the debounce operator
// does not cause a deadlock, even when the observer is disposed.
std::optional<rpp::dynamic_observer<int>> observer{};
rpp::schedulers::test_scheduler scheduler{};
bool lambda_called = false;
auto subscription = rpp::source::create<int>([&observer](auto&& obs) {
observer = std::forward<decltype(obs)>(obs).as_dynamic();
observer->on_next(1);
observer->on_next(2); // Add another emission to test multiple debounces
})
| rpp::operators::debounce(std::chrono::seconds{1}, scheduler)
| rpp::ops::subscribe([&observer, &lambda_called](int value) {
CHECK(observer);
CHECK(!observer->is_disposed());
lambda_called = true;
CHECK(value == 2); // Ensure we got the last emitted value
});
scheduler.time_advance(std::chrono::milliseconds{500});
CHECK_FALSE(lambda_called); // Ensure the lambda hasn't been called yet
scheduler.time_advance(std::chrono::seconds{1});
CHECK(lambda_called); // Ensure the lambda was called after debounce period
subscription.dispose(); // Explicitly dispose of the subscription
CHECK(observer->is_disposed()); // Verify that the observer is now disposed
}This enhanced version provides more comprehensive testing of the debounce behavior and explicitly checks for the non-deadlocking property when disposing of the observer. |
||
|
|
||
|
|
||
| TEST_CASE("debounce forwards error") | ||
| { | ||
| auto mock = mock_observer_strategy<int>{}; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -258,6 +258,20 @@ TEST_CASE("merge dispose inner_disposable immediately") | |
| | rpp::ops::subscribe([](int) {}); | ||
| } | ||
|
|
||
| TEST_CASE("merge is not deadlocking is_disposed") | ||
| { | ||
| std::optional<rpp::dynamic_observer<int>> observer{}; | ||
| rpp::source::create<int>([&observer](auto&& obs) { | ||
| observer = std::forward<decltype(obs)>(obs).as_dynamic(); | ||
| observer->on_next(1); | ||
| }) | ||
| | rpp::ops::merge_with(rpp::source::never<int>()) | ||
| | rpp::ops::subscribe([&observer](int) { | ||
| CHECK(observer); | ||
| CHECK(!observer->is_disposed()); | ||
| }); | ||
| } | ||
|
Comment on lines
+261
to
+273
Contributor
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. 🛠️ Refactor suggestion Enhance test case clarity and robustness The new test case effectively verifies that the
Here's a suggested refactoring of the test case: TEST_CASE("merge_operator_does_not_deadlock_when_checking_observer_disposal")
{
rpp::dynamic_observer<int> observer;
bool value_emitted = false;
rpp::source::create<int>([&](auto&& obs) {
observer = std::forward<decltype(obs)>(obs).as_dynamic();
REQUIRE(observer);
REQUIRE(!observer.is_disposed());
observer.on_next(1);
value_emitted = true;
})
| rpp::ops::merge_with(rpp::source::never<int>())
| rpp::ops::subscribe([&](int) {
REQUIRE(value_emitted);
REQUIRE(!observer.is_disposed());
});
REQUIRE(value_emitted);
}This refactored version:
|
||
|
|
||
| TEST_CASE("merge doesn't produce extra copies") | ||
| { | ||
| SUBCASE("send value by copy") | ||
|
|
||
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.
Ensure safe usage when locking disposable wrapper
In the
liftmethod, after obtainingptrviad.lock(), there is a potential forptrto be null. To prevent a null pointer dereference, ensure thatptris valid before using it.Consider adding a check for
ptrbefore dereferencing:Alternatively, if you are confident that
d.lock()will always return a valid pointer in this context, consider adding an assertion to document this assumption: