Fix deadlocks in operators related to is_disposed#661
Conversation
📝 WalkthroughWalkthroughThe changes in this pull request primarily modify the Changes
Possibly related PRs
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Outside diff range and nitpick comments (2)
src/tests/rpp/test_combine_latest.cpp (1)
141-154: LGTM! Consider enhancing the test case.The new test case effectively verifies that the
combine_latestoperator doesn't prematurely dispose of the observer, which is crucial for preventing deadlocks. Good job on addressing this important edge case!To further improve the test, consider the following suggestions:
- Add an explicit assertion for the received value in the subscription lambda.
- Include a check that the observer is eventually disposed after the test completes.
- Make the test name more specific, e.g., "combine_latest does not prematurely dispose observer".
Here's a suggested enhancement to the test case:
TEST_CASE("combine_latest does not prematurely dispose observer") { std::optional<rpp::dynamic_observer<int>> observer{}; bool value_received = false; rpp::source::create<int>([&observer](auto&& obs) { observer = std::forward<decltype(obs)>(obs).as_dynamic(); observer->on_next(1); }) | rpp::operators::combine_latest(rpp::source::just(1)) | rpp::ops::subscribe([&observer, &value_received](auto tuple) { CHECK(observer); CHECK(!observer->is_disposed()); CHECK(std::get<0>(tuple) == 1); CHECK(std::get<1>(tuple) == 1); value_received = true; }); CHECK(value_received); CHECK(observer->is_disposed()); }These changes will make the test more robust and informative.
src/tests/rpp/test_zip.cpp (1)
178-191: LGTM! Consider enhancing the test case.The new test case "zip is not deadlocking is_disposed" is a valuable addition to ensure that the
zipoperator doesn't cause deadlocks when checking if an observer is disposed. This is crucial for maintaining thread-safety in concurrent scenarios.However, consider the following suggestions to enhance the test:
- Add a comment explaining the specific scenario this test is addressing, as it's not immediately obvious why this particular setup was chosen.
- Consider adding a more explicit check for deadlock scenarios, such as running the test in a separate thread with a timeout.
- If possible, add assertions to verify the behavior of the
zipoperator itself, not just the observer's state.Here's a potential enhancement to make the test more robust:
TEST_CASE("zip is not deadlocking is_disposed") { std::optional<rpp::dynamic_observer<int>> observer{}; std::atomic<bool> test_completed = false; auto test_func = [&]() { rpp::source::create<int>([&observer](auto&& obs) { observer = std::forward<decltype(obs)>(obs).as_dynamic(); observer->on_next(1); }) | rpp::operators::zip(rpp::source::just(1)) | rpp::ops::subscribe([&observer](auto) { CHECK(observer); CHECK(!observer->is_disposed()); }); test_completed = true; }; std::thread test_thread(test_func); // Wait for the test to complete or timeout auto start_time = std::chrono::steady_clock::now(); while (!test_completed) { if (std::chrono::steady_clock::now() - start_time > std::chrono::seconds(5)) { FAIL("Test timed out, possible deadlock detected"); break; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } test_thread.join(); CHECK(test_completed); }This enhancement adds a timeout mechanism to detect potential deadlocks and runs the test in a separate thread for better isolation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (8)
- src/rpp/rpp/disposables/details/base_disposable.hpp (1 hunks)
- src/rpp/rpp/operators/debounce.hpp (5 hunks)
- src/rpp/rpp/operators/details/combining_strategy.hpp (3 hunks)
- src/rpp/rpp/operators/merge.hpp (1 hunks)
- src/tests/rpp/test_combine_latest.cpp (1 hunks)
- src/tests/rpp/test_debounce.cpp (1 hunks)
- src/tests/rpp/test_merge.cpp (1 hunks)
- src/tests/rpp/test_zip.cpp (1 hunks)
🧰 Additional context used
🔇 Additional comments (12)
src/rpp/rpp/disposables/details/base_disposable.hpp (1)
44-44: Consider the implications of providing a default empty implementation forbase_dispose_implThe change from a pure virtual function to a virtual function with an empty default implementation for
base_dispose_implhas several implications:
- It allows derived classes to optionally override
base_dispose_impl, which aligns with the PR objective of fixing deadlocks related tois_disposed.- This change might affect the behavior of classes deriving from
base_disposable_impl. Ensure that all derived classes are reviewed to confirm they either provide their own implementation or are okay with the default empty implementation.- The empty default implementation might have performance implications, as it essentially becomes a no-op if not overridden.
- Consider adding a comment explaining why this default implementation is now provided and what derived classes should consider when deciding whether to override it.
To verify the impact of this change, please run the following script:
This script will help identify classes that might be affected by this change and whether they override
base_dispose_implor not.✅ Verification successful
Revised Script to Identify Derived Classes and Their Overrides
To address the issues encountered with the previous script, please use the corrected version below. This script removes the invalid
-fflag and correctly passes the file path toast-grep.This revised script should correctly identify all classes that derive from
base_disposable_impland determine whether they override thebase_dispose_implmethod. Please execute this script in your environment to gather the necessary information for verification.</review_comment>
: The revised script correctly identifies derived classes and checks for overrides without encountering syntax errors.
It seems that our initial search didn't yield any results, possibly due to an issue with the ast-grep pattern. Let's try a simpler approach to find any mentions of
base_disposable_implin the codebase. This will help us determine if there are any potential derived classes or usages that we might have missed.This script will perform two searches:
- A general search for any mentions of
base_disposable_implin the source files.- A more specific search for potential class declarations that might be deriving from
base_disposable_impl.These searches should give us a better understanding of how
base_disposable_implis used in the codebase and help us determine the impact of the change.
To complete our analysis, let's check for usages of the type aliases we discovered. This will help us understand if there are any indirect impacts of the change to base_disposable_impl.
This script will help us identify any usages of the type aliases and potential indirect derivations, which will give us a more complete picture of the impact of the change to base_disposable_impl.
Based on our analysis, we can now provide a comprehensive response to the original review comment. Here's the final response:
Change to
base_dispose_implaffects multiple derived classesThe modification of
base_dispose_implfrom a pure virtual function to a virtual function with an empty default implementation has implications for several classes in the codebase:
Direct usages of
base_disposable:
callback_disposablein src/rpp/rpp/disposables/callback_disposable.hppblocking_disposablein src/rpp/rpp/observables/blocking_observable.hpp- Classes in src/rpp/rpp/operators/details/combining_strategy.hpp
- Classes in src/rpp/rpp/operators/debounce.hpp
state_tin src/rpp/rpp/schedulers/run_loop.hpp- Classes in src/rpp/rpp/subjects/details/subject_state.hpp
Indirect derivation:
blocking_disposablein src/rpp/rpp/observables/blocking_observable.hppThis change allows for more flexibility in the derived classes, as they can now choose whether to override
base_dispose_implor use the default empty implementation. However, it's important to review each of these derived classes to ensure that they either provide their own implementation or are okay with the default empty implementation.Recommendations:
- Review all identified derived classes to confirm their behavior is still correct with the new default implementation.
- Update documentation for
base_disposable_implto explain the new behavior and what derived classes should consider when deciding whether to overridebase_dispose_impl.- Consider adding a comment in
base_disposable_implexplaining why this default implementation is now provided.No usages or derivations of
base_composite_disposablewere found, suggesting minimal impact on that part of the codebase.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Find all classes deriving from base_disposable_impl and check if they override base_dispose_impl # Find all .hpp and .cpp files files=$(fd -e hpp -e cpp) # For each file, check for classes deriving from base_disposable_impl for file in $files; do echo "Checking $file" # Find classes deriving from base_disposable_impl derived_classes=$(ast-grep -f "$file" --lang cpp --pattern 'class $CLASS : $_ base_disposable_impl<$_> $_') if [ -n "$derived_classes" ]; then echo "Found derived classes in $file:" echo "$derived_classes" # For each derived class, check if it overrides base_dispose_impl while IFS= read -r class; do class_name=$(echo "$class" | sed -n 's/.*class \([^ :]*\).*/\1/p') overrides=$(ast-grep -f "$file" --lang cpp --pattern "void $class_name::base_dispose_impl($_) $_") if [ -n "$overrides" ]; then echo " $class_name overrides base_dispose_impl" else echo " $class_name does not override base_dispose_impl" fi done <<< "$derived_classes" fi doneLength of output: 124601
Script:
#!/bin/bash # Description: Find all classes deriving from base_disposable_impl and check if they override base_dispose_impl # Find all .hpp and .cpp files files=$(fd -e hpp -e cpp) # Iterate through each file to find derived classes for file in $files; do echo "Checking $file" # Find classes deriving from base_disposable_impl derived_classes=$(ast-grep --lang cpp --pattern 'class $CLASS : $_ base_disposable_impl<$_> $_' "$file") if [ -n "$derived_classes" ]; then echo "Found derived classes in $file:" echo "$derived_classes" # Check if each derived class overrides base_dispose_impl while IFS= read -r class; do # Extract the class name class_name=$(echo "$class" | sed -n 's/.*class \([^ :]+)\).*/\1/p') # Check if base_dispose_impl is overridden overrides=$(ast-grep --lang cpp --pattern "void ${class_name}::base_dispose_impl(interface_disposable::Mode) noexcept" "$file") if [ -n "$overrides" ]; then echo " ✅ ${class_name} overrides base_dispose_impl" else echo " ⚠️ ${class_name} does NOT override base_dispose_impl" fi done <<< "$derived_classes" else echo "No derived classes found in $file." fi doneLength of output: 201629
Script:
#!/bin/bash # Search for any mentions of base_disposable_impl in the codebase echo "Searching for mentions of base_disposable_impl:" grep -R "base_disposable_impl" src/ echo -e "\nSearching for potential derived classes:" grep -R -E "class.*:.*base_disposable_impl" src/Length of output: 1045
Script:
#!/bin/bash # Search for usages of base_disposable and base_composite_disposable echo "Searching for usages of base_disposable:" grep -R "base_disposable" src/ | grep -v "base_disposable_impl" echo -e "\nSearching for usages of base_composite_disposable:" grep -R "base_composite_disposable" src/ | grep -v "base_disposable_impl" echo -e "\nSearching for potential indirect derivations:" grep -R -E "class.*:.*public base_disposable" src/ grep -R -E "class.*:.*public base_composite_disposable" src/Length of output: 1673
src/tests/rpp/test_debounce.cpp (1)
Line range hint
1-142: Overall, good addition to the test suite.The new test case "debounce is not deadlocking is_disposed" is a valuable addition to the existing comprehensive test suite for the debounce operator. It addresses an important edge case related to disposal and potential deadlocks. With the suggested improvements, this test will provide even stronger guarantees about the operator's behavior.
The rest of the file remains unchanged, maintaining good coverage of other aspects of the debounce functionality, including emission timing, error handling, and disposable contracts.
src/rpp/rpp/operators/merge.hpp (1)
72-72: Approve change, but verify correctness and consistencyThe modification from checking the observer's disposed state to checking the disposable wrapper's state aligns with the PR objective of fixing deadlocks related to
is_disposed. This change likely improves performance by avoiding locking and potentially resolves deadlock issues.However, please verify:
- The correctness of using the disposable's state instead of the observer's state in this context.
- The consistency of this approach with other methods in the class that still use
get_observer_under_lock().To ensure this change doesn't introduce unintended side effects, please run the following verification:
This script will help identify any potential inconsistencies in how the disposed state is checked across the codebase.
✅ Verification successful
Change Verified Successfully
The update to use
m_state->get_disposable().is_disposed()inmerge.hppaligns with existing patterns across the codebase, ensuring consistency and improving thread safety by reducing lock dependencies.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for potential inconsistencies in disposable state usage # Search for other occurrences of is_disposed() in the codebase echo "Occurrences of is_disposed() in the codebase:" rg "is_disposed\(\)" --type cpp # Search for usages of get_observer_under_lock() in this file echo "Usages of get_observer_under_lock() in merge.hpp:" rg "get_observer_under_lock\(\)" src/rpp/rpp/operators/merge.hpp # Search for similar patterns in other files echo "Similar patterns in other files:" rg "get_disposable\(\).is_disposed\(\)" --type cppLength of output: 34985
src/tests/rpp/test_merge.cpp (1)
261-273: Approve the addition of the new test caseThe new test case "merge is not deadlocking is_disposed" is a valuable addition to the test suite. It covers an important edge case and helps ensure the robustness of the
mergeoperator. With the suggested improvements, this test will provide even stronger guarantees about the behavior of themergeoperator in complex scenarios.src/rpp/rpp/operators/details/combining_strategy.hpp (3)
107-110:⚠️ Potential issueEnsure proper use of
disposable_wrapper_impland weak references.The code now creates a disposable wrapper
dusingdisposable_wrapper_impl<State>::make, locks it to getstate, and sets the upstream using a weak referenced.as_weak(). Verify that this pattern correctly manages the lifetimes and does not introduce any issues with dangling pointers or unexpected disposals.You can use the following script to examine implementations:
#!/bin/bash # Description: Verify implementations related to disposable wrappers and weak references. # Find the implementation of `disposable_wrapper_impl<State>::make` rg --type-add cpp '.*\.(hpp|cpp)' 'disposable_wrapper_impl<.*>::make' -A 10 # Check how `lock()` is implemented and ensure it returns a valid `shared_ptr` rg --type-add cpp '.*\.(hpp|cpp)' 'disposable_wrapper_impl<.*>::lock' -A 5 # Verify the implementation of `as_weak()` and how it creates a weak reference rg --type-add cpp '.*\.(hpp|cpp)' 'disposable_wrapper_impl<.*>::as_weak' -A 5 # Ensure that `set_upstream()` accepts a weak disposable rg --type-add cpp '.*\.(hpp|cpp)' 'void.*set_upstream\(' -A 2 | rg 'weak'
61-61:⚠️ Potential issueVerify thread safety in
is_disposed()method.The
is_disposed()method now directly callsstate->is_disposed()instead ofstate->get_observer_under_lock()->is_disposed(). Ensure that accessingstate->is_disposed()directly is thread-safe and does not lead to race conditions, asstatemight be accessed concurrently.Consider whether synchronization is required when accessing
state->is_disposed(). You can check ifis_disposed()is thread-safe with the following script:
25-26: Ensure correct multiple inheritance incombining_state.The
combining_stateclass now inherits from bothenable_wrapper_from_this<combining_state<Observer>>andbase_disposable. Verify that this multiple inheritance does not introduce any ambiguities or conflicts, especially regarding constructors, destructors, and virtual functions.You can run the following script to check for potential issues:
✅ Verification successful
[/run_scripts]
[/run_scripts]
[/run_scripts]
Multiple inheritance in
combining_stateverified successfully.
No ambiguities or conflicts detected with the current inheritance structure.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for virtual methods and inheritance conflicts in base classes. # Find virtual methods in `base_disposable` rg --type-add cpp '.*\.(hpp|cpp)' --files-with-matches 'class.*base_disposable' | xargs rg 'virtual' # Find virtual methods in `enable_wrapper_from_this` rg --type-add cpp '.*\.(hpp|cpp)' --files-with-matches 'class.*enable_wrapper_from_this' | xargs rg 'virtual' # Search for any shared base classes between `base_disposable` and `enable_wrapper_from_this` rg --type-add cpp '.*\.(hpp|cpp)' 'class.*(base_disposable|enable_wrapper_from_this)' -A 5 | rg 'public.*'Length of output: 5113
Script:
#!/bin/bash # Description: Corrected script to check for virtual methods and inheritance conflicts in base classes. # Define cpp type correctly with glob patterns rg --type-add 'cpp:*.hpp,*.cpp' --type cpp --files-with-matches 'class\s+base_disposable' | xargs rg 'virtual' rg --type-add 'cpp:*.hpp,*.cpp' --type cpp --files-with-matches 'class\s+enable_wrapper_from_this' | xargs rg 'virtual' # Search for any shared base classes between `base_disposable` and `enable_wrapper_from_this` rg --type-add 'cpp:*.hpp,*.cpp' --type cpp 'class\s+(base_disposable|enable_wrapper_from_this)' -A 5 | rg 'public\s+'Length of output: 536
Script:
#!/bin/bash # Description: Check for constructors and destructors in base classes and identify overlapping members. # Define cpp type correctly with glob patterns rg --type-add 'cpp:*.hpp,*.cpp' --type cpp --files-with-matches 'class\s+base_disposable' | xargs rg -E 'constructor|destructor' rg --type-add 'cpp:*.hpp,*.cpp' --type cpp --files-with-matches 'class\s+enable_wrapper_from_this' | xargs rg -E 'constructor|destructor' # Search for overlapping member functions or variables between the two base classes rg --type-add 'cpp:*.hpp,*.cpp' --type cpp 'class\s+(base_disposable|enable_wrapper_from_this)' -A 20 | grep -E '(public|protected|private)\s+.*;'Length of output: 539
Script:
#!/bin/bash # Description: Corrected script to check for constructors and destructors in base classes and identify overlapping members. # Define cpp type correctly with glob patterns rg --type-add 'cpp:*.hpp,*.cpp' --type cpp --files-with-matches 'class\s+base_disposable' | xargs rg 'constructor|destructor' rg --type-add 'cpp:*.hpp,*.cpp' --type cpp --files-with-matches 'class\s+enable_wrapper_from_this' | xargs rg 'constructor|destructor' # Search for overlapping member functions or variables between the two base classes rg --type-add 'cpp:*.hpp,*.cpp' --type cpp 'class\s+(base_disposable|enable_wrapper_from_this)' -A 20 | rg '(public|protected|private)\s+.*;'Length of output: 408
src/rpp/rpp/operators/debounce.hpp (5)
28-28: Simplifiedis_disposed()method for better readabilityThe
is_disposed()method now directly returnsstate->is_disposed(), which simplifies the code and improves readability.
34-35: Enhanced inheritance for improved disposability and shared ownership
debounce_statenow inherits fromrpp::details::enable_wrapper_from_thisandrpp::details::base_disposable. This change enhances shared ownership management and ensures proper disposable behavior within the class.
83-83: Updated scheduling to use custom shared ownership mechanismThe
schedulemethod now usesthis->wrapper_from_this().lock()instead ofshared_from_this(). This aligns with the change toenable_wrapper_from_this, ensuring consistent and safe shared ownership throughout the code.
124-124: Direct disposal check improves clarityIn
debounce_observer_strategy, theis_disposed()method now directly callsstate->is_disposed(), simplifying the disposal check and enhancing code clarity.
157-157: Updated disposable strategy to include additional disposablesThe
updated_disposable_strategynow correctly accounts for the additional disposables by adding one to the previous strategy. This ensures the disposal hierarchy remains accurate and all disposables are properly managed.
| 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}); | ||
| } |
There was a problem hiding this comment.
🛠️ 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:
- Add a comment explaining the purpose of the test and the potential deadlock scenario it's preventing.
- Explicitly dispose of the observer after the scheduler advancement to test the full lifecycle.
- Add assertions after advancing the scheduler to verify the expected behavior (e.g., that the lambda was called).
- Consider testing with multiple emissions to ensure consistent behavior.
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("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()); | ||
| }); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Enhance test case clarity and robustness
The new test case effectively verifies that the merge operator doesn't cause deadlocks when checking if the observer is disposed. However, consider the following improvements:
- Use a more descriptive name for the test case, e.g., "merge_operator_does_not_deadlock_when_checking_observer_disposal".
- Consider using
REQUIREinstead ofCHECKfor critical assertions to ensure the test fails immediately if these conditions are not met. - Add a check to verify the observer's state before emission to ensure it's not disposed prematurely.
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:
- Uses a more descriptive test case name
- Replaces
std::optionalwith a directrpp::dynamic_observer<int> - Adds checks before and after emission
- Uses
REQUIREfor critical assertions - Adds a flag to ensure the value was actually emitted
| auto d = rpp::disposable_wrapper_impl<debounce_state<std::decay_t<Observer>, worker_t>>::make(std::forward<Observer>(observer), scheduler.create_worker(), duration); | ||
| auto ptr = d.lock(); | ||
| ptr->get_observer_under_lock()->set_upstream(d.as_weak()); |
There was a problem hiding this comment.
Ensure safe usage when locking disposable wrapper
In the lift method, after obtaining ptr via d.lock(), there is a potential for ptr to be null. To prevent a null pointer dereference, ensure that ptr is valid before using it.
Consider adding a check for ptr before dereferencing:
auto ptr = d.lock();
if (ptr)
{
ptr->get_observer_under_lock()->set_upstream(d.as_weak());
return rpp::observer<Type, debounce_observer_strategy<std::decay_t<Observer>, worker_t>>{std::move(ptr)};
}
else
{
// Handle the null case appropriately
}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:
auto ptr = d.lock();
assert(ptr != nullptr);
ptr->get_observer_under_lock()->set_upstream(d.as_weak());
BENCHMARK RESULTS (AUTOGENERATED)
|
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| Subscribe empty callbacks to empty observable | 301.75 ns | 2.16 ns | 2.16 ns | 1.00 |
| Subscribe empty callbacks to empty observable via pipe operator | 305.99 ns | 2.16 ns | 2.16 ns | 1.00 |
Sources
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| from array of 1 - create + subscribe + immediate | 685.83 ns | 0.31 ns | 0.31 ns | 1.00 |
| from array of 1 - create + subscribe + current_thread | 1034.89 ns | 3.70 ns | 3.71 ns | 1.00 |
| concat_as_source of just(1 immediate) create + subscribe | 2251.70 ns | 142.08 ns | 151.49 ns | 0.94 |
| defer from array of 1 - defer + create + subscribe + immediate | 733.08 ns | 0.31 ns | 0.31 ns | 1.00 |
| interval - interval + take(3) + subscribe + immediate | 2193.41 ns | 59.23 ns | 59.23 ns | 1.00 |
| interval - interval + take(3) + subscribe + current_thread | 2991.44 ns | 32.42 ns | 32.43 ns | 1.00 |
| from array of 1 - create + as_blocking + subscribe + new_thread | 29326.87 ns | 27389.10 ns | 27577.11 ns | 0.99 |
| from array of 1000 - create + as_blocking + subscribe + new_thread | 37373.34 ns | 51518.85 ns | 48957.91 ns | 1.05 |
| concat_as_source of just(1 immediate) and just(1,2 immediate)create + subscribe | 3526.54 ns | 213.99 ns | 213.76 ns | 1.00 |
Filtering Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+take(1)+subscribe | 1077.20 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just+filter(true)+subscribe | 840.65 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just(1,2)+skip(1)+subscribe | 994.91 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just(1,1,2)+distinct_until_changed()+subscribe | 846.63 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just(1,2)+first()+subscribe | 1246.75 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just(1,2)+last()+subscribe | 917.73 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just+take_last(1)+subscribe | 1136.97 ns | 18.20 ns | 17.91 ns | 1.02 |
| immediate_just(1,2,3)+element_at(1)+subscribe | 830.58 ns | 0.31 ns | 0.31 ns | 1.00 |
Schedulers
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate scheduler create worker + schedule | 264.95 ns | 2.16 ns | 2.16 ns | 1.00 |
| current_thread scheduler create worker + schedule | 364.37 ns | 5.87 ns | 5.57 ns | 1.05 |
| current_thread scheduler create worker + schedule + recursive schedule | 821.40 ns | 56.16 ns | 56.33 ns | 1.00 |
Transforming Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+map(v*2)+subscribe | 886.42 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just+scan(10, std::plus)+subscribe | 894.30 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just+flat_map(immediate_just(v*2))+subscribe | 2381.32 ns | 191.25 ns | 208.73 ns | 0.92 |
| immediate_just+buffer(2)+subscribe | 1535.36 ns | 13.90 ns | 13.59 ns | 1.02 |
| immediate_just+window(2)+subscribe + subscsribe inner | 2435.66 ns | 1322.33 ns | 1276.86 ns | 1.04 |
Conditional Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+take_while(false)+subscribe | 832.45 ns | - | - | 0.00 |
| immediate_just+take_while(true)+subscribe | 855.21 ns | 0.31 ns | 0.31 ns | 1.00 |
Utility Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just(1)+subscribe_on(immediate)+subscribe | 1982.37 ns | 0.31 ns | 0.31 ns | 1.00 |
Combining Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just(immediate_just(1), immediate_just(1)) + merge() + subscribe | 3454.31 ns | 236.67 ns | 259.63 ns | 0.91 |
| immediate_just(1) + merge_with(immediate_just(2)) + subscribe | 3871.26 ns | 190.95 ns | 215.45 ns | 0.89 |
| immediate_just(1) + with_latest_from(immediate_just(2)) + subscribe | - | 178.74 ns | 178.41 ns | 1.00 |
| immediate_just(immediate_just(1),immediate_just(1)) + switch_on_next() + subscribe | 3481.76 ns | 1200.81 ns | 1238.08 ns | 0.97 |
| immediate_just(1) + zip(immediate_just(2)) + subscribe | 2191.14 ns | 223.05 ns | 202.41 ns | 1.10 |
Subjects
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| publish_subject with 1 observer - on_next | 34.49 ns | 14.64 ns | 14.72 ns | 0.99 |
| subscribe 100 observers to publish_subject | 198279.17 ns | 15978.25 ns | 15761.87 ns | 1.01 |
| 100 on_next to 100 observers to publish_subject | 26967.29 ns | 17221.73 ns | 19484.39 ns | 0.88 |
Scenarios
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| basic sample | 1371.83 ns | 12.66 ns | 13.90 ns | 0.91 |
| basic sample with immediate scheduler | 1376.97 ns | 5.55 ns | 5.55 ns | 1.00 |
Aggregating Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+reduce(10, std::plus)+subscribe | 913.43 ns | 0.31 ns | 0.31 ns | 1.00 |
Error Handling Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| create(on_next(1), on_error())+on_error_resume_next(immediate_just(2)))+subscribe | 2071.26 ns | 915.58 ns | 937.98 ns | 0.98 |
| create(on_error())+retry(1)+subscribe | 645.84 ns | 120.34 ns | 122.33 ns | 0.98 |
ci-macos
General
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| Subscribe empty callbacks to empty observable | 990.13 ns | 3.99 ns | 3.92 ns | 1.02 |
| Subscribe empty callbacks to empty observable via pipe operator | 981.28 ns | 3.96 ns | 3.90 ns | 1.01 |
Sources
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| from array of 1 - create + subscribe + immediate | 1940.80 ns | 0.23 ns | 0.24 ns | 0.97 |
| from array of 1 - create + subscribe + current_thread | 2471.99 ns | 32.48 ns | 33.70 ns | 0.96 |
| concat_as_source of just(1 immediate) create + subscribe | 5462.82 ns | 435.47 ns | 423.17 ns | 1.03 |
| defer from array of 1 - defer + create + subscribe + immediate | 1947.97 ns | 0.23 ns | 0.24 ns | 0.97 |
| interval - interval + take(3) + subscribe + immediate | 4950.96 ns | 114.89 ns | 116.93 ns | 0.98 |
| interval - interval + take(3) + subscribe + current_thread | 6222.34 ns | 95.87 ns | 104.13 ns | 0.92 |
| from array of 1 - create + as_blocking + subscribe + new_thread | 81740.38 ns | 81089.92 ns | 74958.47 ns | 1.08 |
| from array of 1000 - create + as_blocking + subscribe + new_thread | 87654.00 ns | 85902.23 ns | 87103.38 ns | 0.99 |
| concat_as_source of just(1 immediate) and just(1,2 immediate)create + subscribe | 8181.09 ns | 590.03 ns | 613.72 ns | 0.96 |
Filtering Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+take(1)+subscribe | 2879.97 ns | 0.25 ns | 0.23 ns | 1.09 |
| immediate_just+filter(true)+subscribe | 2114.70 ns | 0.23 ns | 0.23 ns | 1.00 |
| immediate_just(1,2)+skip(1)+subscribe | 2746.78 ns | 0.23 ns | 0.23 ns | 1.00 |
| immediate_just(1,1,2)+distinct_until_changed()+subscribe | 2071.95 ns | 0.47 ns | 0.47 ns | 1.00 |
| immediate_just(1,2)+first()+subscribe | 3172.37 ns | 0.23 ns | 0.23 ns | 1.00 |
| immediate_just(1,2)+last()+subscribe | 2377.24 ns | 0.23 ns | 0.23 ns | 1.00 |
| immediate_just+take_last(1)+subscribe | 3018.53 ns | 0.23 ns | 0.23 ns | 1.00 |
| immediate_just(1,2,3)+element_at(1)+subscribe | 2118.51 ns | 0.23 ns | 0.23 ns | 1.00 |
Schedulers
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate scheduler create worker + schedule | 865.29 ns | 4.17 ns | 4.36 ns | 0.96 |
| current_thread scheduler create worker + schedule | 1192.26 ns | 36.81 ns | 39.79 ns | 0.93 |
| current_thread scheduler create worker + schedule + recursive schedule | 2045.10 ns | 201.70 ns | 222.03 ns | 0.91 |
Transforming Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+map(v*2)+subscribe | 2141.43 ns | 4.43 ns | 4.43 ns | 1.00 |
| immediate_just+scan(10, std::plus)+subscribe | 2361.15 ns | 0.47 ns | 0.47 ns | 1.00 |
| immediate_just+flat_map(immediate_just(v*2))+subscribe | 5203.96 ns | 495.06 ns | 622.13 ns | 0.80 |
| immediate_just+buffer(2)+subscribe | 2440.82 ns | 64.96 ns | 64.51 ns | 1.01 |
| immediate_just+window(2)+subscribe + subscsribe inner | 5328.98 ns | 2323.16 ns | 2356.96 ns | 0.99 |
Conditional Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+take_while(false)+subscribe | 2108.34 ns | - | - | 0.00 |
| immediate_just+take_while(true)+subscribe | 2126.04 ns | 0.23 ns | 0.23 ns | 1.00 |
Utility Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just(1)+subscribe_on(immediate)+subscribe | 4912.90 ns | 4.90 ns | 4.90 ns | 1.00 |
Combining Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just(immediate_just(1), immediate_just(1)) + merge() + subscribe | 7467.04 ns | 592.19 ns | 802.80 ns | 0.74 |
| immediate_just(1) + merge_with(immediate_just(2)) + subscribe | 8433.31 ns | 505.01 ns | 640.88 ns | 0.79 |
| immediate_just(1) + with_latest_from(immediate_just(2)) + subscribe | - | 579.76 ns | 595.10 ns | 0.97 |
| immediate_just(immediate_just(1),immediate_just(1)) + switch_on_next() + subscribe | 7979.43 ns | 1897.80 ns | 1953.34 ns | 0.97 |
| immediate_just(1) + zip(immediate_just(2)) + subscribe | 5146.32 ns | 926.61 ns | 792.91 ns | 1.17 |
Subjects
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| publish_subject with 1 observer - on_next | 72.36 ns | 46.64 ns | 48.98 ns | 0.95 |
| subscribe 100 observers to publish_subject | 338723.00 ns | 39072.82 ns | 40815.78 ns | 0.96 |
| 100 on_next to 100 observers to publish_subject | 52318.00 ns | 18246.46 ns | 18004.50 ns | 1.01 |
Scenarios
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| basic sample | 2703.57 ns | 65.75 ns | 70.89 ns | 0.93 |
| basic sample with immediate scheduler | 2715.43 ns | 18.30 ns | 18.55 ns | 0.99 |
Aggregating Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+reduce(10, std::plus)+subscribe | 2259.90 ns | 0.23 ns | 0.23 ns | 1.00 |
Error Handling Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| create(on_next(1), on_error())+on_error_resume_next(immediate_just(2)))+subscribe | 6126.33 ns | 3730.20 ns | 3874.78 ns | 0.96 |
| create(on_error())+retry(1)+subscribe | 1723.73 ns | 346.25 ns | 362.62 ns | 0.95 |
ci-ubuntu-clang
General
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| Subscribe empty callbacks to empty observable | 266.85 ns | 1.54 ns | 0.88 ns | 1.76 |
| Subscribe empty callbacks to empty observable via pipe operator | 266.63 ns | 1.54 ns | 0.88 ns | 1.76 |
Sources
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| from array of 1 - create + subscribe + immediate | 577.84 ns | 0.31 ns | 0.31 ns | 1.00 |
| from array of 1 - create + subscribe + current_thread | 805.80 ns | 4.01 ns | 4.33 ns | 0.93 |
| concat_as_source of just(1 immediate) create + subscribe | 2341.10 ns | 185.02 ns | 182.35 ns | 1.01 |
| defer from array of 1 - defer + create + subscribe + immediate | 776.01 ns | 0.31 ns | 0.31 ns | 1.00 |
| interval - interval + take(3) + subscribe + immediate | 2212.27 ns | 58.32 ns | 58.40 ns | 1.00 |
| interval - interval + take(3) + subscribe + current_thread | 3188.34 ns | 30.86 ns | 30.86 ns | 1.00 |
| from array of 1 - create + as_blocking + subscribe + new_thread | 30137.13 ns | 27835.12 ns | 27652.10 ns | 1.01 |
| from array of 1000 - create + as_blocking + subscribe + new_thread | 38139.24 ns | 33730.33 ns | 35166.85 ns | 0.96 |
| concat_as_source of just(1 immediate) and just(1,2 immediate)create + subscribe | 3708.41 ns | 304.70 ns | 304.62 ns | 1.00 |
Filtering Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+take(1)+subscribe | 1152.62 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just+filter(true)+subscribe | 842.24 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just(1,2)+skip(1)+subscribe | 1087.08 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just(1,1,2)+distinct_until_changed()+subscribe | 865.83 ns | 0.31 ns | 0.62 ns | 0.50 |
| immediate_just(1,2)+first()+subscribe | 1403.72 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just(1,2)+last()+subscribe | 996.50 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just+take_last(1)+subscribe | 1196.91 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just(1,2,3)+element_at(1)+subscribe | 864.67 ns | 0.31 ns | 0.31 ns | 1.00 |
Schedulers
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate scheduler create worker + schedule | 278.45 ns | 1.54 ns | 0.88 ns | 1.76 |
| current_thread scheduler create worker + schedule | 392.51 ns | 4.60 ns | 4.94 ns | 0.93 |
| current_thread scheduler create worker + schedule + recursive schedule | 855.05 ns | 56.20 ns | 56.21 ns | 1.00 |
Transforming Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+map(v*2)+subscribe | 837.26 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just+scan(10, std::plus)+subscribe | 962.35 ns | 0.31 ns | 0.31 ns | 1.00 |
| immediate_just+flat_map(immediate_just(v*2))+subscribe | 2271.46 ns | 228.33 ns | 172.99 ns | 1.32 |
| immediate_just+buffer(2)+subscribe | 1525.11 ns | 13.90 ns | 14.20 ns | 0.98 |
| immediate_just+window(2)+subscribe + subscsribe inner | 2474.81 ns | 927.39 ns | 928.30 ns | 1.00 |
Conditional Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+take_while(false)+subscribe | 847.35 ns | - | - | 0.00 |
| immediate_just+take_while(true)+subscribe | 866.17 ns | 0.31 ns | 0.31 ns | 1.00 |
Utility Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just(1)+subscribe_on(immediate)+subscribe | 2000.37 ns | 0.31 ns | 0.31 ns | 1.00 |
Combining Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just(immediate_just(1), immediate_just(1)) + merge() + subscribe | 3284.02 ns | 291.22 ns | 201.39 ns | 1.45 |
| immediate_just(1) + merge_with(immediate_just(2)) + subscribe | 3723.68 ns | 220.69 ns | 160.56 ns | 1.37 |
| immediate_just(1) + with_latest_from(immediate_just(2)) + subscribe | - | 198.92 ns | 200.77 ns | 0.99 |
| immediate_just(immediate_just(1),immediate_just(1)) + switch_on_next() + subscribe | 3372.45 ns | 846.28 ns | 863.63 ns | 0.98 |
| immediate_just(1) + zip(immediate_just(2)) + subscribe | 2251.17 ns | 198.06 ns | 160.59 ns | 1.23 |
Subjects
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| publish_subject with 1 observer - on_next | 54.09 ns | 18.21 ns | 17.83 ns | 1.02 |
| subscribe 100 observers to publish_subject | 215262.40 ns | 16047.95 ns | 15973.14 ns | 1.00 |
| 100 on_next to 100 observers to publish_subject | 37551.50 ns | 20680.58 ns | 17364.46 ns | 1.19 |
Scenarios
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| basic sample | 1293.97 ns | 11.55 ns | 11.73 ns | 0.98 |
| basic sample with immediate scheduler | 1303.21 ns | 6.17 ns | 6.17 ns | 1.00 |
Aggregating Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+reduce(10, std::plus)+subscribe | 996.32 ns | 0.31 ns | 0.31 ns | 1.00 |
Error Handling Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| create(on_next(1), on_error())+on_error_resume_next(immediate_just(2)))+subscribe | 2256.41 ns | 1007.33 ns | 1030.32 ns | 0.98 |
| create(on_error())+retry(1)+subscribe | 656.72 ns | 157.19 ns | 156.94 ns | 1.00 |
ci-windows
General
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| Subscribe empty callbacks to empty observable | 559.56 ns | 4.01 ns | 4.32 ns | 0.93 |
| Subscribe empty callbacks to empty observable via pipe operator | 575.97 ns | 4.01 ns | 4.32 ns | 0.93 |
Sources
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| from array of 1 - create + subscribe + immediate | 1148.00 ns | 9.71 ns | 9.71 ns | 1.00 |
| from array of 1 - create + subscribe + current_thread | 1423.55 ns | 17.89 ns | 17.95 ns | 1.00 |
| concat_as_source of just(1 immediate) create + subscribe | 3699.67 ns | 233.25 ns | 244.29 ns | 0.95 |
| defer from array of 1 - defer + create + subscribe + immediate | 1198.47 ns | 9.46 ns | 9.43 ns | 1.00 |
| interval - interval + take(3) + subscribe + immediate | 3381.66 ns | 145.68 ns | 145.37 ns | 1.00 |
| interval - interval + take(3) + subscribe + current_thread | 3453.64 ns | 64.35 ns | 65.91 ns | 0.98 |
| from array of 1 - create + as_blocking + subscribe + new_thread | 120387.50 ns | 114180.00 ns | 112263.64 ns | 1.02 |
| from array of 1000 - create + as_blocking + subscribe + new_thread | 129425.00 ns | 131677.78 ns | 132437.50 ns | 0.99 |
| concat_as_source of just(1 immediate) and just(1,2 immediate)create + subscribe | 5369.06 ns | 312.73 ns | 332.85 ns | 0.94 |
Filtering Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+take(1)+subscribe | 1798.27 ns | 25.28 ns | 25.28 ns | 1.00 |
| immediate_just+filter(true)+subscribe | 1321.73 ns | 24.35 ns | 24.35 ns | 1.00 |
| immediate_just(1,2)+skip(1)+subscribe | 1735.20 ns | 24.06 ns | 24.07 ns | 1.00 |
| immediate_just(1,1,2)+distinct_until_changed()+subscribe | 1352.46 ns | 28.99 ns | 28.99 ns | 1.00 |
| immediate_just(1,2)+first()+subscribe | 2352.35 ns | 22.82 ns | 22.83 ns | 1.00 |
| immediate_just(1,2)+last()+subscribe | 1784.49 ns | 24.06 ns | 24.07 ns | 1.00 |
| immediate_just+take_last(1)+subscribe | 1995.52 ns | 70.76 ns | 72.06 ns | 0.98 |
| immediate_just(1,2,3)+element_at(1)+subscribe | 1338.11 ns | 27.45 ns | 28.57 ns | 0.96 |
Schedulers
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate scheduler create worker + schedule | 480.39 ns | 6.17 ns | 6.48 ns | 0.95 |
| current_thread scheduler create worker + schedule | 651.97 ns | 13.92 ns | 13.93 ns | 1.00 |
| current_thread scheduler create worker + schedule + recursive schedule | 1090.37 ns | 103.31 ns | 105.85 ns | 0.98 |
Transforming Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+map(v*2)+subscribe | 1307.22 ns | 24.37 ns | 24.35 ns | 1.00 |
| immediate_just+scan(10, std::plus)+subscribe | 1448.11 ns | 26.84 ns | 26.55 ns | 1.01 |
| immediate_just+flat_map(immediate_just(v*2))+subscribe | 3443.80 ns | 270.07 ns | 349.68 ns | 0.77 |
| immediate_just+buffer(2)+subscribe | 2625.06 ns | 70.12 ns | 69.08 ns | 1.02 |
| immediate_just+window(2)+subscribe + subscsribe inner | 4077.22 ns | 1324.30 ns | 1347.09 ns | 0.98 |
Conditional Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+take_while(false)+subscribe | 1317.40 ns | 23.12 ns | 23.12 ns | 1.00 |
| immediate_just+take_while(true)+subscribe | 1313.04 ns | 24.36 ns | 24.35 ns | 1.00 |
Utility Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just(1)+subscribe_on(immediate)+subscribe | 3493.20 ns | 11.11 ns | 11.10 ns | 1.00 |
Combining Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just(immediate_just(1), immediate_just(1)) + merge() + subscribe | 5016.67 ns | 310.32 ns | 420.62 ns | 0.74 |
| immediate_just(1) + merge_with(immediate_just(2)) + subscribe | 5402.43 ns | 275.13 ns | 326.95 ns | 0.84 |
| immediate_just(1) + with_latest_from(immediate_just(2)) + subscribe | - | 303.61 ns | 309.32 ns | 0.98 |
| immediate_just(immediate_just(1),immediate_just(1)) + switch_on_next() + subscribe | 5208.13 ns | 933.27 ns | 946.83 ns | 0.99 |
| immediate_just(1) + zip(immediate_just(2)) + subscribe | 3539.22 ns | 574.22 ns | 565.80 ns | 1.01 |
Subjects
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| publish_subject with 1 observer - on_next | 36.50 ns | 20.24 ns | 19.46 ns | 1.04 |
| subscribe 100 observers to publish_subject | 274025.00 ns | 27035.71 ns | 29477.14 ns | 0.92 |
| 100 on_next to 100 observers to publish_subject | 54910.53 ns | 32662.16 ns | 32584.37 ns | 1.00 |
Scenarios
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| basic sample | 1873.94 ns | 101.59 ns | 101.66 ns | 1.00 |
| basic sample with immediate scheduler | 1875.51 ns | 73.93 ns | 74.83 ns | 0.99 |
Aggregating Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| immediate_just+reduce(10, std::plus)+subscribe | 1470.11 ns | 24.97 ns | 24.99 ns | 1.00 |
Error Handling Operators
| name | rxcpp | rpp | prev rpp | ratio |
|---|---|---|---|---|
| create(on_next(1), on_error())+on_error_resume_next(immediate_just(2)))+subscribe | 2151.40 ns | 246.06 ns | 252.61 ns | 0.97 |
| create(on_error())+retry(1)+subscribe | 1459.57 ns | 191.36 ns | 191.97 ns | 1.00 |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## v2 #661 +/- ##
==========================================
+ Coverage 98.27% 98.28% +0.01%
==========================================
Files 154 154
Lines 7670 7717 +47
==========================================
+ Hits 7538 7585 +47
Misses 132 132 ☔ View full report in Codecov by Sentry. |
|
|
FYI, in this project (https://github.com/thorstink/Symmetri/tree/feature/gui) I was stuck on a few-months old version of rpp v2, because it would deadlock if I upgraded to a more recent version. This update finally seemed to have fixed it :) Thanks! (I couldn't really create a nice isolated example for you, but not needed anymore :) ) |
Glad to hear it! But you could write about your issues next time anyway, I would be really glad to help you to debug and fix issues 😅 |



Summary by CodeRabbit
New Features
Bug Fixes
Tests
combine_latest,debounce,merge, andzipoperators to verify non-deadlocking behavior.