blob: d1b7b238a6d77f6b23c35825fff95ef605967937 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/synchronization/waitable_event.h"
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/trace_event.h"
#include "base/tracing_buildflags.h"
namespace base {
WaitableEvent::~WaitableEvent() {
// As requested in the documentation of perfetto::Flow::FromPointer, we should
// emit a TerminatingFlow(this) from our destructor if we ever emitted a
// Flow(this) which may be unmatched since the ptr value of `this` may be
// reused after this destructor. This can happen if a signaled event is never
// waited upon (or isn't the one to satisfy a WaitMany condition).
if (!only_used_while_idle_) {
// Check the tracing state to avoid an unnecessary syscall on destruction
// (which can be performance sensitive, crbug.com/40275035).
static const uint8_t* flow_enabled =
TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("wakeup.flow,toplevel.flow");
if (*flow_enabled && IsSignaled()) {
TRACE_EVENT_INSTANT("wakeup.flow,toplevel.flow",
"~WaitableEvent while Signaled",
perfetto::TerminatingFlow::FromPointer(this));
}
}
}
void WaitableEvent::Signal() {
// Must be ordered before SignalImpl() to guarantee it's emitted before the
// matching TerminatingFlow in TimedWait().
if (!only_used_while_idle_) {
TRACE_EVENT_INSTANT("wakeup.flow,toplevel.flow", "WaitableEvent::Signal",
perfetto::Flow::FromPointer(this));
}
SignalImpl();
}
void WaitableEvent::Wait(const Location& location) {
const bool result = TimedWait(TimeDelta::Max(), location);
DCHECK(result) << "TimedWait() should never fail with infinite timeout";
}
bool WaitableEvent::TimedWait(TimeDelta wait_delta, const Location& location) {
if (wait_delta <= TimeDelta()) {
return IsSignaled();
}
// Consider this thread blocked unless the event is already signaled. Ignore
// this for non-blocking WaitableEvents.
std::optional<internal::ScopedBlockingCallWithBaseSyncPrimitives>
scoped_blocking_call;
if (!only_used_while_idle_) {
// Always verify thread restrictions to avoid fortuitous allowance if it's
// already signaled.
internal::AssertBaseSyncPrimitivesAllowed();
if (IsSignaled()) {
return true;
}
scoped_blocking_call.emplace(location, BlockingType::WILL_BLOCK);
}
const bool result = TimedWaitImpl(wait_delta);
if (result && !only_used_while_idle_) {
TRACE_EVENT_INSTANT("wakeup.flow,toplevel.flow",
"WaitableEvent::Wait Complete",
perfetto::TerminatingFlow::FromPointer(this));
}
return result;
}
size_t WaitableEvent::WaitMany(base::span<WaitableEvent*> events) {
DCHECK(!events.empty()) << "Cannot wait on no events";
internal::ScopedBlockingCallWithBaseSyncPrimitives scoped_blocking_call(
FROM_HERE, BlockingType::MAY_BLOCK);
const size_t signaled_id = WaitManyImpl(events);
WaitableEvent* const signaled_event = events[signaled_id];
if (!signaled_event->only_used_while_idle_) {
TRACE_EVENT_INSTANT("wakeup.flow,toplevel.flow",
"WaitableEvent::WaitMany Complete",
perfetto::TerminatingFlow::FromPointer(signaled_event));
}
return signaled_id;
}
OnceClosure WaitableEvent::GetWaitCallbackForTesting() {
return BindOnce(&WaitableEvent::Wait, Unretained(this), FROM_HERE);
}
} // namespace base