Recently we were debugging a Ruby crash by looking at a core dump and
realized that there was no crash, it was another process sending a
SIGABRT signal to the process. There was no differentiation in the crash
report for this case. Now, when another process sends a SIGABRT, SIGSEGV,
etc to a process it shows the PID of the sender. This way we can know right
away that it was not a real crash.
The crash message now looks like this:
```
[BUG] Aborted at 0x00000001831c150c (sent by pid 53859)
```
If "(sent by pid XXX)" or "(sent by another process)" is included in the crash
message, the signal was sent by another process.
Make the VM-global GC roots per-Ractor so the main Ractor's ordinary
local GC runs lock-free. Add the shareability and objspace-locality
checks to the write barrier, and split the VM-wide tables behind
finer-grained locks.
Co-authored-by: Claude <noreply@anthropic.com>
When VM state is corrupted enough, we can call abort() from the SIGABRT
handler. Previously, we would spam until the stack is full:
ABRT received in SEGV handler
SEGV received in ABRT handler
ABRT received in SEGV handler
ABRT received in ABRT handler
ABRT received in ABRT handler
ABRT received in ABRT handler
[...]
We've seen this on CI:
https://github.com/ruby/ruby/actions/runs/26591192708/job/78350130653
To test this situation locally, temporarily patch in a call to abort()
in rb_bug_for_fatal_signal() then use `Process.kill(:ABRT, Process.pid)`.
Fix by restoring the default signal handler before aborting.
SIGABRT is for abnormal termination so we should handle it to output a bug
report. Specifically, glibc malloc uses it to exit when there is corruption.
For example, the following script produces a double free:
mem = Fiddle.malloc(10)
Fiddle.free(mem)
Fiddle.free(mem)
Before this patch, it just outputs the following and exits:
free(): double free detected in tcache 2
After this patch, it also outputs a bug report:
free(): double free detected in tcache 2
test.rb:11: [BUG] Aborted at 0x000003e8000ab65c
* Fix race condition in signal handler query
* Initialize signal lock dynamically and reset after fork
* Fix signal handler mutex initialization conditions
Rework ractors so that any ractor action (Ractor.receive, Ractor#send, Ractor.yield, Ractor#take,
Ractor.select) will operate on the thread that called the action. It will put that thread to sleep if
it's a blocking function and it needs to put it to sleep, and the awakening action (Ractor.yield,
Ractor#send) will wake up the blocked thread.
Before this change every blocking ractor action was associated with the ractor struct and its fields.
If a ractor called Ractor.receive, its wait status was wait_receiving, and when another ractor calls
r.send on it, it will look for that status in the ractor struct fields and wake it up. The problem was that
what if 2 threads call blocking ractor actions in the same ractor. Imagine if 1 thread has called Ractor.receive
and another r.take. Then, when a different ractor calls r.send on it, it doesn't know which ruby thread is associated
to which ractor action, so what ruby thread should it schedule? This change moves some fields onto the ruby thread
itself so that ruby threads are the ones that have ractor blocking statuses, and threads are then specifically scheduled
when unblocked.
Fixes [#17624]
Fixes [#21037]
On Linux, `siginfo_t` uses a union for each `si_code`, and the field
corresponding to `si_pid` does not belong to the `_sigfault` field for
SIGSEGV. It actually overlaps the `si_addr` field, which is usually
non-zero on stack overflow.
https://github.com/ruby/ruby/pull/10201#issuecomment-2034723244
Previously, when another process sends a fatal signals such as SIGBUS
to Ruby, we could mis-interpret it as a stack overflow Ruby itself
generated. When the si_pid field is set on the signal, we shouldn't
check the si_addr field to check for stack overflow.
> Signals sent with kill(2) and sigqueue(3) fill in si_pid and si_uid.
Co-authored-by: Alan Wu <alanwu@ruby-lang.org>
Previously the code assigns `handler` function pointer, which accepts
one argument, to `sigact.sa_sigaction`, which accepts three argument.
This mismatch is not allowed in Wasm.
I don't see the reason to use `sa_sigaction` here, so this change
assigns to `sa_handler`, which accepts one argument, in Wasm.
On Linux, while the signal handler runs, that signal is masked, so in
the rb_bug_for_fatal_signal() code path we didn't get the default signal
action as intended. See signal(7). It worked fine on macOS, though.
Before:
$ ./miniruby -e 'Process.kill :SIGSEGV, Process.pid'
<snip>
Aborted (core dumped)
After:
$ ./miniruby -e 'Process.kill :SIGSEGV, Process.pid'
<snip>
Segmentation fault (core dumped)
Follow-up for 1ac0afab4d "rb_bug_for_fatal_signal: exit with the right
signal".
Let signal.c include "internal/error.h" explicitly to ensure that the
identifier rb_sys_fail_str in signal.c refers to the macro defined in
"internal/error.h" instead of the actual function.
That macro reads errno before evaluating its argument. Without this
change, the rb_signo2signm(sig) expression in the "trap" function in
signal.c will overwrite the errno before the actual rb_sys_fail_str
function reads the errno.
* Revert "Remove special handling of `SIGCHLD`. (#7482)"
This reverts commit 44a0711eab.
* Revert "Remove prototypes for functions that are no longer used. (#7497)"
This reverts commit 4dce12bead.
* Revert "Remove SIGCHLD `waidpid`. (#7476)"
This reverts commit 1658e7d966.
* Fix change to rjit variable name.
The altstack memory of a thread may be free'ed even after the VM is
destructed. After that, GC is no longer available, so calling xfree
may lead to a segfault.
This changeset uses the bare free function to free the altstack memory
instead of xfree. [Bug #18126]
iff means if and only if, but readers without that knowledge might
assume this to be a spelling mistake. To me, this seems like
exclusionary language that is unnecessary. Simply using "if and only if"
instead should suffice.