Skip to content

feat(libc): implement alarm - #2765

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
TheAggie:feat/libc-alarm
Oct 10, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
TheAggie:feat/libc-alarm

Conversation

@TheAggie

Copy link
Copy Markdown
Contributor

What

RetroArch (PPSA99169) stops at alarm on current main:

FAIL: unresolved ELF import DQXJraCc1rA from libSceLibcInternal.prx

This implements it, following FreeBSD.

  • alarm(seconds) arms one process-wide real-time alarm and returns the seconds left on the previous one. Any partial second counts, as in FreeBSD's alarm.c, which adds one when it_value.tv_usec is non-zero. alarm(0) cancels the pending alarm.
  • When the alarm expires, SIGALRM's disposition as recorded by sigaction/signal applies:
    • ignored: nothing happens;
    • default: the process terminates, as on FreeBSD (exit status 128 + 14, with a line on stderr);
    • a guest handler: it throws alarm: delivering SIGALRM to a guest handler is not implemented, because the signal layer has no asynchronous delivery to a guest handler yet.
  • The timer and the disposition lookup are in libkernel next to the signal dispositions (GuestAlarm_nid_no_patch). libSceLibcInternal, which titles import alarm from, forwards to it.

Tested

Windows 11 Pro (26200), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release.

  • guest_signal_tests: alarm(0) with nothing pending returns 0; arming 5 and then re-arming 3 returns 5; cancelling returns 3, then 0. With SIGALRM ignored, a 1-second alarm expires during a 1.5 s sleep without ending the process, and nothing is left pending.
  • RetroArch, relinked against main cf8b129: on main it stops at the line above. With this branch it gets past alarm and stops at FAIL: unresolved ELF import E4wZaG1zSFc from libSceLibcInternal.prx (dirname, closed feat(libc): implement dirname #1761).
  • Based on main cf8b129. Full suite: 550 of 554 pass, 1 skipped. agc_driver_prepared_shaders_debug_failure and guest_memory fail the same way on clean main on this machine. agc_driver_prepared_shaders failed under the parallel run and passes alone (twice). It was run from the generated CTestTestfile.cmake commands because antivirus blocks ctest.exe on this machine.
  • tools/check_conventions.py passes.

The default-disposition and guest-handler paths end the process, so they have no in-process test. Not tested on Linux or macOS.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Progress report (181420f → e5ae8df)

➖ System libraries: 85.66% (+0.0%, +1 implemented functions)

✅ 1 implemented
Library Name
libSceLibcInternal alarm_nid_postfix

@TheAggie

Copy link
Copy Markdown
Contributor Author

A second title stops at alarm on main. I triaged the whole homebrew catalog on main cf8b129 (Windows 11, MinGW-w64 GCC 15.2.0; each release ZIP checked against its catalog sha256, relinked with relinker --windows --registry, run for 12 s):

Title main with this PR
RetroArch (PPSA99169) DQXJraCc1rA (alarm) E4wZaG1zSFc (dirname)
Porpoise (PPSA99764) DQXJraCc1rA (alarm) E4wZaG1zSFc (dirname)

The "with this PR" column comes from one build of main with this PR plus #1872, #2478 and #2024, which add unrelated imports (pthread_set_name_np, dup/dup2, utime).

AI-assisted (Claude Code).

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, alarm is a real next stop for RetroArch/Porpoise. One blocker, found on Linux:

  • guest_signals hangs on Linux (killed after 455 s by ctest); on main it passes. All checks finish, then the process never exits. alarmLock/alarmChanged are function-local-namespace statics, and RunAlarms is a detached thread parked in alarmChanged.wait(lock) (core/libs/prx/libkernel/System/src/Signals.cpp:109). At exit the static std::condition_variable is destroyed with a waiter still on it, and glibc's pthread_cond_destroy blocks forever. A 10-line program with a detached thread waiting on a global condition variable reproduces it. MinGW doesn't block there, which is why it passed on your machine. This would also hang any guest process that calls alarm and then exits normally.
    Fix: don't destroy the state at exit (e.g. heap-allocate the mutex/condition variable/deadline and leak them), or make the thread stoppable and joined; then rerun guest_signals on Linux.
  • Once rebased: #3047 also edits the includes of Signals.cpp (lines 7-8); whichever lands second needs a trivial rebase.

The rest matches FreeBSD's alarm.c (round-up remaining seconds, alarm(0) cancels) and the new test pins those values.

@babyhuey

Copy link
Copy Markdown
Contributor

Linux check, since this was only run on Windows: 018097e merged onto main 262c031 (clean), Ubuntu 24.04, GCC 13, Release.

guest_signals hangs on Linux. On main the same binary exits 0 at once; with this PR it never exits (killed after 45 s with SIGKILL; plain timeout can't stop it because the test ignores signal 15). A marker printed at the end of main() does show up, so every check passes and the hang is at process exit. At that point both threads sit in futex_do_wait: the main thread and the detached RunAlarms thread.

That fits static destruction: alarmChanged and alarmLock are namespace-scope statics, and glibc's pthread_cond_destroy blocks while the detached thread is still a waiter on the condition variable. A title that calls alarm and then returns from main would hang the same way on Linux. Keeping the alarm state alive for the whole process should avoid it, for example putting the mutex, condition variable and deadline in a struct allocated once with new and never destroyed, or having RunAlarms take no lock that is destroyed at exit.

TheAggie and others added 2 commits October 10, 2026 17:59
RetroArch (PPSA99169) stops at alarm on main: `FAIL: unresolved ELF import DQXJraCc1rA from libSceLibcInternal.prx`.

alarm(seconds) arms one process-wide real-time alarm and returns the seconds left on the previous one, rounded up like FreeBSD's alarm.c (any partial second counts); alarm(0) cancels it. When it expires, SIGALRM's disposition as recorded by sigaction or signal applies: ignored, nothing happens; default, the process terminates as on FreeBSD (exit status 128 + 14); a guest handler throws, because asynchronous delivery to a guest handler is not implemented.

The timer lives in libkernel next to the signal dispositions (GuestAlarm_nid_no_patch); libSceLibcInternal, which the titles import alarm from, forwards to it.

AI-assisted (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The detached alarm thread waits on a condition variable that was a
namespace-scope static. At exit its destructor ran with the thread still
waiting, and glibc's pthread_cond_destroy blocks then, so a process that
called alarm never exited on Linux. The mutex, condition variable, deadline
and started flag now live in one structure allocated once and never
destroyed.

AI-assisted (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#3047 core/libs/prx/libkernel/System/src/Signals.cpp L7-8

@TheAggie

Copy link
Copy Markdown
Contributor Author

Thanks @adriadam10 and @babyhuey for running it on Linux. Fixed in e5ae8df:

  • The mutex, condition variable, deadline and started flag now live in one AlarmState allocated once with new and never destroyed.
  • So nothing the detached RunAlarms thread waits on is torn down at exit.

I also rebased onto current main (no conflicts; #3047 hasn't landed yet, so the include change will be a one-line rebase whichever goes second).

guest_signals passes on Windows. I don't have a Linux machine here to confirm the exit hang is gone. Could one of you rerun it there?

AI-assisted (Claude Code).

@babyhuey

Copy link
Copy Markdown
Contributor

Confirmed on Linux: e5ae8df merged onto main ad33692 (clean), Ubuntu 24.04, GCC 13, Release. guest_signals now exits 0 in 1 to 2 s on each of five runs, and passes 5/5 under ctest --repeat until-fail:5. Porpoise (PPSA99764) gets past alarm and stops at undefined symbol: 2eQpqTjJ5Y4 (asinh).

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked on Linux: build and guest_signals pass on the merge with main (and fail to link without the change); the exit hang is gone, also confirmed by @babyhuey on Ubuntu. alarm semantics match FreeBSD alarm.c (rounding up partial seconds, 0 cancels), and the unmodelled guest-handler path throws. Merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants