Commit a2792966 authored by Keivaun Waugh's avatar Keivaun Waugh Committed by Facebook GitHub Bot

Add callback for single EVB loop duration violators

Summary:
Context for motivation is in
https://fb.workplace.com/groups/560979627394613/permalink/2194915787334314.
This solution is different than the one we discussed in that post. I considered
implementing a set of post-evb-loop function callbacks that all took in the
ebv loop_time, but that was a bigger change than expected since currently the
callbacks are designed to not take any variables, and modifying/adding classes
like `FunctionLoopCallback` to either take a param or not seemed like overkill.
I instead implemented the callback basically the same as setMaxLatency
exception that the duration is a single-loop duration with no exponential
smoothing or backoffs.

Differential Revision: D30916498

fbshipit-source-id: 27cc7edf919ab11d2ee75a7f722b6a48b193c445
parent bb46a529
...@@ -1260,6 +1260,8 @@ REGISTER_TYPED_TEST_CASE_P( ...@@ -1260,6 +1260,8 @@ REGISTER_TYPED_TEST_CASE_P(
CallbackOrderTest, CallbackOrderTest,
AlwaysEnqueueCallbackOrderTest, AlwaysEnqueueCallbackOrderTest,
IdleTime, IdleTime,
MaxLatencyUndamped,
UnsetMaxLatencyUndamped,
ThisLoop, ThisLoop,
EventBaseThreadLoop, EventBaseThreadLoop,
EventBaseThreadName, EventBaseThreadName,
......
...@@ -413,13 +413,18 @@ bool EventBase::loopBody(int flags, bool ignoreKeepAlive) { ...@@ -413,13 +413,18 @@ bool EventBase::loopBody(int flags, bool ignoreKeepAlive) {
<< " notificationQueueSize: " << getNotificationQueueSize() << " notificationQueueSize: " << getNotificationQueueSize()
<< " nothingHandledYet(): " << nothingHandledYet(); << " nothingHandledYet(): " << nothingHandledYet();
if (maxLatency_ > std::chrono::microseconds::zero()) {
// see if our average loop time has exceeded our limit // see if our average loop time has exceeded our limit
if ((maxLatency_ > std::chrono::microseconds::zero()) && if (dampenMaxLatency_ &&
(maxLatencyLoopTime_.get() > double(maxLatency_.count()))) { (maxLatencyLoopTime_.get() > double(maxLatency_.count()))) {
maxLatencyCob_(); maxLatencyCob_();
// back off temporarily -- don't keep spamming maxLatencyCob_ // back off temporarily -- don't keep spamming maxLatencyCob_
// if we're only a bit over the limit // if we're only a bit over the limit
maxLatencyLoopTime_.dampen(0.9); maxLatencyLoopTime_.dampen(0.9);
} else if (!dampenMaxLatency_ && loop_time > maxLatency_) {
// If no damping, we compare the raw loop time
maxLatencyCob_();
}
} }
// Our loop run did real work; reset the idle timer // Our loop run did real work; reset the idle timer
......
...@@ -632,10 +632,14 @@ class EventBase : public TimeoutManager, ...@@ -632,10 +632,14 @@ class EventBase : public TimeoutManager,
* called when that latency is exceeded. * called when that latency is exceeded.
* OBS: This functionality depends on time-measurement. * OBS: This functionality depends on time-measurement.
*/ */
void setMaxLatency(std::chrono::microseconds maxLatency, Func maxLatencyCob) { void setMaxLatency(
std::chrono::microseconds maxLatency,
Func maxLatencyCob,
bool dampen = true) {
assert(enableTimeMeasurement_); assert(enableTimeMeasurement_);
maxLatency_ = maxLatency; maxLatency_ = maxLatency;
maxLatencyCob_ = std::move(maxLatencyCob); maxLatencyCob_ = std::move(maxLatencyCob);
dampenMaxLatency_ = dampen;
} }
/** /**
...@@ -917,6 +921,10 @@ class EventBase : public TimeoutManager, ...@@ -917,6 +921,10 @@ class EventBase : public TimeoutManager,
// to reduce spamminess // to reduce spamminess
SmoothLoopTime maxLatencyLoopTime_; SmoothLoopTime maxLatencyLoopTime_;
// If true, max latency callbacks will use a dampened SmoothLoopTime, else
// they'll use the raw loop time.
bool dampenMaxLatency_ = true;
// callback called when latency limit is exceeded // callback called when latency limit is exceeded
Func maxLatencyCob_; Func maxLatencyCob_;
......
...@@ -60,6 +60,8 @@ REGISTER_TYPED_TEST_CASE_P( ...@@ -60,6 +60,8 @@ REGISTER_TYPED_TEST_CASE_P(
CallbackOrderTest, CallbackOrderTest,
AlwaysEnqueueCallbackOrderTest, AlwaysEnqueueCallbackOrderTest,
IdleTime, IdleTime,
MaxLatencyUndamped,
UnsetMaxLatencyUndamped,
ThisLoop, ThisLoop,
EventBaseThreadLoop, EventBaseThreadLoop,
EventBaseThreadName, EventBaseThreadName,
......
...@@ -1979,6 +1979,44 @@ TYPED_TEST_P(EventBaseTest, IdleTime) { ...@@ -1979,6 +1979,44 @@ TYPED_TEST_P(EventBaseTest, IdleTime) {
ASSERT_EQ(21, tos->getTimeouts()); ASSERT_EQ(21, tos->getTimeouts());
} }
TYPED_TEST_P(EventBaseTest, MaxLatencyUndamped) {
auto eventBasePtr = getEventBase<TypeParam>();
folly::EventBase& eb = *eventBasePtr;
int maxDurationViolations = 0;
eb.setMaxLatency(
std::chrono::milliseconds{1}, [&]() { maxDurationViolations++; }, false);
eb.runInLoop(
[&]() {
/* sleep override */ std::this_thread::sleep_for(
std::chrono::microseconds{1001});
eb.terminateLoopSoon();
},
true);
eb.loop();
ASSERT_EQ(maxDurationViolations, 1);
}
TYPED_TEST_P(EventBaseTest, UnsetMaxLatencyUndamped) {
auto eventBasePtr = getEventBase<TypeParam>();
folly::EventBase& eb = *eventBasePtr;
int maxDurationViolations = 0;
eb.setMaxLatency(
std::chrono::milliseconds{1}, [&]() { maxDurationViolations++; }, false);
// Immediately unset it and make sure the counter isn't incremented. If the
// function gets called, this will raise an std::bad_function_call.
std::function<void()> bad_func = nullptr;
eb.setMaxLatency(std::chrono::milliseconds{0}, bad_func, false);
eb.runInLoop(
[&]() {
/* sleep override */ std::this_thread::sleep_for(
std::chrono::microseconds{1001});
eb.terminateLoopSoon();
},
true);
eb.loop();
ASSERT_EQ(maxDurationViolations, 0);
}
/** /**
* Test that thisLoop functionality works with terminateLoopSoon * Test that thisLoop functionality works with terminateLoopSoon
*/ */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment