Skip to content

Commit 133f2e0

Browse files
Joshua Pollackmeta-codesync[bot]
authored andcommitted
Drain admitted TKO probes before Proxy EventBase teardown
Summary: During `CarbonRouterInstance` shutdown, a TKO health probe could still be armed on a Proxy's EventBase after the proxy `VirtualEventBase`s were dropped. The probe re-entered `VirtualEventBase::destroyImpl()` on an already-completed keepalive and aborted with `std::future_error: Promise already satisfied`. Close probe admission before tearing the proxies down: - `shutdownImpl()` queues a terminal `disableProbes()` on each Proxy EventBase, after the auxiliary threads join and before the proxy `VirtualEventBase`s are dropped. - `ProxyDestinationBase` checks the gate before arming the probe timer and again in the timer callback, so nothing is admitted once the gate is set. - Probes admitted before the gate retain the existing `VirtualEventBase` keepalive and drain before Proxy destruction; the steady-state probe path is unchanged. Reviewed By: ghostonhuang Differential Revision: D115378060 fbshipit-source-id: 39d93973192e6396e60e82f559924017790db8a9
1 parent 3905cf7 commit 133f2e0

4 files changed

Lines changed: 86 additions & 0 deletions

File tree

mcrouter/CarbonRouterInstance-inl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,12 @@ CarbonRouterInstance<RouterInfo>::CarbonRouterInstance(
406406
template <class RouterInfo>
407407
void CarbonRouterInstance<RouterInfo>::shutdownImpl() noexcept {
408408
joinAuxiliaryThreads();
409+
for (size_t i = 0; i < proxies_.size(); ++i) {
410+
proxyEvbs_[i]->runInEventBaseThread(
411+
[destinationMap = proxies_[i]->destinationMap()] {
412+
destinationMap->disableProbes();
413+
});
414+
}
409415
proxyEvbs_.clear();
410416
resetMetadata();
411417
resetAxonProxyClientFactory();

mcrouter/ProxyDestinationBase.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,19 @@ void ProxyDestinationBase::scheduleNextProbe() {
224224
}
225225

226226
void ProxyDestinationBase::startSendingProbes() {
227+
if (proxy().destinationMap()->probesDisabled()) {
228+
return;
229+
}
230+
227231
// Reset the RequestContext before scheduling repeated tasks.
228232
folly::RequestContextScopeGuard guard{nullptr};
229233
probeDelayNextMs = proxy().router().opts().probe_delay_initial_ms;
230234
probeTimer_ =
231235
folly::AsyncTimeout::make(proxy().eventBase(), [this]() noexcept {
236+
if (proxy().destinationMap()->probesDisabled()) {
237+
return;
238+
}
239+
232240
// Note that the previous probe might still be in flight
233241
if (!probeInflight_) {
234242
probeInflight_ = true;

mcrouter/ProxyDestinationMap.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ class ProxyDestinationMap {
103103
*/
104104
void setResetTimer(std::chrono::milliseconds interval);
105105

106+
void disableProbes() {
107+
probesDisabled_ = true;
108+
}
109+
110+
bool probesDisabled() const {
111+
return probesDisabled_;
112+
}
113+
106114
/**
107115
* Calls f(const ProxyDestination&) for each destination stored
108116
* in ProxyDestinationMap. The whole map is locked during the call.
@@ -145,6 +153,7 @@ class ProxyDestinationMap {
145153

146154
uint32_t inactivityTimeout_;
147155
std::unique_ptr<folly::AsyncTimeout> resetTimer_;
156+
bool probesDisabled_{false};
148157

149158
/**
150159
* Schedules timeout for resetting inactive connections.

mcrouter/test/cpp_unit_tests/McrouterClientUsage.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@
1010
#include <utility>
1111
#include <vector>
1212

13+
#include <fmt/core.h>
14+
#include <glog/logging.h>
1315
#include <gtest/gtest.h>
1416

1517
#include <folly/fibers/Baton.h>
1618
#include <folly/io/async/EventBase.h>
19+
#include <thrift/lib/cpp2/util/ScopedServerInterfaceThread.h>
1720

1821
#include "mcrouter/CarbonRouterClient.h"
1922
#include "mcrouter/CarbonRouterInstance.h"
2023
#include "mcrouter/ExecutorObserver.h"
2124
#include "mcrouter/McReqUtil.h"
2225
#include "mcrouter/Proxy.h"
26+
#include "mcrouter/ProxyDestinationMap.h"
2327
#include "mcrouter/config.h"
2428
#include "mcrouter/lib/carbon/RequestReplyUtil.h"
2529
#include "mcrouter/lib/carbon/example/gen/HelloGoodbyeRouterInfo.h"
@@ -28,13 +32,16 @@
2832
#include "mcrouter/lib/network/AsyncMcServerWorker.h"
2933
#include "mcrouter/lib/network/McServerRequestContext.h"
3034
#include "mcrouter/lib/network/gen/MemcacheRouterInfo.h"
35+
#include "mcrouter/lib/network/gen/gen-cpp2/Memcache.h"
3136

3237
#include "mcrouter/stats.h"
3338

3439
using facebook::memcache::McGetReply;
3540
using facebook::memcache::McGetRequest;
3641
using facebook::memcache::McStatsReply;
3742
using facebook::memcache::McStatsRequest;
43+
using facebook::memcache::McVersionReply;
44+
using facebook::memcache::McVersionRequest;
3845
using facebook::memcache::MemcacheRouterInfo;
3946
using facebook::memcache::mcrouter::CarbonRouterClient;
4047
using facebook::memcache::mcrouter::CarbonRouterInstance;
@@ -756,3 +763,59 @@ TEST(CarbonRouterClient, requestExpiryTestWithLatencyInjectionRoute) {
756763
server.shutdown();
757764
EXPECT_TRUE(replyReceived);
758765
}
766+
767+
class TkoProbeHandler final : public apache::thrift::ServiceHandler<
768+
facebook::memcache::thrift::Memcache> {
769+
public:
770+
void async_eb_mcGet(
771+
apache::thrift::HandlerCallbackPtr<McGetReply> callback,
772+
const McGetRequest&) override final {
773+
callback->result(McGetReply(carbon::Result::TIMEOUT));
774+
}
775+
776+
void async_eb_mcVersion(
777+
apache::thrift::HandlerCallbackPtr<McVersionReply> callback,
778+
const McVersionRequest&) override final {
779+
versionRequested_.post();
780+
callback->result(McVersionReply(carbon::Result::OK));
781+
}
782+
783+
folly::fibers::Baton& versionRequested() {
784+
return versionRequested_;
785+
}
786+
787+
private:
788+
folly::fibers::Baton versionRequested_;
789+
};
790+
791+
TEST(CarbonRouterInstance, disableProbesPreventsScheduledTkoProbe) {
792+
auto handler = std::make_shared<TkoProbeHandler>();
793+
apache::thrift::ScopedServerInterfaceThread server(handler, "127.0.0.1", 0);
794+
795+
auto opts = defaultTestOptions();
796+
opts.failures_until_tko = 1;
797+
opts.probe_delay_initial_ms = 1;
798+
opts.config_str = fmt::format(
799+
R"({{"route":{{"type":"PoolRoute","pool":{{"name":"A","servers":["127.0.0.1:{}"],"protocol":"thrift"}}}}}})",
800+
server.getPort());
801+
auto router = CarbonRouterInstance<MemcacheRouterInfo>::init(
802+
"disableProbesPreventsScheduledTkoProbe", opts);
803+
804+
auto& proxy = *CHECK_NOTNULL(router->getProxy(0));
805+
auto client = router->createClient(0, false);
806+
auto& clientRef = *CHECK_NOTNULL(client.get());
807+
folly::fibers::Baton replyReceived;
808+
const McGetRequest request("key");
809+
clientRef.send(
810+
request,
811+
[&proxy, &replyReceived](const McGetRequest&, McGetReply&& reply) {
812+
EXPECT_EQ(carbon::Result::TIMEOUT, *reply.result());
813+
proxy.destinationMap()->disableProbes();
814+
replyReceived.post();
815+
});
816+
replyReceived.wait();
817+
818+
EXPECT_FALSE(
819+
handler->versionRequested().try_wait_for(std::chrono::seconds(1)));
820+
router->shutdown();
821+
}

0 commit comments

Comments
 (0)