Ruby 4.1.0dev (2026-09-07 revision b57404b461ba8bf34e802d86b0db78388216e182)
vm_sync.c (b57404b461ba8bf34e802d86b0db78388216e182)
1#include "internal/gc.h"
2#include "internal/thread.h"
3#include "vm_core.h"
4#include "vm_sync.h"
5#include "ractor_core.h"
6#include "vm_debug.h"
7#include "probes.h"
8
9void rb_ractor_sched_barrier_start(rb_vm_t *vm, rb_ractor_t *cr);
10void rb_ractor_sched_barrier_join(rb_vm_t *vm, rb_ractor_t *cr);
11void rb_ractor_sched_barrier_end(rb_vm_t *vm, rb_ractor_t *cr);
12
13static bool
14vm_locked(rb_vm_t *vm)
15{
16 return vm_locked_by_ractor_p(vm, GET_RACTOR());
17}
18
19#if RUBY_DEBUG > 0
20static bool
21vm_lock_assertable_p(void)
22{
23 return rb_current_execution_context(false) != NULL;
24}
25
26void
27RUBY_ASSERT_vm_locking(void)
28{
29 if (vm_lock_assertable_p() && rb_multi_ractor_p()) {
30 rb_vm_t *vm = GET_VM();
31 VM_ASSERT(vm_locked(vm));
32 }
33}
34
35void
36RUBY_ASSERT_vm_locking_with_barrier(void)
37{
38 if (vm_lock_assertable_p() && rb_multi_ractor_p()) {
39 rb_vm_t *vm = GET_VM();
40 VM_ASSERT(vm_locked(vm));
41
42 if (vm->ractor.cnt > 1) {
43 /* Written to only when holding both ractor.sync and ractor.sched lock */
44 VM_ASSERT(vm->ractor.sched.barrier_is_waiting);
45 }
46 }
47}
48
49void
50RUBY_ASSERT_vm_unlocking(void)
51{
52 if (vm_lock_assertable_p() && rb_multi_ractor_p()) {
53 rb_vm_t *vm = GET_VM();
54 VM_ASSERT(!vm_locked(vm));
55 }
56}
57#endif
58
59bool
60rb_vm_locked_p(void)
61{
62 return vm_locked(GET_VM());
63}
64
65static bool
66vm_need_barrier_waiting(const rb_vm_t *vm)
67{
68 return vm->ractor.sched.barrier_is_waiting;
69}
70
71static bool
72vm_need_barrier(bool no_barrier, const rb_ractor_t *cr, const rb_vm_t *vm)
73{
74 return !no_barrier && cr->threads.sched.running != NULL && vm_need_barrier_waiting(vm); // ractor has running threads.
75}
76
77static void
78vm_lock_enter(rb_ractor_t *cr, rb_vm_t *vm, bool locked, bool no_barrier, unsigned int *lev APPEND_LOCATION_ARGS)
79{
80 RUBY_DEBUG_LOG2(file, line, "start locked:%d", locked);
81
82 if (locked) {
83 ASSERT_vm_locking();
84 }
85 else {
86#if RACTOR_CHECK_MODE
87 // locking ractor and acquire VM lock will cause deadlock
88 VM_ASSERT(cr->sync.locked_by != rb_ractor_self(cr));
89#endif
90 // lock
91 rb_native_mutex_lock(&vm->ractor.sync.lock);
92 VM_ASSERT(vm->ractor.sync.lock_owner == NULL);
93 VM_ASSERT(vm->ractor.sync.lock_rec == 0);
94
95 // barrier
96 if (vm_need_barrier(no_barrier, cr, vm)) {
97 rb_execution_context_t *ec = GET_EC();
98 RB_VM_SAVE_MACHINE_CONTEXT(rb_ec_thread_ptr(ec));
99
100 do {
101 VM_ASSERT(vm_need_barrier_waiting(vm));
102 RUBY_DEBUG_LOG("barrier serial:%u", vm->ractor.sched.barrier_serial);
103 rb_ractor_sched_barrier_join(vm, cr);
104 } while (vm_need_barrier_waiting(vm));
105 }
106
107 VM_ASSERT(vm->ractor.sync.lock_rec == 0);
108 VM_ASSERT(vm->ractor.sync.lock_owner == NULL);
109 vm->ractor.sync.lock_owner = cr;
110 }
111
112 vm->ractor.sync.lock_rec++;
113 *lev = vm->ractor.sync.lock_rec;
114
115 RUBY_DEBUG_LOG2(file, line, "rec:%u owner:%u", vm->ractor.sync.lock_rec,
116 (unsigned int)rb_ractor_id(vm->ractor.sync.lock_owner));
117
118 if (RUBY_DTRACE_GVL_ACQUIRE_ENABLED()) {
119 RUBY_DTRACE_GVL_ACQUIRE();
120 }
121}
122
123static void
124vm_lock_leave(rb_vm_t *vm, bool no_barrier, unsigned int *lev APPEND_LOCATION_ARGS)
125{
126 MAYBE_UNUSED(rb_ractor_t *cr = vm->ractor.sync.lock_owner);
127
128 RUBY_DEBUG_LOG2(file, line, "rec:%u owner:%u%s", vm->ractor.sync.lock_rec,
129 (unsigned int)rb_ractor_id(cr),
130 vm->ractor.sync.lock_rec == 1 ? " (leave)" : "");
131
132 ASSERT_vm_locking();
133 VM_ASSERT(vm->ractor.sync.lock_rec > 0);
134 VM_ASSERT(vm->ractor.sync.lock_rec == *lev);
135 VM_ASSERT(cr == GET_RACTOR());
136
137 if (vm->ractor.sched.barrier_ractor == cr &&
138 vm->ractor.sched.barrier_lock_rec == vm->ractor.sync.lock_rec) {
139 VM_ASSERT(!no_barrier);
140 rb_ractor_sched_barrier_end(vm, cr);
141 }
142
143 if (RUBY_DTRACE_GVL_RELEASE_ENABLED()) {
144 RUBY_DTRACE_GVL_RELEASE();
145 }
146
147 vm->ractor.sync.lock_rec--;
148 *lev = vm->ractor.sync.lock_rec;
149
150 if (vm->ractor.sync.lock_rec == 0) {
151 vm->ractor.sync.lock_owner = NULL;
152 rb_native_mutex_unlock(&vm->ractor.sync.lock);
153 }
154}
155
156void
157rb_vm_lock_enter_body(unsigned int *lev APPEND_LOCATION_ARGS)
158{
159 rb_vm_t *vm = GET_VM();
160 if (vm_locked(vm)) {
161 vm_lock_enter(NULL, vm, true, false, lev APPEND_LOCATION_PARAMS);
162 }
163 else {
164 vm_lock_enter(GET_RACTOR(), vm, false, false, lev APPEND_LOCATION_PARAMS);
165 }
166}
167
168void
169rb_vm_lock_enter_body_nb(unsigned int *lev APPEND_LOCATION_ARGS)
170{
171 rb_vm_t *vm = GET_VM();
172 if (vm_locked(vm)) {
173 vm_lock_enter(NULL, vm, true, true, lev APPEND_LOCATION_PARAMS);
174 }
175 else {
176 vm_lock_enter(GET_RACTOR(), vm, false, true, lev APPEND_LOCATION_PARAMS);
177 }
178}
179
180void
181rb_vm_lock_enter_body_cr(rb_ractor_t *cr, unsigned int *lev APPEND_LOCATION_ARGS)
182{
183 rb_vm_t *vm = GET_VM();
184 vm_lock_enter(cr, vm, vm_locked(vm), false, lev APPEND_LOCATION_PARAMS);
185}
186
187void
188rb_vm_lock_leave_body_nb(unsigned int *lev APPEND_LOCATION_ARGS)
189{
190 vm_lock_leave(GET_VM(), true, lev APPEND_LOCATION_PARAMS);
191}
192
193void
194rb_vm_lock_leave_body(unsigned int *lev APPEND_LOCATION_ARGS)
195{
196 vm_lock_leave(GET_VM(), false, lev APPEND_LOCATION_PARAMS);
197}
198
199void
200rb_vm_lock_body(LOCATION_ARGS)
201{
202 rb_vm_t *vm = GET_VM();
203 ASSERT_vm_unlocking();
204
205 vm_lock_enter(GET_RACTOR(), vm, false, false, &vm->ractor.sync.lock_rec APPEND_LOCATION_PARAMS);
206}
207
208void
209rb_vm_unlock_body(LOCATION_ARGS)
210{
211 rb_vm_t *vm = GET_VM();
212 ASSERT_vm_locking();
213 VM_ASSERT(vm->ractor.sync.lock_rec == 1);
214 vm_lock_leave(vm, false, &vm->ractor.sync.lock_rec APPEND_LOCATION_PARAMS);
215}
216
217static void
218vm_cond_wait(rb_vm_t *vm, rb_nativethread_cond_t *cond, unsigned long msec)
219{
220 ASSERT_vm_locking();
221 unsigned int lock_rec = vm->ractor.sync.lock_rec;
222 rb_ractor_t *cr = vm->ractor.sync.lock_owner;
223
224 vm->ractor.sync.lock_rec = 0;
225 vm->ractor.sync.lock_owner = NULL;
226 if (msec > 0) {
227 rb_native_cond_timedwait(cond, &vm->ractor.sync.lock, msec);
228 }
229 else {
230 rb_native_cond_wait(cond, &vm->ractor.sync.lock);
231 }
232 vm->ractor.sync.lock_rec = lock_rec;
233 vm->ractor.sync.lock_owner = cr;
234}
235
236void
237rb_vm_cond_wait(rb_vm_t *vm, rb_nativethread_cond_t *cond)
238{
239 vm_cond_wait(vm, cond, 0);
240}
241
242void
243rb_vm_cond_timedwait(rb_vm_t *vm, rb_nativethread_cond_t *cond, unsigned long msec)
244{
245 vm_cond_wait(vm, cond, msec);
246}
247
248static bool
249vm_barrier_acquired_p(const rb_vm_t *vm, const rb_ractor_t *cr)
250{
251 return vm->ractor.sched.barrier_ractor == cr;
252}
253
254void
255rb_vm_barrier(void)
256{
257 RB_DEBUG_COUNTER_INC(vm_sync_barrier);
258
259 if (!rb_multi_ractor_p()) {
260 // no other ractors
261 return;
262 }
263 else {
264 rb_vm_t *vm = GET_VM();
265 rb_ractor_t *cr = vm->ractor.sync.lock_owner;
266
267 ASSERT_vm_locking();
268 VM_ASSERT(cr == GET_RACTOR());
269 VM_ASSERT(rb_ractor_status_p(cr, ractor_running));
270
271 if (vm_barrier_acquired_p(vm, cr)) {
272 // already in barrier synchronization
273 return;
274 }
275 else {
276 VM_ASSERT(!vm->ractor.sched.barrier_is_waiting);
277 rb_ractor_sched_barrier_start(vm, cr);
278 }
279 }
280}
281
282void
283rb_ec_vm_lock_rec_release(const rb_execution_context_t *ec,
284 unsigned int recorded_lock_rec,
285 unsigned int current_lock_rec)
286{
287 VM_ASSERT(recorded_lock_rec != current_lock_rec);
288
289 if (UNLIKELY(recorded_lock_rec > current_lock_rec)) {
290 rb_bug("unexpected situation - recordd:%u current:%u",
291 recorded_lock_rec, current_lock_rec);
292 }
293 else {
294 while (recorded_lock_rec < current_lock_rec) {
295 RB_VM_LOCK_LEAVE_LEV(&current_lock_rec);
296 }
297 }
298
299 VM_ASSERT(recorded_lock_rec == rb_ec_vm_lock_rec(ec));
300}
301
302VALUE
303rb_vm_lock_with_barrier(VALUE (*func)(void *args), void *args)
304{
305 VALUE result = 0;
306 RB_VM_LOCKING() {
307 rb_vm_barrier();
308 result = func(args);
309 }
310 return result;
311}
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_lock.
void rb_native_mutex_unlock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_unlock.
void rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex)
Waits for the passed condition variable to be signalled.
void rb_native_cond_timedwait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex, unsigned long msec)
Identical to rb_native_cond_wait(), except it additionally takes timeout in msec resolution.
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40