summaryrefslogtreecommitdiff
path: root/thread_sync.c
AgeCommit message (Collapse)Author
2025-12-18thread_sync.c: rename mutex_trylock internal functionJean Boussier
[Bug #21793] To fix a naming conflict on solaris.
2025-12-18thread_sync.c: declare queue_data_type as parent of szqueue_data_type.Jean Boussier
Allows to remove some duplicated code like szqueue_length, etc.
2025-12-18thread_sync.c: simplify `check_array`Jean Boussier
If the queue was allocated without calling initialize, `ary` will be `0`.
2025-12-18thread_sync.c: eliminate GET_EC() from queue_do_popJean Boussier
We receive the ec as argument, it's much cheaper to pass it around that to look it up again.
2025-12-18thread_sync: Mutex keep `rb_thread_t *` instead of `VALUE`Jean Boussier
We never need the actual thread object and this avoid any issue if the thread object is ever moved.
2025-12-16Rename fiber_serial into ec_serialJean Boussier
Since it now live in the EC.
2025-12-16Store the fiber_serial in the EC to allow inliningJean Boussier
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-12thead_sync.c: directly pass the execution context to yieldJean Boussier
Saves one more call to GET_EC()
2025-12-12Define Thread::ConditionVariable in thread_sync.rbJean Boussier
It's more consistent with Mutex, but also the `#wait` method benefits from receiving the execution context instead of having to call `GET_EC`.
2025-12-11Mutex: avoid repeated calls to `GET_EC`Jean Boussier
That call is surprisingly expensive, so trying doing it once in `#synchronize` and then passing the EC to lock and unlock saves quite a few cycles. Before: ``` ruby 4.0.0dev (2025-12-10T09:30:18Z master c5608ab4d7) +YJIT +PRISM [arm64-darwin25] Warming up -------------------------------------- Mutex 1.888M i/100ms Monitor 1.633M i/100ms Calculating ------------------------------------- Mutex 22.610M (± 0.2%) i/s (44.23 ns/i) - 113.258M in 5.009097s Monitor 19.148M (± 0.3%) i/s (52.22 ns/i) - 96.366M in 5.032755s ``` After: ``` ruby 4.0.0dev (2025-12-10T10:40:07Z speedup-mutex 1c901cd4f8) +YJIT +PRISM [arm64-darwin25] Warming up -------------------------------------- Mutex 2.095M i/100ms Monitor 1.578M i/100ms Calculating ------------------------------------- Mutex 24.456M (± 0.4%) i/s (40.89 ns/i) - 123.584M in 5.053418s Monitor 19.176M (± 0.1%) i/s (52.15 ns/i) - 96.243M in 5.018977s ``` Bench: ``` require 'bundler/inline' gemfile do gem "benchmark-ips" end mutex = Mutex.new require "monitor" monitor = Monitor.new Benchmark.ips do |x| x.report("Mutex") { mutex.synchronize { } } x.report("Monitor") { monitor.synchronize { } } end ```
2025-12-10Monitor: avoid repeated calls to `rb_fiber_current()`Jean Boussier
That call is surprisingly expensive, so trying doing it once in `#synchronize` and then passing the fiber to enter and exit saves quite a few cycles.
2025-12-08fix SEGV on clang-16/18Koichi Sasada
Maybe because of TLS/coroutine problem, CI fails on clang-16/18 ``` 1) Failure: TestTimeout#test_ractor [/tmp/ruby/src/trunk_clang_18/test/test_timeout.rb:288]: pid 307341 killed by SIGSEGV (signal 11) (core dumped) | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:98: [BUG] Segmentation fault at 0x0000000000000030 | ruby 4.0.0dev (2025-12-07T16:51:02Z master 4f900c35bc) +PRISM [x86_64-linux] | | -- Control frame information ----------------------------------------------- | c:0006 p:---- s:0026 e:000025 l:y b:---- CFUNC :sleep | c:0005 p:---- s:0023 e:000022 l:y b:---- CFUNC :wait | c:0004 p:0020 s:0017 e:000016 l:n b:---- BLOCK /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:98 [FINISH] | c:0003 p:---- s:0014 e:000013 l:y b:---- CFUNC :synchronize | c:0002 p:0072 s:0010 e:000009 l:n b:---- BLOCK /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:96 [FINISH] | c:0001 p:---- s:0003 e:000002 l:y b:---- DUMMY [FINISH] | | -- Ruby level backtrace information ---------------------------------------- | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:96:in 'block in create_timeout_thread' | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:96:in 'synchronize' | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:98:in 'block (2 levels) in create_timeout_thread' | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:98:in 'wait' | /tmp/ruby/src/trunk_clang_18/lib/timeout.rb:98:in 'sleep' | | -- Threading information --------------------------------------------------- | Total ractor count: 3 | Ruby thread count for this ractor: 2 | | -- Machine register context ------------------------------------------------ | RIP: 0x0000602b1e08a5b5 RBP: 0x000071c65facd130 RSP: 0x000071c6258842e0 | RAX: 0x0000000000000000 RBX: 0x000000006935f7c4 RCX: 0x0000000000000000 | RDX: 0x0000602b1e520c20 RDI: 0x000071c620012480 RSI: 0x000071c620012480 | R8: 0x0000000000000000 R9: 0x0000000000000000 R10: 0x0000000000000000 | R11: 0x0000000000000000 R12: 0x000071c65fa2f640 R13: 0x000071c65fb66e48 | R14: 0x0000000000000000 R15: 0xfdccaa3270000002 EFL: 0x0000000000010202 | | -- C level backtrace information ------------------------------------------- | /tmp/ruby/build/trunk_clang_18/ruby(rb_print_backtrace+0x14) [0x602b1e31a6ea] /tmp/ruby/src/trunk_clang_18/vm_dump.c:1105 | /tmp/ruby/build/trunk_clang_18/ruby(rb_vm_bugreport) /tmp/ruby/src/trunk_clang_18/vm_dump.c:1450 | /tmp/ruby/build/trunk_clang_18/ruby(rb_bug_for_fatal_signal+0x15c) [0x602b1e2d960c] /tmp/ruby/src/trunk_clang_18/error.c:1131 | /tmp/ruby/build/trunk_clang_18/ruby(sigsegv+0x5a) [0x602b1e05528a] /tmp/ruby/src/trunk_clang_18/signal.c:948 | /lib/x86_64-linux-gnu/libc.so.6(0x71c65fd46320) [0x71c65fd46320] | /tmp/ruby/build/trunk_clang_18/ruby(vm_check_ints_blocking+0x0) [0x602b1e08a5b5] /tmp/ruby/src/trunk_clang_18/vm_core.h:2097 | /tmp/ruby/build/trunk_clang_18/ruby(rb_current_execution_context) /tmp/ruby/src/trunk_clang_18/thread_sync.c:617 | /tmp/ruby/build/trunk_clang_18/ruby(rb_mutex_sleep) /tmp/ruby/src/trunk_clang_18/thread_sync.c:617 ``` This patch introduces workaround by acquiring EC before swithcing coroutine.
2025-11-20Use a serial to keep track of Mutex-owning FiberJohn Hawthorn
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-09-25Properly unlock locked mutexes on thread cleanup.Luke Gruber
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-08-28Make Thread::Queue and SizedQueue support compactionPeter Zhu
2025-08-14Remove duplicated line of code in thread_sync.cPeter Zhu
2025-04-02[DOC] Doc for Thread::ConditionVariableKunshan Wang
Documented the necessity of calling `wait` in a loop. We modified the example to demonstrate the idiomatic use, and added a third thread `a2` to demonstrate another reason that necessitates the loop. Mentioned spurious wake-up in the doc. Notes: Merged: https://github.com/ruby/ruby/pull/13024
2024-11-23Ensure fiber scheduler re-acquires mutex when interrupted from sleep. (#12158)Samuel Williams
[Bug #20907] Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2024-02-12Replace assert with RUBY_ASSERT in thread_sync.cPeter Zhu
assert does not print the bug report, only the file and line number of the assertion that failed. RUBY_ASSERT prints the full bug report, which makes it much easier to debug.
2024-01-17Remove SizedQueue#freezeJeremy Evans
Queue#freeze uses the same C function, so SizedQueue#freeze can use that via normal method lookup.
2023-12-14[DOC] Adjust some new features wording/examples. (#9183)Victor Shepelev
* Reword Range#overlap? docs last paragraph. * Docs: add explanation about Queue#freeze * Docs: Add :rescue event docs for TracePoint * Docs: Enhance Module#set_temporary_name documentation * Docs: Slightly expand Process::Status deprecations * Fix MatchData#named_captures rendering glitch * Improve Dir.fchdir examples * Adjust Refinement#target docs
2023-10-12M:N thread scheduler for RactorsKoichi Sasada
This patch introduce M:N thread scheduler for Ractor system. In general, M:N thread scheduler employs N native threads (OS threads) to manage M user-level threads (Ruby threads in this case). On the Ruby interpreter, 1 native thread is provided for 1 Ractor and all Ruby threads are managed by the native thread. From Ruby 1.9, the interpreter uses 1:1 thread scheduler which means 1 Ruby thread has 1 native thread. M:N scheduler change this strategy. Because of compatibility issue (and stableness issue of the implementation) main Ractor doesn't use M:N scheduler on default. On the other words, threads on the main Ractor will be managed with 1:1 thread scheduler. There are additional settings by environment variables: `RUBY_MN_THREADS=1` enables M:N thread scheduler on the main ractor. Note that non-main ractors use the M:N scheduler without this configuration. With this configuration, single ractor applications run threads on M:1 thread scheduler (green threads, user-level threads). `RUBY_MAX_CPU=n` specifies maximum number of native threads for M:N scheduler (default: 8). This patch will be reverted soon if non-easy issues are found. [Bug #19842]
2023-09-27Make {Queue,SizedQueue}#freeze raise TypeErrorJeremy Evans
Fixes [Bug #17146]
2023-09-08Try default `gcc` 9.4.0 to see if it exhibits the same compiler bugs. (#8394)Samuel Williams
* Revert "Extract `do_mutex_lock_check_interrupts` to try and fix `ppc64le`. (#8393)" This reverts commit 5184b40dd4dc446660cd35c3e53896324e95b317. * .travis.yml: Try default gcc 9.4.0 instead of gcc-10 in ppc64le and s390x. Use gcc 9.4.0 instead of gcc-10 to avoid the current failures by a possible GCC 10 compiler bug in the Travis ppc64le and s390x cases. And it also aligns with RubyCI Ubuntu ppc64le and s390x where the default gcc is used. --------- Co-authored-by: Jun Aruga <jaruga@ruby-lang.org> Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2023-09-08Extract `do_mutex_lock_check_interrupts` to try and fix `ppc64le`. (#8393)Samuel Williams
We found some tests were hanging in `do_mutex_lock`, specifically the fiber scheduler autoload test. After much investigation, it may be a code generation bug. Because we didn't change the code, but only extracted it into a separate function, and it appears to fix the problem. Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2023-07-13[DOC] Add parenthetical to explain what FIFO abbrev meansShane Becker
Notes: Merged: https://github.com/ruby/ruby/pull/8062
2023-07-13[DOC] Fix example code indentation from 3 to 2Shane Becker
Notes: Merged: https://github.com/ruby/ruby/pull/8063
2023-03-07Correctly clean up `keeping_mutexes` before resuming any other threads. (#7460)Samuel Williams
It's possible (but very rare) to have a race condition between setting `mutex->fiber = NULL` and `thread_mutex_remove(th, mutex)` which results in the following bug: ``` [BUG] invalid keeping_mutexes: Attempt to unlock a mutex which is not locked ``` Fixes <https://bugs.ruby-lang.org/issues/19480>. Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2023-02-08Replace `PACKED_STRUCT` and `PACKED_STRUCT_UNALIGNED`Nobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/7268
2023-01-21Add `queue_list` and `szqueue_list` macrosNobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/7161
2023-01-19Fix compilation warnings in thread_sync.cPeter Zhu
Fixes the following compilation warnings: thread_sync.c:908:48: warning: taking address of packed member of `struct rb_queue` may result in an unaligned pointer value [-Waddress-of-packed-member] thread_sync.c:1181:48: warning: taking address of packed member of `struct rb_queue` may result in an unaligned pointer value [-Waddress-of-packed-member]
2023-01-18Don't redefine RB_OBJ_WRITEPeter Zhu
RB_OBJ_WRITE already exists in rgengc.h, so we shouldn't redefine it in gc.h. Notes: Merged: https://github.com/ruby/ruby/pull/7131
2022-11-09mutex: Raise a ThreadError when detecting a fiber deadlock (#6680)Jean byroot Boussier
[Bug #19105] If no fiber scheduler is registered and the fiber that owns the lock and the one that try to acquire it both belong to the same thread, we're in a deadlock case. Co-authored-by: Jean Boussier <byroot@ruby-lang.org> Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2022-10-20Avoid missed wakeup with fiber scheduler and Fiber.blocking. (#6588)Samuel Williams
* Ensure that blocked fibers don't prevent valid wakeups. Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2022-10-18Adjust indents [ci skip]Nobuyoshi Nakada
2022-10-17thread_sync.c: Clarify and document the behavior of timeout == 0Jean Boussier
[Feature #18982] Instead of introducing an `exception: false` argument to have `non_block` return nil rather than raise, we can clearly document that a timeout of 0 immediately returns. The code is refactored a bit to avoid doing a time calculation in such case. Notes: Merged: https://github.com/ruby/ruby/pull/6500
2022-08-18Implement SizedQueue#push(timeout: sec)Jean Boussier
[Feature #18944] If both `non_block=true` and `timeout:` are supplied, ArgumentError is raised. Notes: Merged: https://github.com/ruby/ruby/pull/6207
2022-08-06Adjust styles [ci skip]Nobuyoshi Nakada
2022-08-04thread_sync.c: pass proper argument to queue_sleep in rb_szqueue_pushJean Boussier
When I removed the SizeQueue#push timeout from my PR, I forgot to update the `queue_sleep` parameters to be a `queue_sleep_arg`. Somehow this worked on most archs, but on Solaris/Sparc it would legitimately crash when trying to access the `timeout` and `end` members of the struct. Notes: Merged: https://github.com/ruby/ruby/pull/6213
2022-08-02Implement Queue#pop(timeout: sec)Jean Boussier
[Feature #18774] As well as `SizedQueue#pop(timeout: sec)` If both `non_block=true` and `timeout:` are supplied, ArgumentError is raised. Notes: Merged: https://github.com/ruby/ruby/pull/6185
2022-07-26Rename rb_ary_tmp_new to rb_ary_hidden_newPeter Zhu
rb_ary_tmp_new suggests that the array is temporary in some way, but that's not true, it just creates an array that's hidden and not on the transient heap. This commit renames it to rb_ary_hidden_new. Notes: Merged: https://github.com/ruby/ruby/pull/6180
2022-07-21Expand tabs [ci skip]Takashi Kokubun
[Misc #18891] Notes: Merged: https://github.com/ruby/ruby/pull/6094
2022-03-30Prefix ccan headers (#4568)Nobuyoshi Nakada
* Prefixed ccan headers * Remove unprefixed names in ccan/build_assert * Remove unprefixed names in ccan/check_type * Remove unprefixed names in ccan/container_of * Remove unprefixed names in ccan/list Co-authored-by: Samuel Williams <samuel.williams@oriontransfer.co.nz> Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2021-12-15[DOC] Improve Thread::Queue.new docs [ci skip]Victor Shepelev
Notes: Merged: https://github.com/ruby/ruby/pull/5273 Merged-By: nobu <nobu@ruby-lang.org>
2021-11-28Suppress address-of-packed-member warning by gccNobuyoshi Nakada
2021-09-17Add WB_PROTECTED to mutexesJohn Hawthorn
mutex_mark is (basically) NULL, so we don't have any references to mark. This means we should safely be able to mark Mutex as WB_PROTECTED without changing anything else. Notes: Merged: https://github.com/ruby/ruby/pull/4852
2021-08-02Using RBOOL macroS.H
Notes: Merged: https://github.com/ruby/ruby/pull/4695 Merged-By: nobu <nobu@ruby-lang.org>
2021-07-25Distinguish signal and timeout [Bug #16608]Nobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/4256
2021-07-01Replace copy coroutine with pthread implementation.Samuel Williams
2021-06-29Prefer qualified names under ThreadNobuyoshi Nakada