Make topic monitor compatible with tsan

tsan does funny things to signals, preventing signals from being delivered
in a blocking read. Switch the topic monitor to non-blocking reads under
tsan.
This commit is contained in:
ridiculousfish 2019-05-04 12:41:15 -07:00
parent 0784b76570
commit 0dd9f64bd9

View file

@ -6,6 +6,17 @@
#include <unistd.h> #include <unistd.h>
// Whoof. Thread Sanitizer swallows signals and replays them at its leisure, at the point where
// instrumented code makes certain blocking calls. But tsan cannot interrupt a signal call, so
// if we're blocked in read() (like the topic monitor wants to be!), we'll never receive SIGCHLD
// and so deadlock. So if tsan is enabled, we mark our fd as non-blocking (so reads will never
// block) and use use select() to poll it.
#if defined(__has_feature)
#if __has_feature(thread_sanitizer)
#define TOPIC_MONITOR_TSAN_WORKAROUND 1
#endif
#endif
/// Implementation of the principal monitor. This uses new (and leaks) to avoid registering a /// Implementation of the principal monitor. This uses new (and leaks) to avoid registering a
/// pointless at-exit handler for the dtor. /// pointless at-exit handler for the dtor.
static topic_monitor_t *const s_principal = new topic_monitor_t(); static topic_monitor_t *const s_principal = new topic_monitor_t();
@ -25,6 +36,10 @@ topic_monitor_t::topic_monitor_t() {
// Make sure that our write side doesn't block, else we risk hanging in a signal handler. // Make sure that our write side doesn't block, else we risk hanging in a signal handler.
// The read end must block to avoid spinning in await. // The read end must block to avoid spinning in await.
DIE_ON_FAILURE(make_fd_nonblocking(pipes_.write.fd())); DIE_ON_FAILURE(make_fd_nonblocking(pipes_.write.fd()));
#if TOPIC_MONITOR_TSAN_WORKAROUND
DIE_ON_FAILURE(make_fd_nonblocking(pipes_.read.fd()));
#endif
} }
topic_monitor_t::~topic_monitor_t() = default; topic_monitor_t::~topic_monitor_t() = default;
@ -74,8 +89,18 @@ void topic_monitor_t::await_metagen(generation_t mgen) {
// Read until the metagen changes. It may already have changed. // Read until the metagen changes. It may already have changed.
// Note because changes are coalesced, we can read a lot, potentially draining the pipe. // Note because changes are coalesced, we can read a lot, potentially draining the pipe.
while (mgen == current_metagen()) { while (mgen == current_metagen()) {
int fd = pipes_.read.fd();
#if TOPIC_MONITOR_TSAN_WORKAROUND
// Under tsan our notifying pipe is non-blocking, so we would busy-loop on the read() call
// until data is available (that is, fish would use 100% cpu while waiting for processes).
// The select prevents that.
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
(void)select(fd + 1, &fds, nullptr, nullptr, nullptr /* timeout */);
#endif
uint8_t ignored[PIPE_BUF]; uint8_t ignored[PIPE_BUF];
(void)read(pipes_.read.fd(), ignored, sizeof ignored); (void)read(fd, ignored, sizeof ignored);
} }
// Release the lock and wake up the remaining waiters. // Release the lock and wake up the remaining waiters.