blob: 84b73bd041993e5e6474362288b5d3f5fa77fede [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 <sys/types.h>
#include <unistd.h>
#include <atomic>
#include <chrono>
#include <filesystem>
#include <format>
#include <fstream>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include "platform/api/task_runner.h"
#include "util/alarm.h"
#include "util/chrono_helpers.h"
#include "util/no_destructor.h"
#include "util/osp_logging.h"
#include "util/scoped_wake_lock.h"
#include "util/thread_annotations.h"
namespace openscreen {
namespace {
constexpr auto kLockTimeout = std::chrono::seconds(10);
// This mutex guards the reference count to ensure atomic transitions between
// 0 and 1 references, which correspond to acquiring and releasing the physical
// wake lock.
std::mutex g_wake_lock_mutex;
// Use atomic pointers to allow thread-safe overriding in tests without
// needing to lock the mutex just to read the paths or clock.
std::atomic<const char*> g_wake_lock_path{"/sys/power/wake_lock"};
std::atomic<const char*> g_wake_unlock_path{"/sys/power/wake_unlock"};
std::atomic<ClockNowFunctionPtr> g_clock_now{&Clock::now};
std::string_view GetLockName() {
// Initialized exactly once on the first call. Thread-safe by C++11/C++20
// standards.
static const NoDestructor<std::string> kLockName("openscreen.wakelock." +
std::to_string(getpid()));
return *kLockName;
}
void WriteToSysfs(const std::filesystem::path& path, std::string_view content) {
// std::ios::app ensures we append, mimicking O_APPEND
std::ofstream stream(path, std::ios::out | std::ios::app);
if (!stream) {
OSP_LOG_WARN << "Failed to open wake lock path: " << path;
return;
}
// Write the string view. std::ofstream handles partial writes and
// buffering automatically.
stream << content;
if (stream.bad()) {
OSP_LOG_WARN << "Failed to write to wake lock path: " << path;
}
}
void AcquireWakeLockLocked() OSP_EXCLUSIVE_LOCKS_REQUIRED(g_wake_lock_mutex) {
const std::string content =
std::format("{} {}", GetLockName(), to_nanoseconds(kLockTimeout).count());
WriteToSysfs(g_wake_lock_path.load(), content);
}
void ReleaseWakeLockLocked() OSP_EXCLUSIVE_LOCKS_REQUIRED(g_wake_lock_mutex) {
WriteToSysfs(g_wake_unlock_path.load(), GetLockName());
}
} // namespace
void SetWakeLockPathsForTesting(const char* lock_path,
const char* unlock_path) {
g_wake_lock_path.store(lock_path);
g_wake_unlock_path.store(unlock_path);
}
void SetWakeLockClockForTesting(ClockNowFunctionPtr clock_fn) {
g_clock_now.store(clock_fn);
}
class ScopedWakeLockLinux : public ScopedWakeLock {
public:
explicit ScopedWakeLockLinux(TaskRunner& task_runner);
~ScopedWakeLockLinux() override;
private:
TaskRunner& task_runner_;
Alarm heartbeat_alarm_;
static int reference_count_ OSP_GUARDED_BY(g_wake_lock_mutex);
void ScheduleHeartbeat();
};
int ScopedWakeLockLinux::reference_count_ = 0;
ScopedWakeLockPtr ScopedWakeLock::Create(TaskRunner& task_runner) {
return TaskRunnerDeleter::MakeUnique<ScopedWakeLockLinux>(task_runner,
task_runner);
}
ScopedWakeLockLinux::ScopedWakeLockLinux(TaskRunner& task_runner)
: task_runner_(task_runner),
heartbeat_alarm_(g_clock_now.load(), task_runner_) {
std::lock_guard<std::mutex> lock(g_wake_lock_mutex);
if (reference_count_++ == 0) {
AcquireWakeLockLocked();
}
ScheduleHeartbeat();
}
ScopedWakeLockLinux::~ScopedWakeLockLinux() {
std::lock_guard<std::mutex> lock(g_wake_lock_mutex);
if (--reference_count_ == 0) {
ReleaseWakeLockLocked();
}
}
// The Linux sysfs wake lock interface (/sys/power/wake_lock) does not
// automatically clean up locks when a process exits. To prevent leaking locks
// on crash, we acquire the lock with a short timeout (10 seconds) and run a
// heartbeat timer to periodically renew it.
//
// When renewing, we write to the wake lock file again. This resets the timeout
// in the kernel without needing to increment our logical reference count, as we
// are just maintaining the existing lock.
void ScopedWakeLockLinux::ScheduleHeartbeat() {
heartbeat_alarm_.ScheduleFromNow(
[this] {
{
std::lock_guard<std::mutex> lock(g_wake_lock_mutex);
AcquireWakeLockLocked();
}
ScheduleHeartbeat();
},
std::chrono::seconds(5));
}
} // namespace openscreen