-
Notifications
You must be signed in to change notification settings - Fork 2
fix: race condition between status check and provider lookup #108
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: main
Are you sure you want to change the base?
Changes from 10 commits
212e050
c327b5c
2be23ba
18f55be
0861ae9
9b17006
6a3bb4d
1ed56b5
8d1e0da
134157b
8b8ba18
2dad4bb
bd7a6d6
cbd0bc9
0572daa
dcea3cd
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 |
|---|---|---|
|
|
@@ -3,8 +3,12 @@ | |
| #include <gmock/gmock.h> | ||
| #include <gtest/gtest.h> | ||
|
|
||
| #include <atomic> | ||
| #include <chrono> | ||
| #include <future> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <thread> | ||
|
|
||
| #include "absl/status/status.h" | ||
| #include "mocks/mock_feature_provider.h" | ||
|
|
@@ -381,3 +385,85 @@ | |
|
|
||
| EXPECT_TRUE(client.GetBooleanValue("flag", false)); | ||
| } | ||
|
|
||
| TEST_F(ClientAPITest, ParallelProviderSwapRaceCondition) { | ||
| std::string domain = "race-domain"; | ||
| ClientAPI client(repo_, domain); | ||
| std::atomic<bool> evaluate{true}; | ||
| std::atomic<bool> running{true}; | ||
|
|
||
| // Continuously evaluate flags when allowed. | ||
| std::thread evaluation_thread([&]() { | ||
| while (running) { | ||
| if (evaluate) { | ||
| client.GetBooleanValue("flag", false); | ||
| } else { | ||
| std::this_thread::sleep_for(std::chrono::microseconds(10)); | ||
| } | ||
| } | ||
| }); | ||
|
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. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Replace sleep-based race coordination with an explicit handshake. Line 439 and Line 442 rely on scheduler timing. This can pass without any evaluation occurring while the provider is Also applies to: 439-445 🤖 Prompt for AI Agents |
||
|
|
||
| // Continuously swap providers. | ||
| std::thread swap_thread([&]() { | ||
| int iterations = 50; | ||
| for (int i = 0; i < iterations && running; ++i) { | ||
| auto not_ready_provider = | ||
| std::make_shared<StrictMock<MockFeatureProvider>>(); | ||
|
|
||
| auto init_called = std::make_shared<std::promise<void>>(); | ||
| auto proceed_init = std::make_shared<std::promise<void>>(); | ||
| auto init_finished = std::make_shared<std::promise<void>>(); | ||
| std::shared_future<void> proceed_future = | ||
| proceed_init->get_future().share(); | ||
|
|
||
| EXPECT_CALL(*not_ready_provider, Init(_)) | ||
| .WillOnce( | ||
| testing::Invoke([init_called, proceed_future, init_finished]( | ||
| const EvaluationContext&) -> absl::Status { | ||
| init_called->set_value(); | ||
| proceed_future.wait(); | ||
| init_finished->set_value(); | ||
| return absl::OkStatus(); | ||
| })); | ||
|
|
||
| EXPECT_CALL(*not_ready_provider, GetBooleanEvaluation(_, _, _)).Times(0); | ||
| EXPECT_CALL(*not_ready_provider, Shutdown()) | ||
| .Times(testing::AtMost(1)) | ||
| .WillOnce(Return(absl::OkStatus())); | ||
|
|
||
| repo_.SetProvider(domain, not_ready_provider, | ||
| EvaluationContext::Builder().build(), false); | ||
|
|
||
| init_called->get_future().wait(); | ||
|
|
||
| std::this_thread::sleep_for(std::chrono::microseconds(100)); | ||
|
|
||
| evaluate = false; | ||
| std::this_thread::sleep_for(std::chrono::microseconds(10)); | ||
|
|
||
| proceed_init->set_value(); | ||
| init_finished->get_future().wait(); | ||
|
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. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Bound the future waits to avoid hanging the test suite. If 🤖 Prompt for AI Agents |
||
|
|
||
| auto ready_provider = std::make_shared<NiceMock<MockFeatureProvider>>(); | ||
| ON_CALL(*ready_provider, Init(_)).WillByDefault(Return(absl::OkStatus())); | ||
| ON_CALL(*ready_provider, GetBooleanEvaluation(_, _, _)) | ||
| .WillByDefault(testing::Invoke( | ||
| [](std::string_view, bool, const EvaluationContext&) | ||
| -> absl::StatusOr<std::unique_ptr<BoolResolutionDetails>> { | ||
| return std::make_unique<BoolResolutionDetails>( | ||
| true, Reason::kTargetingMatch, std::nullopt, | ||
| FlagMetadata()); | ||
| })); | ||
|
|
||
| repo_.SetProvider(domain, ready_provider, | ||
| EvaluationContext::Builder().build(), true); | ||
| evaluate = true; | ||
| std::this_thread::sleep_for(std::chrono::microseconds(100)); | ||
| } | ||
| running = false; | ||
| }); | ||
|
|
||
| swap_thread.join(); | ||
| running = false; | ||
| evaluation_thread.join(); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.