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",