From f56b17cfae0d6dbfc2ac21ac09fa4d8623ac7c4d Mon Sep 17 00:00:00 2001 From: Roger Zanoni Date: Fri, 10 Jul 2026 14:47:51 -0300 Subject: [PATCH] aosp: wrap alarm() and make it use a thread-directed timer Bionic's implementation of alarm triggers a setitimer set with ITIMER_REAL making it a process-directed signal. From the signal(7) manpage: A process-directed signal may be delivered to any one of the threads that does not currently have the signal blocked. If more than one of the threads has the signal unblocked, then the kernel chooses an arbitrary thread to which to deliver the signal. Android's ART runtime/runtime.cc configures only a small set of signals: void Runtime::BlockSignals() { SignalSet signals; signals.Add(SIGPIPE); signals.Add(SIGQUIT); signals.Add(SIGUSR1); signals.Block(); } SIGALRM is unblocked on the ART's main thread and it may catch the process-directed signal, never letting it reach cobalt/nplb calling threads, making it unreliable to wait for the signal using musl's implementation. Fixes PosixClockNanosleepTest.ErrorEintrAbsoluteSleep, PosixClockNanosleepTest.ErrorEintrRelativeSleep and PosixNanosleepTests.ErrorEintr which were failing with a "signal never interrupted sleep" error. Bug: 532068409 --- starboard/android/shared/BUILD.gn | 1 + starboard/android/shared/posix_emu/alarm.cc | 85 +++++++++++++++++++ .../shared/platform_configuration/BUILD.gn | 1 + 3 files changed, 87 insertions(+) create mode 100644 starboard/android/shared/posix_emu/alarm.cc diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn index d47a0ef66939..e94316caee7a 100644 --- a/starboard/android/shared/BUILD.gn +++ b/starboard/android/shared/BUILD.gn @@ -347,6 +347,7 @@ static_library("starboard_platform") { "asset_manager.cc", "asset_manager.h", "posix_emu/access.cc", + "posix_emu/alarm.cc", "posix_emu/errno.cc", "posix_emu/file.cc", "posix_emu/net_if.cc", diff --git a/starboard/android/shared/posix_emu/alarm.cc b/starboard/android/shared/posix_emu/alarm.cc new file mode 100644 index 000000000000..880db4ddd045 --- /dev/null +++ b/starboard/android/shared/posix_emu/alarm.cc @@ -0,0 +1,85 @@ +// Copyright 2026 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "starboard/common/log.h" + +namespace { + +// A one-shot POSIX timer used to implement a thread-directed alarm() +struct ThreadAlarmTimer { + timer_t timer = nullptr; + + ~ThreadAlarmTimer() { + if (timer) { + timer_delete(timer); + } + } +}; + +thread_local ThreadAlarmTimer g_thread_alarm_timer; + +} // namespace + +extern "C" { + +unsigned int __real_alarm(unsigned int seconds); + +// alarm() on Android/ART calls setitimer set with ITIMER_REAL, which raises a +// process-directed SIGALRM. signal() may deliver process-directed signals to +// any of the process threads that has SIGALRM unblocked, and the kernel decides +// which is going to be the receiving thread. So on Android, waiting for SIGALRM +// isn't reliable and to circunvent this issue a per-thread POSIX timer is set +// with SIGEV_THREAD_ID, which sets up the thread that started the timer as the +// receiver, not letting the signal go to unblocked receivers like the ART +// signal catcher. + +unsigned int __wrap_alarm(unsigned int seconds) { + ThreadAlarmTimer& thread_timer = g_thread_alarm_timer; + if (thread_timer.timer == nullptr) { + if (seconds == 0) { + // No timer to create yet; just cancel any existing process-level alarm. + return __real_alarm(0); + } + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_signo = SIGALRM; + sev.sigev_notify_thread_id = gettid(); + if (timer_create(CLOCK_REALTIME, &sev, &thread_timer.timer) != 0) { + SB_LOG(WARNING) << "alarm(): timer_create failed (" << strerror(errno) + << "), falling back to the process-level alarm"; + return __real_alarm(seconds); + } + } + + struct itimerspec new_value = {}; + new_value.it_value.tv_sec = seconds; + struct itimerspec old_value = {}; + if (timer_settime(thread_timer.timer, 0, &new_value, &old_value) != 0) { + SB_LOG(WARNING) << "alarm(): timer_settime failed (" << strerror(errno) + << "), falling back to the process-level alarm"; + return __real_alarm(seconds); + } + + // matches musl's implementation return logic, but using + // nsec instead of usec + return old_value.it_value.tv_sec + !!old_value.it_value.tv_nsec; +} + +} // extern "C" diff --git a/starboard/aosp/shared/platform_configuration/BUILD.gn b/starboard/aosp/shared/platform_configuration/BUILD.gn index 3d1c29dc06c6..671ccb476173 100644 --- a/starboard/aosp/shared/platform_configuration/BUILD.gn +++ b/starboard/aosp/shared/platform_configuration/BUILD.gn @@ -21,6 +21,7 @@ config("platform_configuration") { if (current_toolchain == starboard_toolchain) { ldflags = [ "-Wl,--wrap=access", + "-Wl,--wrap=alarm", "-Wl,--wrap=close", "-Wl,--wrap=fstatat", "-Wl,--wrap=if_indextoname",