1202 Commits

Author SHA1 Message Date
Jean Boussier
e42bcd7ce7 Rename fiber_serial into ec_serial
Since it now live in the EC.
2025-12-16 09:51:07 +01:00
Jean Boussier
28b195fc67 Store the fiber_serial in the EC to allow inlining
Mutexes spend a significant amount of time in `rb_fiber_serial`
because it can't be inlined (except with LTO).
The fiber struct is opaque the so function can't be defined as inlineable.

Ideally the while fiber struct would not be opaque to the rest of
Ruby core, but it's tricky to do.

Instead we can store the fiber serial in the execution context
itself, and make its access cheaper:

```
$ hyperfine './miniruby-baseline --yjit /tmp/mut.rb' './miniruby-inline-serial --yjit /tmp/mut.rb'
Benchmark 1: ./miniruby-baseline --yjit /tmp/mut.rb
  Time (mean ± σ):      4.011 s ±  0.084 s    [User: 3.977 s, System: 0.011 s]
  Range (min … max):    3.950 s …  4.245 s    10 runs

Benchmark 2: ./miniruby-inline-serial --yjit /tmp/mut.rb
  Time (mean ± σ):      3.495 s ±  0.150 s    [User: 3.448 s, System: 0.009 s]
  Range (min … max):    3.340 s …  3.869 s    10 runs

Summary
  ./miniruby-inline-serial --yjit /tmp/mut.rb ran
    1.15 ± 0.05 times faster than ./miniruby-baseline --yjit /tmp/mut.rb
```

```ruby
i = 10_000_000
mut = Mutex.new
while i > 0
  i -= 1
  mut.synchronize { }
  mut.synchronize { }
  mut.synchronize { }
  mut.synchronize { }
  mut.synchronize { }
  mut.synchronize { }
  mut.synchronize { }
  mut.synchronize { }
  mut.synchronize { }
  mut.synchronize { }
end
```
2025-12-16 09:51:07 +01:00
Luke Gruber
3add3db797
Fewer calls to GET_EC() and GET_THREAD() (#15506)
The changes are to `io.c` and `thread.c`.
I changed the API of 2 exported thread functions from `internal/thread.h` that
didn't look like they had any use in C extensions:

* rb_thread_wait_for_single_fd
* rb_thread_io_wait

I didn't change the following exported internal function because it's
used in C extensions:

* rb_thread_fd_select

I added a comment to note that this function, although internal, is used
in C extensions.
2025-12-12 14:47:43 -05:00
Samuel Williams
42f5654b69
Yield to scheduler if interrupts are pending. (#14700) 2025-12-06 21:44:14 +13:00
Keenan Brock
2b05785941 Allow rb_thread_call_with_gvl() to work when thread already has GVL
[Feature #20750]

Co-authored-by: Benoit Daloze <eregontp@gmail.com>
2025-12-05 20:50:00 +01:00
Sharon Rosner
5f6b31c23f
Correctly handle Process.fork with an active Fiber.scheduler. (#15385)
In the child process, nullify the current fiber scheduler and set the current fiber to blocking.
2025-12-05 18:20:39 +13:00
Samuel Williams
8eea9a5020
Nullify scheduler during terminate_atfork_i. (#15354) 2025-12-01 17:19:42 +13:00
S-H-GAMELINKS
3ebb5b9c78 Remove unneeded trailing semicolons 2025-11-27 16:30:16 +09:00
John Hawthorn
ff1d23eccb Use a serial to keep track of Mutex-owning Fiber
Previously this held a pointer to the Fiber itself, which requires
marking it (which was only implemented recently, prior to that it was
buggy). Using a monotonically increasing integer instead allows us to
avoid having a free function and keeps everything simpler.

My main motivations in making this change are that the root fiber lazily
allocates self, which makes the writebarrier implementation challenging
to do correctly, and wanting to avoid sending Mutexes to the remembered
set when locked by a short-lived Fiber.
2025-11-20 14:06:33 -08:00
Luke Gruber
16c6f36039
[DOC] Clarify Thread#kill documentation. (#15132)
Mention that it is asynchronous and that the killed thread can still run
a small amount of ruby code before exiting.
2025-11-10 20:33:07 -05:00
Peter Zhu
1858233ffa Free the native thread of the main thread on FREE_AT_EXIT 2025-10-04 18:21:13 -04:00
Luke Gruber
62430c19c9 Properly unlock locked mutexes on thread cleanup.
Mutexes were being improperly unlocked on thread cleanup. This bug was
introduced in 050a8954395.

We must keep a reference from the mutex to the thread, because if the fiber
is collected before the mutex is, then we cannot unlink it from the thread in
`mutex_free`. If it's not unlinked from the the thread when it's freed, it
causes bugs in `rb_thread_unlock_all_locking_mutexes`.

We now mark the fiber when a mutex is locked, and the thread is marked
as well. However, a fiber can still be freed in the same GC cycle as the
mutex, so the reference to the thread is still needed.

The reason we need to mark the fiber is that `mutex_owned_p()` has an ABA
issue where if the fiber is collected while it's locked, a new fiber could be
allocated at the same memory address and we could get false positives.

Fixes [Bug #21342]

Co-authored-by: John Hawthorn <john@hawthorn.email>
2025-09-25 15:29:47 -07:00
Koichi Sasada
55b1ba3bf2 Ractor.shareable_proc
call-seq:
  Ractor.sharable_proc(self: nil){} -> sharable proc

It returns shareable Proc object. The Proc object is
shareable and the self in a block will be replaced with
the value passed via `self:` keyword.

In a shareable Proc, the outer variables should
* (1) refer shareable objects
* (2) be not be overwritten

```ruby
  a = 42
  Ractor.shareable_proc{ p a }
  #=> OK

  b = 43
  Ractor.shareable_proc{ p b; b = 44 }
  #=> Ractor::IsolationError because 'b' is reassigned in the block.

  c = 44
  Ractor.shareable_proc{ p c }
  #=> Ractor::IsolationError because 'c' will be reassigned outside of the block.
  c = 45

  d = 45
  d = 46 if cond
  Ractor.shareable_proc{ p d }
  #=> Ractor::IsolationError because 'd' was reassigned outside of the block.
```

The last `d`'s case can be relaxed in a future version.

The above check will be done in a static analysis at compile time,
so the reflection feature such as `Binding#local_varaible_set`
can not be detected.

```ruby
  e = 42
  shpr = Ractor.shareable_proc{ p e } #=> OK
  binding.local_variable_set(:e, 43)
  shpr.call #=> 42 (returns captured timing value)
```

Ractor.sharaeble_lambda is also introduced.
[Feature #21550]
[Feature #21557]
2025-09-24 03:59:03 +09:00
Samuel Williams
64f508ade8
Support cause: in Thread#raise and Fiber#raise. (#13967)
* Add support for `cause:` argument to `Fiber#raise` and `Thread#raise`.

The implementation behaviour is consistent with `Kernel#raise` and
`Exception#initialize` methods, allowing the `cause:` argument to be
passed to `Fiber#raise` and `Thread#raise`. This change ensures that
the `cause:` argument is handled correctly, providing a more consistent
and expected behavior when raising exceptions in fibers and threads.

[Feature #21360]

* Shared specs for Fiber/Thread/Kernel raise.

---------

Co-authored-by: Samuel Williams <samuel.williams@shopify.com>
2025-07-24 14:45:43 +12:00
John Hawthorn
6c66458070 Fix rb_eSystemExit raised in Ractor
[Bug #21505]

Previously `Ractor.new { exit }.join` would hang because SystemExit was
special cased.

This commit updates this to take the same path as other exceptions,
which wraps the exception in a Ractor::RemoteError and does not end up
exiting the main Ractor. I don't know if that's what this should do, but
I think it's a reasonable behaviour as calling exit() in a Ractor is
odd.

    in 'Ractor#join': thrown by remote Ractor. (Ractor::RemoteError)
       from -e:1:in '<main>'
    in 'Kernel#exit': exit (SystemExit)
            from -e:1:in 'block in <main>'
2025-07-10 15:57:08 -07:00
Erik Berlin
eab4a0bc8d
Fix race condition in signal handler query (#13712)
* Fix race condition in signal handler query

* Initialize signal lock dynamically and reset after fork

* Fix signal handler mutex initialization conditions
2025-06-28 13:55:59 +09:00
Nobuyoshi Nakada
fe9a3be296
Fix the unknown warning group on wasm 2025-06-27 16:29:11 +09:00
Samuel Williams
ba68343d3a
Allow wakeup mutex to be used in trap context. (#13684) 2025-06-24 05:55:07 +00:00
Nobuyoshi Nakada
a60bf9e693
* adjust indent 2025-06-17 12:30:18 +09:00
Samuel Williams
68625a23d6
Fix blocking operation cancellation. (#13614)
Expose `rb_thread_resolve_unblock_function` internally.
2025-06-14 12:32:51 +09:00
John Hawthorn
a34fcf401b Add a new_thread flag to rb_interrupt_exec
Previously rb_ractor_interrupt_exec would use an intermediate function
to create a new thread with the actual target function, replacing the
data being passed in with a piece of malloc memory holding the "next"
function and the original data.

Because of this, passing rb_interrupt_exec_flag_value_data to
rb_ractor_interrupt_exec didn't have the intended effect of allowing
data to be passed in and marked.

This commit adds a rb_interrupt_exec_flag_new_thread flag, which
both simplifies the implementation and allows the original data to be
marked.
2025-06-12 13:13:55 -07:00
Peter Zhu
6e36841dbd Free rb_native_thread memory at fork
We never freed any resources of rb_native_thread at fork because it would
cause it to hang. This is because it called rb_native_cond_destroy for
condition variables.  We can't call rb_native_cond_destroy here because
according to the specs of pthread_cond_destroy:

    Attempting to destroy a condition variable upon which other threads
    are currently blocked results in undefined behavior.

Specifically, glibc's pthread_cond_destroy waits on all the other listeners.
Since after forking all the threads are dead, the condition variable's
listeners will never wake up, so it will hang forever.

This commit changes it to only free the memory and none of the condition
variables.
2025-06-12 15:23:50 -04:00
Samuel Williams
ead14b19aa Fix blocking_operation_wait use-after-free bug. 2025-06-06 13:13:16 +09:00
Samuel Williams
81a23c5793 rb_io_blocking_operation_exit should not execute with pending interrupts. 2025-06-06 13:13:16 +09:00
Samuel Williams
f0cf4dce65
Handle spurious wakeups in Thread#join. (#13532) 2025-06-06 09:38:57 +09:00
Luke Gruber
54ef6c312a
[Bug #21400] Fix rb_bug() when killing current root fiber in non-main thread (#13526)
Fixes the following:

```ruby
Thread.new { Fiber.current.kill }.join
```
2025-06-06 09:31:45 +09:00
Nobuyoshi Nakada
9fddb8d954
Suppress dangling pointer warning by gcc
`__has_warning` is clang, not gcc.
2025-06-04 15:31:40 +09:00
Samuel Williams
9a29252830
Fix compatibility with fiber schedulers that don't implement #fiber_interrupt. (#13492) 2025-06-02 18:50:23 +09:00
Koichi Sasada
ef2bb61018 Ractor::Port
* Added `Ractor::Port`
  * `Ractor::Port#receive` (support multi-threads)
  * `Rcator::Port#close`
  * `Ractor::Port#closed?`
* Added some methods
  * `Ractor#join`
  * `Ractor#value`
  * `Ractor#monitor`
  * `Ractor#unmonitor`
* Removed some methods
  * `Ractor#take`
  * `Ractor.yield`
* Change the spec
  * `Racotr.select`

You can wait for multiple sequences of messages with `Ractor::Port`.

```ruby
ports = 3.times.map{ Ractor::Port.new }
ports.map.with_index do |port, ri|
  Ractor.new port,ri do |port, ri|
    3.times{|i| port << "r#{ri}-#{i}"}
  end
end

p ports.each{|port| pp 3.times.map{port.receive}}

```

In this example, we use 3 ports, and 3 Ractors send messages to them respectively.
We can receive a series of messages from each port.

You can use `Ractor#value` to get the last value of a Ractor's block:

```ruby
result = Ractor.new do
  heavy_task()
end.value
```

You can wait for the termination of a Ractor with `Ractor#join` like this:

```ruby
Ractor.new do
  some_task()
end.join
```

`#value` and `#join` are similar to `Thread#value` and `Thread#join`.

To implement `#join`, `Ractor#monitor` (and `Ractor#unmonitor`) is introduced.

This commit changes `Ractor.select()` method.
It now only accepts ports or Ractors, and returns when a port receives a message or a Ractor terminates.

We removes `Ractor.yield` and `Ractor#take` because:
* `Ractor::Port` supports most of similar use cases in a simpler manner.
* Removing them significantly simplifies the code.

We also change the internal thread scheduler code (thread_pthread.c):
* During barrier synchronization, we keep the `ractor_sched` lock to avoid deadlocks.
  This lock is released by `rb_ractor_sched_barrier_end()`
  which is called at the end of operations that require the barrier.
* fix potential deadlock issues by checking interrupts just before setting UBF.

https://bugs.ruby-lang.org/issues/21262
2025-05-31 04:01:33 +09:00
Nobuyoshi Nakada
aad9fa2853
Use RB_VM_LOCKING 2025-05-25 15:22:43 +09:00
Daisuke Fujimura (fd0)
70f8f7c4b1 Fix warning on cygwin 2025-05-23 20:49:19 +09:00
Samuel Williams
73c9d6ccaa
Allow IO#close to interrupt IO operations on fibers using fiber_interrupt hook. (#12839) 2025-05-23 14:55:05 +09:00
John Hawthorn
05e0e7223a Use atomic load to read interrupt mask 2025-05-20 09:56:31 -07:00
John Hawthorn
d67d169aea Use atomics for system_working global
Although it almost certainly works in this case, volatile is best not
used for multi-threaded code. Using atomics instead avoids warnings from
TSan.

This also simplifies some logic, as system_working was previously only
ever assigned to 1, so --system_working <= 0 should always return true
(unless it underflowed).
2025-05-15 15:18:10 -07:00
John Hawthorn
d845da05e8 Force reset running time in timer interrupt
Co-authored-by: Ivo Anjo <ivo.anjo@datadoghq.com>
Co-authored-by: Luke Gruber <luke.gru@gmail.com>
2025-05-15 14:44:26 -07:00
Nobuyoshi Nakada
2e3f81838c
Align styles [ci skip] 2025-05-15 17:48:40 +09:00
Samuel Williams
87261c2d95
Ensure that forked process do not see invalid blocking operations. (#13343) 2025-05-15 15:50:15 +09:00
Luke Gruber
1d4822a175 Get ractor message passing working with > 1 thread sending/receiving values in same ractor
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]
2025-05-13 13:23:57 -07:00
Samuel Williams
425fa0aeb5
Make waiting_fd behaviour per-IO. (#13127)
- `rb_thread_fd_close` is deprecated and now a no-op.
- IO operations (including close) no longer take a vm-wide lock.
2025-05-13 19:02:03 +09:00
Aaron Patterson
f7ff380998 Clean up Ractor cache after fork
Ractors created in a parent process should be properly shut down in the
child process.  They need their cache cleared and status set to
"terminated"

Co-authored-by: John Hawthorn <john@hawthorn.email>
2025-05-08 10:53:28 -07:00
Nobuyoshi Nakada
c218862d3c
Fix style [ci skip] 2025-04-19 22:02:10 +09:00
Samuel Williams
20a1c1dc6b
Ensure struct rb_io is passed through to thread.c. (#13134) 2025-04-19 09:55:16 +09:00
Samuel Williams
4e970c5d5a Expose ruby_thread_has_gvl_p. 2025-04-14 18:28:09 +09:00
Yusuke Endoh
0d6263bd41 Fix coverage measurement for negative line numbers
Fixes [Bug #21220]

Co-Authored-By: Mike Bourgeous <mike@mikebourgeous.com>
Co-Authored-By: Jean Boussier <jean.boussier@gmail.com>
2025-04-09 23:45:54 +09:00
Jean Boussier
532b9246d3 Initialize ractor thgroup in thread_do_start_proc
Followup: https://github.com/ruby/ruby/pull/13013
2025-03-31 11:14:34 +02:00
Jean Boussier
5e421ce8d9 ractor: don't inherit the default thread group
[Bug #17506]

`Thread.current.group` isn't shareable so it shouldn't be inherited
by the main thread of a new Ractor.

This cause an extra allocation when spawning a ractor, which could
be elided with a bit of extra work, but not sure if it's worth
the effort.
2025-03-31 10:25:52 +02:00
John Hawthorn
310c00a137 Reset thread interrupt lock on fork
If a thread was holding this lock before fork, it will not exist in the
child process. We should re-initialize these locks as we do with the VM
locks when forking.

Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
2025-03-25 19:14:26 -07:00
Masataka Pocke Kuwabara
0cab608d3a
[Bug #21127] Thread deadlock does not display backtraces (#12721)
Previously, Ruby displayed backtraces for each thread on deadlock. However, it has not been shown since Ruby 3.0.
It should display the backtrace for debugging.

Co-authored-by: Jeremy Evans <code@jeremyevans.net>
2025-02-14 16:31:58 +09:00
Nobuyoshi Nakada
4a67ef09cc
[Feature #21116] Extract RJIT as a third-party gem 2025-02-13 18:01:03 +09:00
Nobuyoshi Nakada
aca0b92c2f prev_mn_schedulable might be clobbered by longjmp 2025-01-30 18:19:53 +09:00