Repository navigation
event loop: tolerate backward monotonic clock jumps #369
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: master
Are you sure you want to change the base?
Changes from 1 commit
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 |
|---|---|---|
|
|
@@ -286,11 +286,39 @@ EventLoop::~EventLoop() | |
| // cleanup.wait(m_io_context.waitScope); | ||
| } | ||
|
|
||
| namespace { | ||
| #if CAPNP_VERSION < 1002000 | ||
| //! Ignore "can't advance backwards in time" errors from Cap'n Proto versions | ||
| //! before 1.2, which raise them when CLOCK_MONOTONIC briefly goes backwards | ||
| //! (https://github.com/capnproto/capnproto/issues/2261). Returning lets | ||
| //! TimerImpl::advanceTo() keep the previous time, like newer versions do. | ||
|
Comment on lines
+291
to
+294
Collaborator
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. In commit "event loop: tolerate backward monotonic clock jumps" (f5ce6f8) Note for reviewers: The comment above says that returning from the callback lets // Before 1.2 (non-Apple). When the callback returns, the { return; } block runs.
KJ_REQUIRE(newTime >= time, "can't advance backwards in time") { return; }
time = newTime;
for (;;) { /* fire timers with deadline <= time */ }
// 1.2 and later.
time = std::max(time, newTime);
for (;;) { /* fire timers with deadline <= time */ }When the clock goes backwards, both leave That doesn't matter in practice. Timers due at or before |
||
| class ClockErrorCallback : public kj::ExceptionCallback | ||
|
Collaborator
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. In commit "event loop: tolerate backward monotonic clock jumps" (f5ce6f8) Note: Not for this PR, but in case anyone wants to pick it up, I think the stack check that caused the ASan failure (comment above) could be fixed upstream in Cap'n Proto with a small patch, so The check is A fix that works regardless of how libkj was built would be to declare the public ASan functions from extern "C" {
void* __asan_get_current_fake_stack() __attribute__((weak));
void* __asan_addr_is_in_fake_stack(void* fake_stack, void* addr, void** beg, void** end) __attribute__((weak));
}
void requireOnStack(void* ptr, kj::StringPtr description) {
if (__asan_get_current_fake_stack && __asan_addr_is_in_fake_stack) {
void* fake_stack = __asan_get_current_fake_stack();
if (fake_stack && __asan_addr_is_in_fake_stack(fake_stack, ptr, nullptr, nullptr)) return;
}
...existing check...
}I tried this in a small stand-in for libkj built without ASan, with a program built with and without ASan, and with |
||
| { | ||
| public: | ||
| explicit ClockErrorCallback(EventLoop& loop) : m_loop(loop) {} | ||
| void onRecoverableException(kj::Exception&& exception) override | ||
| { | ||
| if (exception.getType() == kj::Exception::Type::FAILED && | ||
| exception.getDescription().endsWith("can't advance backwards in time")) { | ||
| MP_LOG(m_loop, Log::Warning) << "EventLoop: ignoring non-monotonic clock read."; | ||
| return; | ||
| } | ||
| next.onRecoverableException(kj::mv(exception)); | ||
| } | ||
| EventLoop& m_loop; | ||
| }; | ||
| #endif | ||
| } // namespace | ||
|
|
||
| void EventLoop::loop() | ||
| { | ||
| assert(!CurrentThread().loop_thread); | ||
| CurrentThread().loop_thread = true; | ||
| KJ_DEFER(CurrentThread().loop_thread = false); | ||
| #if CAPNP_VERSION < 1002000 | ||
| // Registered with KJ for this thread until loop() returns. | ||
| ClockErrorCallback clock_error_callback{*this}; | ||
| #endif | ||
|
Comment on lines
+318
to
+321
Collaborator
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. In commit "event loop: tolerate backward monotonic clock jumps" (f5ce6f8) Fix is needed here. The The There's no way to turn the check off from outside KJ, but it works to disable ASan instrumentation for diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h
index b226284..9e3bd29 100644
--- a/include/mp/proxy-io.h
+++ b/include/mp/proxy-io.h
@@ -266,6 +266,10 @@ public:
//! the m_num_refs reference count is 0.
void loop();
+ //! Implementation of loop(), separate so loop() can be excluded from ASan
+ //! instrumentation.
+ void loopImpl();
+
//! Run function on event loop thread. Does not return until function completes.
//! Must be called while the loop() function is active.
void sync(kj::FunctionParam<void()> fn);
diff --git a/src/mp/proxy.cpp b/src/mp/proxy.cpp
index 20ce5a7..cb4e475 100644
--- a/src/mp/proxy.cpp
+++ b/src/mp/proxy.cpp
@@ -310,6 +310,15 @@ public:
#endif
} // namespace
+// ASan is disabled for this function so clock_error_callback stays on the real
+// stack. KJ requires ExceptionCallback objects to be on the stack, but ASan's
+// detect_stack_use_after_return option moves local variables to a
+// heap-allocated fake stack, and KJ only skips its check when KJ itself is
+// built with ASan. The event loop code is in loopImpl(), which is still
+// instrumented.
+#if defined(__GNUC__) || defined(__clang__)
+__attribute__((no_sanitize("address")))
+#endif
void EventLoop::loop()
{
assert(!CurrentThread().loop_thread);
@@ -319,7 +328,11 @@ void EventLoop::loop()
// Registered with KJ for this thread until loop() returns.
ClockErrorCallback clock_error_callback{*this};
#endif
+ loopImpl();
+}
+void EventLoop::loopImpl()
+{
{
const Lock lock(m_mutex);
assert(!m_async_fns);I tested this by building with ASan against Cap'n Proto 1.2 with the version check forced on, so the callback is registered. With |
||
|
|
||
| { | ||
| const Lock lock(m_mutex); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "event loop: tolerate backward monotonic clock jumps" (f5ce6f8)
nit: I'd suggest dropping the
#if CAPNP_VERSION < 1002000around theClockErrorCallbackdefinition and keeping it only around the registration inloop(). That way the class still gets compiled with Cap'n Proto 1.2 and later, so if a refactoring breaks it, that shows up right away in every build instead of only later inolddepsor in builds against distro packages. The comment above the class could mention that it is only used with versions before 1.2, so it's still clear what to delete when the minimum version is raised.