-
Notifications
You must be signed in to change notification settings - Fork 37
Fix early disposing inside delay #662
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
f3bfba3
5b98a3c
f62afa4
9000d5e
51d39eb
906250a
6484db5
31ec720
82927fe
bc99305
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 |
|---|---|---|
|
|
@@ -52,10 +52,9 @@ namespace rpp::details | |
| template<rpp::constraint::observer TObserver, constraint::decayed_type PackedContainer> | ||
| struct concat_source_observer_strategy | ||
| { | ||
| using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; | ||
| using preferred_disposable_strategy = rpp::details::observers::locally_disposable_strategy; | ||
|
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. 💡 Codebase verification Inconsistent The switch to 🔗 Analysis chainApprove change to The change from To ensure this change doesn't introduce any unintended side effects, please verify:
You may want to run the following command to check for any other occurrences of 🏁 Scripts executedThe following scripts were executed for the analysis: Script: rg "none_disposable_strategy" --type cpp
Length of output: 6313 |
||
|
|
||
| std::shared_ptr<concat_state_t<TObserver, PackedContainer>> state{}; | ||
| mutable bool locally_disposed{}; | ||
|
|
||
| template<typename T> | ||
| void on_next(T&& v) const | ||
|
|
@@ -65,17 +64,15 @@ namespace rpp::details | |
|
|
||
| void on_error(const std::exception_ptr& err) const | ||
| { | ||
| locally_disposed = true; | ||
| state->observer.on_error(err); | ||
| } | ||
|
|
||
| void set_upstream(const disposable_wrapper& d) { state->disposable.add(d); } | ||
|
|
||
| bool is_disposed() const { return locally_disposed || state->disposable.is_disposed(); } | ||
| bool is_disposed() const { return state->disposable.is_disposed(); } | ||
|
|
||
| void on_completed() const | ||
| { | ||
| locally_disposed = true; | ||
| state->disposable.clear(); | ||
|
|
||
| if (state->is_inside_drain.exchange(false, std::memory_order::seq_cst)) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| #include <rpp/subjects/publish_subject.hpp> | ||
|
|
||
| #include "disposable_observable.hpp" | ||
| #include "rpp_trompeloil.hpp" | ||
|
|
||
| namespace | ||
| { | ||
|
|
@@ -232,9 +233,32 @@ TEST_CASE("delay delays observable's emissions") | |
| } | ||
| } | ||
|
|
||
| TEST_CASE("delay is not disposing early") | ||
| { | ||
| mock_observer<int> mock{}; | ||
| trompeloeil::sequence s{}; | ||
|
|
||
| rpp::schedulers::test_scheduler scheduler{}; | ||
|
|
||
| std::optional<rpp::composite_disposable_wrapper> d{}; | ||
| rpp::source::create<int>([&d](auto&& obs) { | ||
| d = rpp::composite_disposable_wrapper::make(); | ||
| obs.set_upstream(d.value()); | ||
| obs.on_completed(); | ||
| CHECK(obs.is_disposed()); | ||
| }) | ||
| | rpp::ops::delay(std::chrono::seconds{1}, scheduler) | ||
| | rpp::ops::subscribe(mock); | ||
|
|
||
| CHECK(!d->is_disposed()); | ||
| REQUIRE_CALL(*mock, on_completed()).LR_WITH(!d->is_disposed()).IN_SEQUENCE(s); | ||
| scheduler.time_advance(std::chrono::seconds{1}); | ||
| CHECK(d->is_disposed()); | ||
| } | ||
|
|
||
| TEST_CASE("delay satisfies disposable contracts") | ||
| { | ||
| test_operator_with_disposable<int>(rpp::ops::delay(std::chrono::seconds{0}, manual_scheduler{})); | ||
| test_operator_with_disposable<int>(rpp::ops::delay(std::chrono::seconds{0}, rpp::schedulers::immediate{})); | ||
|
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. 💡 Codebase verification Consistent Scheduler Usage Recommended in Delay Tests The recent change replaces However, additional instances of
This will standardize the scheduler usage across all delay operator tests, ensuring consistent behavior and improved test reliability. 🔗 Analysis chainGood update to use immediate scheduler in disposable contract test. The change from This change enhances the test coverage by verifying the disposable contracts under a different scheduling strategy. It's a good practice to test operators with various scheduler types to ensure consistent behavior across different execution contexts. To ensure consistency across the test suite, let's verify if similar changes are needed in other test cases: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check for other uses of manual_scheduler in delay tests
# Search for other occurrences of manual_scheduler in test_delay.cpp
echo "Occurrences of manual_scheduler in test_delay.cpp:"
rg "manual_scheduler" src/tests/rpp/test_delay.cpp
# Search for other delay operator tests that might benefit from using immediate scheduler
echo "\nOther delay operator tests:"
rg "ops::delay" src/tests/rpp/test_delay.cpp
Length of output: 1243 |
||
| } | ||
|
|
||
| TEST_CASE("observe_on forward error immediately") | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -216,18 +216,21 @@ TEST_CASE_TEMPLATE("merge handles race condition", TestType, rpp::memory_model:: | |||||||||||||||||||||||||||||||||||||||
| extracted_obs.emplace(std::forward<decltype(obs)>(obs).as_dynamic()); | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| auto test = [&](auto source) { | ||||||||||||||||||||||||||||||||||||||||
| SUBCASE("subscribe on it") | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| SUBCASE("on_error can't interleave with on_next") | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| std::mutex m{}; | ||||||||||||||||||||||||||||||||||||||||
| source | ||||||||||||||||||||||||||||||||||||||||
| | rpp::ops::as_blocking() | ||||||||||||||||||||||||||||||||||||||||
| | rpp::ops::subscribe([&](auto&&) { | ||||||||||||||||||||||||||||||||||||||||
| REQUIRE(extracted_obs.has_value()); | ||||||||||||||||||||||||||||||||||||||||
| CHECK(!on_error_called); | ||||||||||||||||||||||||||||||||||||||||
| std::thread{[extracted_obs] | ||||||||||||||||||||||||||||||||||||||||
| std::thread{[extracted_obs,&m] | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| std::lock_guard lock{m}; | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+225
to
+233
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. Potential data race when accessing 'extracted_obs' The variable Consider synchronizing all accesses to Undefined behavior due to capturing local mutex by reference in detached thread The mutex To fix this issue, consider extending the lifetime of the mutex by moving it outside the lambda or by capturing it by value using a Apply this diff to fix the issue: - std::mutex m{};
+ auto m = std::make_shared<std::mutex>();
source
| rpp::ops::as_blocking()
| rpp::ops::subscribe([&](auto&&) {
REQUIRE(extracted_obs.has_value());
CHECK(!on_error_called);
- std::thread{[extracted_obs,&m]
+ std::thread{[extracted_obs, m]
{
- std::lock_guard lock{m};
+ std::lock_guard lock{*m};
extracted_obs->on_error(std::exception_ptr{});
}}.detach();
std::this_thread::sleep_for(std::chrono::seconds{1});
CHECK(!on_error_called); },
[&](auto) { on_error_called = true; });📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
| extracted_obs->on_error(std::exception_ptr{}); | ||||||||||||||||||||||||||||||||||||||||
| }}.detach(); | ||||||||||||||||||||||||||||||||||||||||
| std::this_thread::sleep_for(std::chrono::seconds{1}); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
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.
💡 Codebase verification
Issues Found with Removal of 'locally_disposed'
The
locally_disposedvariable is still referenced insrc/rpp/rpp/operators/retry_when.hpp:mutable bool locally_disposed{};locally_disposed = true;is_disposed()method still checkslocally_disposed || state->disposable.is_disposed();These remaining references indicate that the removal of
locally_disposedis incomplete and may affect the disposal logic as originally intended.🔗 Analysis chain
Verify the impact of removing 'locally_disposed' on disposal logic
The
is_disposed()method now solely checksstate->disposable.is_disposed()after the removal oflocally_disposed. Ensure that this change does not affect the correctness of disposal checks, especially in scenarios where local disposal status was significant for stopping retries.To confirm that the removal of
locally_disposeddoes not introduce issues, run the following script to check for any remaining references and to assess disposal behavior:🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 34493