blob: a802166552281cf18b6d136b235c162bd93f18db [file]
// Copyright 2026 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/mac/workgroup.h"
#include <errno.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <os/object.h>
#include <os/workgroup.h>
#include <stdint.h>
#include <algorithm>
#include <atomic>
#include <memory>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/synchronization/lock.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_local_storage.h"
#include "build/build_config.h"
namespace base::mac {
BASE_FEATURE(kMacWorkgroupInterval, FEATURE_DISABLED_BY_DEFAULT);
namespace {
constexpr bool IsArchitectureSupported() {
#if defined(ARCH_CPU_ARM64)
return true;
#else
return false;
#endif
}
// os_workgroup_interval_create is libdispatch SPI exported since macOS 11.0
// without requiring entitlements. Bind it via weak_import so Chrome degrades
// cleanly if a future OS removes the symbol.
extern "C" os_workgroup_interval_t _Nullable os_workgroup_interval_create(
const char* _Nullable name,
os_clockid_t clock,
os_workgroup_attr_t _Nullable attr) __attribute__((weak_import));
enum class JoinOwner {
kNone,
kSynced,
kExplicit,
kIntervalOwner,
};
// Invariant: static_cast<bool>(workgroup) == (owner != JoinOwner::kNone).
struct ThreadWorkgroupTlsState {
Workgroup workgroup;
os_workgroup_join_token_s token{};
JoinOwner owner = JoinOwner::kNone;
};
struct ForegroundWorkgroupGlobal {
Lock lock;
Workgroup configured_workgroup GUARDED_BY(lock);
bool is_enabled GUARDED_BY(lock) = true;
};
ForegroundWorkgroupGlobal& GetForegroundWorkgroupGlobal() {
static NoDestructor<ForegroundWorkgroupGlobal> instance;
return *instance;
}
} // namespace
struct Workgroup::TlsImpl {
static ThreadLocalStorage::Slot& GetSlot() {
// Intentionally never destroyed so threads exiting during process shutdown
// still run OnThreadExit without racing slot teardown.
static NoDestructor<ThreadLocalStorage::Slot> slot(&TlsImpl::OnThreadExit);
return *slot;
}
static ThreadWorkgroupTlsState* GetState() {
return static_cast<ThreadWorkgroupTlsState*>(GetSlot().Get());
}
static ThreadWorkgroupTlsState* GetOrCreateState() {
auto* state = GetState();
if (!state) {
state = new ThreadWorkgroupTlsState();
GetSlot().Set(state);
}
return state;
}
static void LeaveState(ThreadWorkgroupTlsState* state) {
DCHECK(state->workgroup);
DCHECK_NE(state->owner, JoinOwner::kNone);
os_workgroup_leave(static_cast<os_workgroup_t>(state->workgroup.workgroup_),
&state->token);
state->token = {};
state->owner = JoinOwner::kNone;
state->workgroup = {};
}
static void OnThreadExit(void* raw_tls_state) {
auto* state = static_cast<ThreadWorkgroupTlsState*>(raw_tls_state);
if (!state) {
return;
}
if (state->workgroup) {
LeaveState(state);
}
delete state;
}
static bool JoinNew(ThreadWorkgroupTlsState* state,
Workgroup workgroup,
JoinOwner owner) {
DCHECK(workgroup);
DCHECK_NE(owner, JoinOwner::kNone);
DCHECK(!state || (!state->workgroup && state->owner == JoinOwner::kNone));
os_workgroup_join_token_s token{};
const int rv = os_workgroup_join(
static_cast<os_workgroup_t>(workgroup.workgroup_), &token);
if (rv != 0) {
// EALREADY means a system framework (e.g. CoreAudio) already joined this
// thread to another workgroup; leave that membership untouched.
DLOG_IF(WARNING, rv != EALREADY) << "os_workgroup_join failed: " << rv;
return false;
}
if (!state) {
state = GetOrCreateState();
}
state->token = token;
state->workgroup = std::move(workgroup);
state->owner = owner;
return true;
}
static bool Join(Workgroup workgroup, JoinOwner new_owner) {
DCHECK(new_owner == JoinOwner::kSynced ||
new_owner == JoinOwner::kExplicit);
auto* state = GetState();
if (state && state->owner == JoinOwner::kIntervalOwner) {
return workgroup && state->workgroup.SameBackingAs(workgroup);
}
if (!workgroup) {
if (state && state->workgroup) {
LeaveState(state);
}
return true;
}
if (state && state->workgroup) {
if (state->workgroup.SameBackingAs(workgroup)) {
if (new_owner == JoinOwner::kExplicit) {
state->owner = JoinOwner::kExplicit;
}
return true;
}
// Leave the prior Workgroup before joining `workgroup`. Do not try to
// re-join the old handle if the new join fails: EALREADY from external
// frameworks cannot occur once our own membership has left, so failure
// means the replacement workgroup itself is invalid/cancelled and
// falling back to the task's default thread group is preferable.
LeaveState(state);
}
return JoinNew(state, std::move(workgroup), new_owner);
}
static bool JoinForInterval(Workgroup workgroup) {
DCHECK(workgroup);
auto* state = GetState();
if (state && state->owner == JoinOwner::kIntervalOwner) {
return false;
}
if (state && state->workgroup) {
LeaveState(state);
}
return JoinNew(state, std::move(workgroup), JoinOwner::kIntervalOwner);
}
static void Leave() {
auto* state = GetState();
if (state && (state->owner == JoinOwner::kSynced ||
state->owner == JoinOwner::kExplicit)) {
LeaveState(state);
}
}
static void LeaveForInterval(const Workgroup& workgroup) {
auto* state = GetState();
CHECK(state && state->workgroup == workgroup &&
state->owner == JoinOwner::kIntervalOwner);
LeaveState(state);
}
static void Sync(Workgroup target_workgroup) {
auto* state = GetState();
if (state && (state->owner == JoinOwner::kExplicit ||
state->owner == JoinOwner::kIntervalOwner)) {
return;
}
const ThreadType thread_type = PlatformThread::GetCurrentThreadType();
if (target_workgroup && ShouldJoinProcessForegroundWorkgroup(thread_type)) {
Join(std::move(target_workgroup), JoinOwner::kSynced);
} else if (state && state->owner == JoinOwner::kSynced) {
LeaveState(state);
}
}
};
struct ProcessSync {
static bool SameBacking(const Workgroup& a, const Workgroup& b) {
return a.SameBackingAs(b);
}
static void SyncCurrentThread(Workgroup target_workgroup) {
Workgroup::TlsImpl::Sync(std::move(target_workgroup));
}
};
namespace internal {
std::atomic<uint32_t> g_foreground_workgroup_generation{0};
void SyncCurrentThreadToForegroundWorkgroupSlow(uint32_t& local_generation) {
Workgroup target_workgroup;
uint32_t observed_generation = 0;
{
auto& global = GetForegroundWorkgroupGlobal();
AutoLock lock(global.lock);
if (global.is_enabled) {
target_workgroup = global.configured_workgroup;
}
observed_generation =
g_foreground_workgroup_generation.load(std::memory_order_relaxed);
}
ProcessSync::SyncCurrentThread(std::move(target_workgroup));
local_generation = observed_generation;
}
} // namespace internal
Workgroup::Workgroup(void* workgroup, mach_port_t port)
: workgroup_(workgroup), port_(port) {
DCHECK(workgroup_);
}
Workgroup::Workgroup(const Workgroup& other)
: workgroup_(other.workgroup_ ? os_retain(other.workgroup_) : nullptr),
port_(other.port_) {}
Workgroup::Workgroup(Workgroup&& other) noexcept
: workgroup_(std::exchange(other.workgroup_, nullptr)),
port_(std::exchange(other.port_, MACH_PORT_NULL)) {}
Workgroup& Workgroup::operator=(Workgroup other) noexcept {
std::swap(workgroup_, other.workgroup_);
std::swap(port_, other.port_);
return *this;
}
Workgroup::~Workgroup() {
if (workgroup_) {
os_release(workgroup_);
}
}
// static
Workgroup Workgroup::CreateFromPort(const char* name, mach_port_t port) {
CHECK(name);
if (!IsArchitectureSupported() || port == MACH_PORT_NULL ||
!MACH_PORT_VALID(port) ||
!FeatureList::IsEnabled(kMacWorkgroupInterval)) {
return Workgroup();
}
os_workgroup_t wg = os_workgroup_create_with_port(name, port);
return wg ? Workgroup(wg, port) : Workgroup();
}
apple::ScopedMachSendRight Workgroup::CopyPort() const {
if (!workgroup_) {
return apple::ScopedMachSendRight();
}
mach_port_t port = MACH_PORT_NULL;
const int rv =
os_workgroup_copy_port(static_cast<os_workgroup_t>(workgroup_), &port);
if (rv != 0 || port == MACH_PORT_NULL) {
return apple::ScopedMachSendRight();
}
return apple::ScopedMachSendRight(port);
}
// static
bool Workgroup::JoinCurrentThread(Workgroup workgroup) {
return TlsImpl::Join(std::move(workgroup), JoinOwner::kExplicit);
}
// static
void Workgroup::LeaveCurrentThread() {
TlsImpl::Leave();
}
// static
Workgroup Workgroup::GetCurrentThreadWorkgroup() {
auto* state = TlsImpl::GetState();
return state ? state->workgroup : Workgroup();
}
void SetProcessForegroundWorkgroup(Workgroup workgroup) {
if (!IsArchitectureSupported()) {
return;
}
Workgroup target_workgroup;
{
auto& global = GetForegroundWorkgroupGlobal();
AutoLock lock(global.lock);
if (ProcessSync::SameBacking(global.configured_workgroup, workgroup)) {
return;
}
global.configured_workgroup = std::move(workgroup);
if (!global.is_enabled) {
return;
}
target_workgroup = global.configured_workgroup;
internal::g_foreground_workgroup_generation.fetch_add(
1, std::memory_order_release);
}
ProcessSync::SyncCurrentThread(std::move(target_workgroup));
}
void SetProcessForegroundWorkgroupEnabled(bool enabled) {
if (!IsArchitectureSupported() ||
!FeatureList::IsEnabled(kMacWorkgroupInterval)) {
return;
}
Workgroup target_workgroup;
{
auto& global = GetForegroundWorkgroupGlobal();
AutoLock lock(global.lock);
if (global.is_enabled == enabled) {
return;
}
global.is_enabled = enabled;
if (!global.configured_workgroup) {
return;
}
if (enabled) {
target_workgroup = global.configured_workgroup;
}
internal::g_foreground_workgroup_generation.fetch_add(
1, std::memory_order_release);
}
ProcessSync::SyncCurrentThread(std::move(target_workgroup));
}
void ResetProcessForegroundWorkgroupForTesting() {
{
auto& global = GetForegroundWorkgroupGlobal();
AutoLock lock(global.lock);
global.configured_workgroup = {};
global.is_enabled = true;
internal::g_foreground_workgroup_generation.fetch_add(
1, std::memory_order_release);
}
Workgroup::LeaveCurrentThread();
}
// static
bool WorkgroupInterval::IsSupported() {
return IsArchitectureSupported() && os_workgroup_interval_create != nullptr;
}
// static
std::unique_ptr<WorkgroupInterval> WorkgroupInterval::Create(const char* name) {
CHECK(name);
if (!IsSupported() || !FeatureList::IsEnabled(kMacWorkgroupInterval)) {
return nullptr;
}
os_workgroup_interval_t wgi =
os_workgroup_interval_create(name, OS_CLOCK_MACH_ABSOLUTE_TIME, nullptr);
if (!wgi) {
return nullptr;
}
Workgroup workgroup(wgi, MACH_PORT_NULL);
if (!Workgroup::TlsImpl::JoinForInterval(workgroup)) {
return nullptr;
}
return WrapUnique(new WorkgroupInterval(std::move(workgroup)));
}
WorkgroupInterval::WorkgroupInterval(Workgroup workgroup)
: workgroup_(std::move(workgroup)) {
DCHECK(workgroup_);
}
WorkgroupInterval::~WorkgroupInterval() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
Finish();
if (is_started_) {
// Leak one ref on shutdown rather than crashing the process via os_crash.
os_retain(workgroup_.workgroup_);
}
Workgroup::TlsImpl::LeaveForInterval(workgroup_);
}
bool WorkgroupInterval::Start(TimeTicks start, TimeTicks deadline) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(Workgroup::GetCurrentThreadWorkgroup() == workgroup_);
DCHECK(!start.is_null());
DCHECK(!deadline.is_null());
DCHECK_LE(start, deadline);
if (is_started_ && !Finish()) {
return false;
}
const uint64_t now_mach = mach_absolute_time();
const uint64_t start_mach = std::min(start.ToMachAbsoluteTime(), now_mach);
const uint64_t deadline_mach =
std::max(start_mach, deadline.ToMachAbsoluteTime());
const int rv = os_workgroup_interval_start(
static_cast<os_workgroup_interval_t>(workgroup_.workgroup_), start_mach,
deadline_mach, nullptr);
if (rv != 0) {
DLOG(WARNING) << "os_workgroup_interval_start failed: " << rv;
return false;
}
current_start_mach_ = start_mach;
is_started_ = true;
return true;
}
bool WorkgroupInterval::Update(TimeTicks deadline) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(Workgroup::GetCurrentThreadWorkgroup() == workgroup_);
DCHECK(!deadline.is_null());
if (!is_started_) {
return false;
}
const uint64_t deadline_mach =
std::max(current_start_mach_, deadline.ToMachAbsoluteTime());
const int rv = os_workgroup_interval_update(
static_cast<os_workgroup_interval_t>(workgroup_.workgroup_),
deadline_mach, nullptr);
if (rv != 0) {
DLOG(WARNING) << "os_workgroup_interval_update failed: " << rv;
return false;
}
return true;
}
bool WorkgroupInterval::Finish() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(Workgroup::GetCurrentThreadWorkgroup() == workgroup_);
if (!is_started_) {
return true;
}
const int rv = os_workgroup_interval_finish(
static_cast<os_workgroup_interval_t>(workgroup_.workgroup_), nullptr);
if (rv != 0) {
// libdispatch only clears its started bit when the kernel call succeeds.
DLOG(WARNING) << "os_workgroup_interval_finish failed: " << rv;
return false;
}
is_started_ = false;
current_start_mach_ = 0;
return true;
}
} // namespace base::mac