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