Skip to content

signal: replace raise(SIGUSR1) with self-pipe to fix SIGABRT on glibc >= 2.42#658

Merged
jnovy merged 1 commit into
mainfrom
fix/self-pipe-signal-handler
May 18, 2026
Merged

signal: replace raise(SIGUSR1) with self-pipe to fix SIGABRT on glibc >= 2.42#658
jnovy merged 1 commit into
mainfrom
fix/self-pipe-signal-handler

Conversation

@jnovy
Copy link
Copy Markdown
Collaborator

@jnovy jnovy commented May 18, 2026

Problem

When on_sig_exit() cannot forward a signal (SIGTERM/SIGQUIT/SIGINT) to the container process because kill() fails, it needs to wake the GLib main loop. Previously this was done via raise(SIGUSR1).

On glibc >= 2.42, calling raise() from a signal handler while the main thread is in ppoll() triggers glibc's __syscall_cancel mechanism, causing conmon to abort with SIGABRT.

Fix

Replace raise(SIGUSR1) with the self-pipe trick — an async-signal-safe pattern:

  • Create a pipe at startup, register a GLib IO source on the read end
  • In the signal handler, write one byte to the pipe instead of calling raise()
  • The callback drains the pipe and triggers check_child_processes()

The self-pipe logic is extracted into a reusable module (src/self_pipe.h / src/self_pipe.c) with self_pipe_init(), self_pipe_wake(), and self_pipe_fini(). This makes it available for any future signal handler that needs to wake the main loop safely.

Fixes: #657

@packit-as-a-service
Copy link
Copy Markdown

Ephemeral COPR build failed. @containers/packit-build please check.

@jnovy jnovy added the jira label May 18, 2026
@jnovy jnovy requested a review from giuseppe May 18, 2026 10:10
@jnovy jnovy force-pushed the fix/self-pipe-signal-handler branch from 50a4d4f to 4c0e851 Compare May 18, 2026 10:11
Copy link
Copy Markdown
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

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

LGTM after the CI failure is addressed

… >= 2.42

When on_sig_exit() cannot forward a signal to the container process
(kill() fails), it needs to wake the GLib main loop to force a
child-process check.  Previously this was done via raise(SIGUSR1),
relying on the fact that SIGUSR1 is blocked and coalesced by signalfd.

On glibc >= 2.42, calling raise() from a signal handler while the main
thread is in ppoll() triggers glibc's __syscall_cancel mechanism:
ppoll() detects that a signal was raised for the current thread and
aborts with SIGABRT.

Fix: replace raise(SIGUSR1) with the self-pipe trick:
  - Create a pipe2(O_CLOEXEC | O_NONBLOCK) during startup.
  - Register a GLib IO source on the read end that drains bytes and
    triggers check_child_processes().
  - In on_sig_exit(), write one byte to the write end instead of
    calling raise(). This is async-signal-safe (POSIX section 2.4.3).
  - errno is preserved around the write() in self_pipe_wake().

Extract the logic into a reusable self_pipe.h/self_pipe.c module so
that any future signal handler can use self_pipe_wake() without
duplicating pipe setup code.  Add self_pipe_fini() for proper cleanup
on exit (g_source_remove + close both ends).

Fixes: #657

Signed-off-by: Jindrich Novy <jnovy@redhat.com>
@jnovy jnovy force-pushed the fix/self-pipe-signal-handler branch from 4c0e851 to bafb655 Compare May 18, 2026 10:19
@jnovy jnovy merged commit d367ff1 into main May 18, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

conmon 2.2.1 SIGABRT in __syscall_cancel during ppoll (94 crashes in 4 days, deterministic)

2 participants