Skip to content

Commit e1d242f

Browse files
sazonovkirillmeta-codesync[bot]
authored andcommitted
Fix 3 flaky thrift tests: InteractionTest, ServiceHealthPoller, RocketNetwork
Summary: Fix race conditions and timing-sensitivity in three flaky thrift tests. 1. InteractionTest::PrioritizedInteractionRequest (cpp2/test/InteractionTest.cpp): The test had a race between TilePromise::fulfill() executing asynchronously on the EventBase thread and the concurrency controller dequeuing the next request. If the interaction request was dequeued before the tile was fully registered, it would hit an uninitialized TilePromise. Fixed by completing the factory method synchronously (get() on sf1) before proceeding, and using a separate "blocker" addPrimitive(0,0) call to occupy the worker thread instead of relying on the interaction setup. 2. ServiceHealthPollerTest::DynamicLiveness (cpp2/test/ServiceHealthPollerTest.cpp): The test used co_await sleep(50ms) then checked the result, which was timing-dependent and could fail under CI load. Replaced with event-driven co_await result->co_next() which waits for the actual state change. Also increased liveness interval from 200ms to 500ms to ensure the "unchanged because liveness is large" assertion has sufficient margin. 3. RocketNetworkTest::ObserverIsNotInstalledWhenFlagIsFalse (transport/rocket/test/network/RocketNetworkTest.cpp): After reconnect(), the old server-side connection may linger briefly while the new one is already created. The retry loop in getServerConnection() only tried 10 times which was insufficient under CI load. Increased to 50 retries. Reviewed By: evanjzou Differential Revision: D94987644 fbshipit-source-id: 16a0e176290f9ca42859e15ee21fa993ced89e0d
1 parent 2dc30c9 commit e1d242f

3 files changed

Lines changed: 35 additions & 24 deletions

File tree

third-party/thrift/src/thrift/lib/cpp2/test/InteractionTest.cpp

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -108,18 +108,18 @@ TEST(InteractionTest, PrioritizedInteractionRequest) {
108108
// test.
109109
FLAGS_thrift_experimental_use_resource_pools = true;
110110
struct BlockingCalculatorHandler : public SemiCalculatorHandler {
111-
folly::Baton<> blockInteraction, blockNormal, startedInteraction;
112-
113-
TileAndResponse<AdditionIf, int> initializedAddition(int x) override {
114-
startedInteraction.post();
115-
EXPECT_TRUE(blockInteraction.try_wait_for(std::chrono::seconds(5)));
116-
return SemiCalculatorHandler::initializedAddition(x);
117-
}
111+
folly::Baton<> blockBlocker, blockNormal, startedBlocker;
118112

119113
folly::SemiFuture<int32_t> semifuture_addPrimitive(
120114
int32_t a, int32_t b) override {
121-
EXPECT_TRUE(blockNormal.try_wait_for(std::chrono::seconds(5)));
122-
blockNormal.reset();
115+
if (a == 0 && b == 0) {
116+
// This is the "blocker" call used to occupy the worker thread.
117+
startedBlocker.post();
118+
EXPECT_TRUE(blockBlocker.try_wait_for(std::chrono::seconds(5)));
119+
} else {
120+
EXPECT_TRUE(blockNormal.try_wait_for(std::chrono::seconds(5)));
121+
blockNormal.reset();
122+
}
123123
return a + b;
124124
}
125125
};
@@ -134,18 +134,26 @@ TEST(InteractionTest, PrioritizedInteractionRequest) {
134134
auto client = runner.newClient<CalculatorAsyncClient>(
135135
nullptr, RocketClientChannel::newChannel);
136136

137+
// Create the interaction and let the factory method complete. This ensures
138+
// the tile is fully registered, avoiding a race where TilePromise::fulfill()
139+
// runs asynchronously on the EventBase thread after the concurrency
140+
// controller has already released the worker to dequeue the next request.
137141
RpcOptions opts;
138142
auto [adder, sf1] = client->eager_semifuture_initializedAddition(opts, 42);
139-
auto sf2 = adder.semifuture_getPrimitive();
143+
EXPECT_EQ(std::move(sf1).get(), 42);
140144

141-
// Wait for the interaction requests to reach the server
142-
handler->startedInteraction.wait();
145+
// Block the sole worker thread with a normal request.
146+
auto blocker = client->semifuture_addPrimitive(0, 0);
147+
handler->startedBlocker.wait();
143148

149+
// While the worker is blocked, send both an interaction request (MID_PRI)
150+
// and a normal request (LO_PRI). Both enter the resource pool's RequestPile.
151+
auto sf2 = adder.semifuture_getPrimitive();
144152
auto normal = client->semifuture_addPrimitive(1, 2);
145153

146-
// Wait for the normal requests to reach the server
154+
// Wait for both requests to be queued in the resource pool.
147155
auto start = std::chrono::steady_clock::now();
148-
while (runner.getThriftServer().resourcePoolSet().numQueued() != 1) {
156+
while (runner.getThriftServer().resourcePoolSet().numQueued() != 2) {
149157
/* sleep override */
150158
std::this_thread::sleep_for(std::chrono::milliseconds(10));
151159
if (std::chrono::steady_clock::now() - start > std::chrono::seconds(5)) {
@@ -154,14 +162,15 @@ TEST(InteractionTest, PrioritizedInteractionRequest) {
154162
}
155163
}
156164

157-
// Unblock the interaction request and wait for it to finish
158-
handler->blockInteraction.post();
159-
EXPECT_EQ(std::move(sf1).get(), 42);
165+
// Unblock the blocker so the worker can process queued requests.
166+
handler->blockBlocker.post();
167+
EXPECT_EQ(std::move(blocker).get(), 0);
160168

161-
// Make sure interaction request is executed before normal request.
169+
// The interaction request (MID_PRI) should be dequeued before the normal
170+
// request (LO_PRI).
162171
EXPECT_EQ(std::move(sf2).get(), 42);
163172

164-
// Unblock the normal request and wait for it to finish
173+
// Unblock the normal request and wait for it to finish.
165174
handler->blockNormal.post();
166175
EXPECT_EQ(std::move(normal).get(), 3);
167176
}

third-party/thrift/src/thrift/lib/cpp2/test/ServiceHealthPollerTest.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,17 +243,17 @@ CO_TEST(ServiceHealthPoller, DynamicLiveness) {
243243
EXPECT_EQ(co_await result->co_next(), ServiceHealth::ERROR);
244244

245245
handler.set(ServiceHealth::OK);
246-
co_await folly::coro::sleep(50ms);
247-
EXPECT_EQ(result->get(), ServiceHealth::OK);
246+
// Use event-driven wait instead of sleep to avoid flakiness under load
247+
EXPECT_EQ(co_await result->co_next(), ServiceHealth::OK);
248248

249-
liveness.setValue(200ms);
249+
liveness.setValue(500ms);
250250
folly::observer_detail::ObserverManager::waitForAllUpdates();
251251
// wait for any existing polling to be done
252252
co_await result->co_next();
253253

254254
handler.set(ServiceHealth::ERROR);
255255
co_await folly::coro::sleep(50ms);
256-
// unchanged because liveness is large
256+
// unchanged because liveness is large (500ms >> 50ms)
257257
EXPECT_EQ(result->get(), ServiceHealth::OK);
258258

259259
EXPECT_EQ(co_await result->co_next(), ServiceHealth::ERROR);

third-party/thrift/src/thrift/lib/cpp2/transport/rocket/test/network/RocketNetworkTest.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,10 @@ class RocketNetworkTest : public testing::Test {
106106
void unsetExpectedSetupMetadata() { server_->setExpectedSetupMetadata({}); }
107107

108108
// Returns the server connection once it is established.
109+
// After reconnect(), the old server-side connection may linger briefly while
110+
// the new one is already created, so we retry until exactly 1 remains.
109111
RocketServerConnection* getServerConnection() {
110-
for (int i = 0, numTries = 10; i < numTries; ++i) {
112+
for (int i = 0, numTries = 50; i < numTries; ++i) {
111113
auto connCount = 0;
112114
RocketServerConnection* conn = nullptr;
113115
this->server_->getEventBase().runInEventBaseThreadAndWait([&] {

0 commit comments

Comments
 (0)