Line | Count | Source |
1 | | /********************************************************************** |
2 | | |
3 | | thread.c - |
4 | | |
5 | | $Author$ |
6 | | |
7 | | Copyright (C) 2004-2007 Koichi Sasada |
8 | | |
9 | | **********************************************************************/ |
10 | | |
11 | | /* |
12 | | YARV Thread Design |
13 | | |
14 | | model 1: Userlevel Thread |
15 | | Same as traditional ruby thread. |
16 | | |
17 | | model 2: Native Thread with Global VM lock |
18 | | Using pthread (or Windows thread) and Ruby threads run concurrent. |
19 | | |
20 | | model 3: Native Thread with fine grain lock |
21 | | Using pthread and Ruby threads run concurrent or parallel. |
22 | | |
23 | | model 4: M:N User:Native threads with Global VM lock |
24 | | Combination of model 1 and 2 |
25 | | |
26 | | model 5: M:N User:Native thread with fine grain lock |
27 | | Combination of model 1 and 3 |
28 | | |
29 | | ------------------------------------------------------------------------ |
30 | | |
31 | | model 2: |
32 | | A thread has mutex (GVL: Global VM Lock or Giant VM Lock) can run. |
33 | | When thread scheduling, running thread release GVL. If running thread |
34 | | try blocking operation, this thread must release GVL and another |
35 | | thread can continue this flow. After blocking operation, thread |
36 | | must check interrupt (RUBY_VM_CHECK_INTS). |
37 | | |
38 | | Every VM can run parallel. |
39 | | |
40 | | Ruby threads are scheduled by OS thread scheduler. |
41 | | |
42 | | ------------------------------------------------------------------------ |
43 | | |
44 | | model 3: |
45 | | Every threads run concurrent or parallel and to access shared object |
46 | | exclusive access control is needed. For example, to access String |
47 | | object or Array object, fine grain lock must be locked every time. |
48 | | */ |
49 | | |
50 | | |
51 | | /* |
52 | | * FD_SET, FD_CLR and FD_ISSET have a small sanity check when using glibc |
53 | | * 2.15 or later and set _FORTIFY_SOURCE > 0. |
54 | | * However, the implementation is wrong. Even though Linux's select(2) |
55 | | * supports large fd size (>FD_SETSIZE), it wrongly assumes fd is always |
56 | | * less than FD_SETSIZE (i.e. 1024). And then when enabling HAVE_RB_FD_INIT, |
57 | | * it doesn't work correctly and makes program abort. Therefore we need to |
58 | | * disable FORTIFY_SOURCE until glibc fixes it. |
59 | | */ |
60 | | #undef _FORTIFY_SOURCE |
61 | | #undef __USE_FORTIFY_LEVEL |
62 | | #define __USE_FORTIFY_LEVEL 0 |
63 | | |
64 | | /* for model 2 */ |
65 | | |
66 | | #include "ruby/internal/config.h" |
67 | | |
68 | | #ifdef __linux__ |
69 | | // Normally, gcc(1) translates calls to alloca() with inlined code. This is not done when either the -ansi, -std=c89, -std=c99, or the -std=c11 option is given and the header <alloca.h> is not included. |
70 | | # include <alloca.h> |
71 | | #endif |
72 | | |
73 | 88 | #define TH_SCHED(th) (&(th)->ractor->threads.sched) |
74 | | |
75 | | #include "eval_intern.h" |
76 | | #include "hrtime.h" |
77 | | #include "internal.h" |
78 | | #include "internal/class.h" |
79 | | #include "internal/cont.h" |
80 | | #include "internal/error.h" |
81 | | #include "internal/eval.h" |
82 | | #include "internal/gc.h" |
83 | | #include "internal/hash.h" |
84 | | #include "internal/io.h" |
85 | | #include "internal/object.h" |
86 | | #include "internal/proc.h" |
87 | | #include "ruby/fiber/scheduler.h" |
88 | | #include "internal/signal.h" |
89 | | #include "internal/thread.h" |
90 | | #include "internal/time.h" |
91 | | #include "internal/warnings.h" |
92 | | #include "iseq.h" |
93 | | #include "ruby/debug.h" |
94 | | #include "ruby/io.h" |
95 | | #include "ruby/thread.h" |
96 | | #include "ruby/thread_native.h" |
97 | | #include "timev.h" |
98 | | #include "vm_core.h" |
99 | | #include "ractor_core.h" |
100 | | #include "vm_debug.h" |
101 | | #include "vm_sync.h" |
102 | | #include "zjit.h" |
103 | | |
104 | | #include "ccan/list/list.h" |
105 | | |
106 | | #ifndef USE_NATIVE_THREAD_PRIORITY |
107 | | #define USE_NATIVE_THREAD_PRIORITY 0 |
108 | 0 | #define RUBY_THREAD_PRIORITY_MAX 3 |
109 | 0 | #define RUBY_THREAD_PRIORITY_MIN -3 |
110 | | #endif |
111 | | |
112 | | static VALUE rb_cThreadShield; |
113 | | static VALUE cThGroup; |
114 | | |
115 | | static VALUE sym_immediate; |
116 | | static VALUE sym_on_blocking; |
117 | | static VALUE sym_never; |
118 | | |
119 | | static uint32_t thread_default_quantum_ms = 100; |
120 | | |
121 | 0 | #define THREAD_LOCAL_STORAGE_INITIALISED FL_USER13 |
122 | | #define THREAD_LOCAL_STORAGE_INITIALISED_P(th) RB_FL_TEST_RAW((th), THREAD_LOCAL_STORAGE_INITIALISED) |
123 | | |
124 | | static inline VALUE |
125 | | rb_thread_local_storage(VALUE thread) |
126 | 0 | { |
127 | 0 | if (LIKELY(!THREAD_LOCAL_STORAGE_INITIALISED_P(thread))) { |
128 | 0 | rb_ivar_set(thread, idLocals, rb_hash_new()); |
129 | 0 | RB_FL_SET_RAW(thread, THREAD_LOCAL_STORAGE_INITIALISED); |
130 | 0 | } |
131 | 0 | return rb_ivar_get(thread, idLocals); |
132 | 0 | } |
133 | | |
134 | | enum SLEEP_FLAGS { |
135 | | SLEEP_DEADLOCKABLE = 0x01, |
136 | | SLEEP_SPURIOUS_CHECK = 0x02, |
137 | | SLEEP_ALLOW_SPURIOUS = 0x04, |
138 | | SLEEP_NO_CHECKINTS = 0x08, |
139 | | }; |
140 | | |
141 | | static void sleep_forever(rb_thread_t *th, unsigned int fl); |
142 | | static int sleep_hrtime(rb_thread_t *, rb_hrtime_t, unsigned int fl); |
143 | | |
144 | | static void rb_thread_sleep_deadly_allow_spurious_wakeup(VALUE blocker, VALUE timeout, rb_hrtime_t end); |
145 | | static int rb_threadptr_dead(rb_thread_t *th); |
146 | | static void rb_check_deadlock(rb_ractor_t *r); |
147 | | static int rb_threadptr_pending_interrupt_empty_p(const rb_thread_t *th); |
148 | | static const char *thread_status_name(rb_thread_t *th, int detail); |
149 | | static int hrtime_update_expire(rb_hrtime_t *, const rb_hrtime_t); |
150 | | NORETURN(static void async_bug_fd(const char *mesg, int errno_arg, int fd)); |
151 | | MAYBE_UNUSED(static int consume_communication_pipe(int fd)); |
152 | | |
153 | | static rb_atomic_t system_working = 1; |
154 | | static rb_internal_thread_specific_key_t specific_key_count; |
155 | | |
156 | | /********************************************************************************/ |
157 | | |
158 | | #define THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION |
159 | | |
160 | | struct rb_blocking_region_buffer { |
161 | | enum rb_thread_status prev_status; |
162 | | }; |
163 | | |
164 | | static int unblock_function_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg, int fail_if_interrupted); |
165 | | static void unblock_function_clear(rb_thread_t *th); |
166 | | |
167 | | static inline int blocking_region_begin(rb_thread_t *th, struct rb_blocking_region_buffer *region, |
168 | | rb_unblock_function_t *ubf, void *arg, int fail_if_interrupted); |
169 | | static inline void blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region); |
170 | | |
171 | 0 | #define THREAD_BLOCKING_BEGIN(th) do { \ |
172 | 0 | struct rb_thread_sched * const sched = TH_SCHED(th); \ |
173 | 0 | RB_VM_SAVE_MACHINE_CONTEXT(th); \ |
174 | 0 | thread_sched_to_waiting((sched), (th), true); |
175 | | |
176 | | #define THREAD_BLOCKING_END(th) \ |
177 | 0 | thread_sched_to_running((sched), (th)); \ |
178 | 0 | rb_ractor_thread_switch(th->ractor, th, false); \ |
179 | 0 | } while(0) |
180 | | |
181 | | #ifdef __GNUC__ |
182 | | #ifdef HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P |
183 | 0 | #define only_if_constant(expr, notconst) __builtin_choose_expr(__builtin_constant_p(expr), (expr), (notconst)) |
184 | | #else |
185 | | #define only_if_constant(expr, notconst) (__builtin_constant_p(expr) ? (expr) : (notconst)) |
186 | | #endif |
187 | | #else |
188 | | #define only_if_constant(expr, notconst) notconst |
189 | | #endif |
190 | 35 | #define BLOCKING_REGION(th, exec, ubf, ubfarg, fail_if_interrupted) do { \ |
191 | 35 | struct rb_blocking_region_buffer __region; \ |
192 | 35 | if (blocking_region_begin(th, &__region, (ubf), (ubfarg), fail_if_interrupted) || \ |
193 | 35 | /* always return true unless fail_if_interrupted */ \ |
194 | 35 | !only_if_constant(fail_if_interrupted, TRUE)) { \ |
195 | 35 | /* Important that this is inlined into the macro, and not part of \ |
196 | 35 | * blocking_region_begin - see bug #20493 */ \ |
197 | 35 | RB_VM_SAVE_MACHINE_CONTEXT(th); \ |
198 | 35 | thread_sched_to_waiting(TH_SCHED(th), th, false); \ |
199 | 35 | exec; \ |
200 | 35 | blocking_region_end(th, &__region); \ |
201 | 35 | }; \ |
202 | 35 | } while(0) |
203 | | |
204 | | /* |
205 | | * returns true if this thread was spuriously interrupted, false otherwise |
206 | | * (e.g. hit by Thread#run or ran a Ruby-level Signal.trap handler) |
207 | | */ |
208 | 41.1M | #define RUBY_VM_CHECK_INTS_BLOCKING(ec) vm_check_ints_blocking(ec) |
209 | | static inline int |
210 | | vm_check_ints_blocking(rb_execution_context_t *ec) |
211 | 41.1M | { |
212 | | #ifdef RUBY_ASSERT_CRITICAL_SECTION |
213 | | VM_ASSERT(ruby_assert_critical_section_entered == 0); |
214 | | #endif |
215 | | |
216 | 41.1M | rb_thread_t *th = rb_ec_thread_ptr(ec); |
217 | | |
218 | 41.1M | if (LIKELY(rb_threadptr_pending_interrupt_empty_p(th))) { |
219 | 41.1M | if (LIKELY(!RUBY_VM_INTERRUPTED_ANY(ec))) return FALSE; |
220 | 41.1M | } |
221 | 0 | else { |
222 | 0 | th->pending_interrupt_queue_checked = 0; |
223 | 0 | RUBY_VM_SET_INTERRUPT(ec); |
224 | 0 | } |
225 | | |
226 | 0 | int result = rb_threadptr_execute_interrupts(th, 1); |
227 | | |
228 | | // When a signal is received, we yield to the scheduler as soon as possible: |
229 | 0 | if (result || RUBY_VM_INTERRUPTED(ec)) { |
230 | 0 | VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); |
231 | 0 | if (scheduler != Qnil) { |
232 | 0 | rb_fiber_scheduler_yield(scheduler); |
233 | 0 | } |
234 | 0 | } |
235 | |
|
236 | 0 | return result; |
237 | 41.1M | } |
238 | | |
239 | | int |
240 | | rb_vm_check_ints_blocking(rb_execution_context_t *ec) |
241 | 0 | { |
242 | 0 | return vm_check_ints_blocking(ec); |
243 | 0 | } |
244 | | |
245 | | /* |
246 | | * poll() is supported by many OSes, but so far Linux is the only |
247 | | * one we know of that supports using poll() in all places select() |
248 | | * would work. |
249 | | */ |
250 | | #if defined(HAVE_POLL) |
251 | | # if defined(__linux__) |
252 | | # define USE_POLL |
253 | | # endif |
254 | | # if defined(__FreeBSD_version) && __FreeBSD_version >= 1100000 |
255 | | # define USE_POLL |
256 | | /* FreeBSD does not set POLLOUT when POLLHUP happens */ |
257 | | # define POLLERR_SET (POLLHUP | POLLERR) |
258 | | # endif |
259 | | #endif |
260 | | |
261 | | static void |
262 | | timeout_prepare(rb_hrtime_t **to, rb_hrtime_t *rel, rb_hrtime_t *end, |
263 | | const struct timeval *timeout) |
264 | 0 | { |
265 | 0 | if (timeout) { |
266 | 0 | *rel = rb_timeval2hrtime(timeout); |
267 | 0 | *end = rb_hrtime_add(rb_hrtime_now(), *rel); |
268 | 0 | *to = rel; |
269 | 0 | } |
270 | 0 | else { |
271 | 0 | *to = 0; |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | | MAYBE_UNUSED(NOINLINE(static int thread_start_func_2(rb_thread_t *th, VALUE *stack_start))); |
276 | | MAYBE_UNUSED(static bool th_has_dedicated_nt(const rb_thread_t *th)); |
277 | | MAYBE_UNUSED(static int waitfd_to_waiting_flag(int wfd_event)); |
278 | | |
279 | | #include THREAD_IMPL_SRC |
280 | | |
281 | | /* |
282 | | * TODO: somebody with win32 knowledge should be able to get rid of |
283 | | * timer-thread by busy-waiting on signals. And it should be possible |
284 | | * to make the GVL in thread_pthread.c be platform-independent. |
285 | | */ |
286 | | #ifndef BUSY_WAIT_SIGNALS |
287 | | # define BUSY_WAIT_SIGNALS (0) |
288 | | #endif |
289 | | |
290 | | #ifndef USE_EVENTFD |
291 | | # define USE_EVENTFD (0) |
292 | | #endif |
293 | | |
294 | | #include "thread_sync.c" |
295 | | |
296 | | void |
297 | | rb_nativethread_lock_initialize(rb_nativethread_lock_t *lock) |
298 | 0 | { |
299 | 0 | rb_native_mutex_initialize(lock); |
300 | 0 | } |
301 | | |
302 | | void |
303 | | rb_nativethread_lock_destroy(rb_nativethread_lock_t *lock) |
304 | 0 | { |
305 | 0 | rb_native_mutex_destroy(lock); |
306 | 0 | } |
307 | | |
308 | | void |
309 | | rb_nativethread_lock_lock(rb_nativethread_lock_t *lock) |
310 | 0 | { |
311 | 0 | rb_native_mutex_lock(lock); |
312 | 0 | } |
313 | | |
314 | | void |
315 | | rb_nativethread_lock_unlock(rb_nativethread_lock_t *lock) |
316 | 0 | { |
317 | 0 | rb_native_mutex_unlock(lock); |
318 | 0 | } |
319 | | |
320 | | static int |
321 | | unblock_function_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg, int fail_if_interrupted) |
322 | 35 | { |
323 | 35 | do { |
324 | 35 | if (fail_if_interrupted) { |
325 | 0 | if (RUBY_VM_INTERRUPTED_ANY(th->ec)) { |
326 | 0 | return FALSE; |
327 | 0 | } |
328 | 0 | } |
329 | 35 | else { |
330 | 35 | RUBY_VM_CHECK_INTS(th->ec); |
331 | 35 | } |
332 | | |
333 | 35 | rb_native_mutex_lock(&th->interrupt_lock); |
334 | 35 | } while (!th->ec->raised_flag && RUBY_VM_INTERRUPTED_ANY(th->ec) && |
335 | 0 | (rb_native_mutex_unlock(&th->interrupt_lock), TRUE)); |
336 | | |
337 | 35 | VM_ASSERT(th->unblock.func == NULL); |
338 | | |
339 | 35 | th->unblock.func = func; |
340 | 35 | th->unblock.arg = arg; |
341 | 35 | rb_native_mutex_unlock(&th->interrupt_lock); |
342 | | |
343 | 35 | return TRUE; |
344 | 35 | } |
345 | | |
346 | | static void |
347 | | unblock_function_clear(rb_thread_t *th) |
348 | 35 | { |
349 | 35 | rb_native_mutex_lock(&th->interrupt_lock); |
350 | 35 | th->unblock.func = 0; |
351 | 35 | rb_native_mutex_unlock(&th->interrupt_lock); |
352 | 35 | } |
353 | | |
354 | | static void |
355 | | threadptr_set_interrupt_locked(rb_thread_t *th, bool trap) |
356 | 0 | { |
357 | | // th->interrupt_lock should be acquired here |
358 | |
|
359 | 0 | RUBY_DEBUG_LOG("th:%u trap:%d", rb_th_serial(th), trap); |
360 | |
|
361 | 0 | if (trap) { |
362 | 0 | RUBY_VM_SET_TRAP_INTERRUPT(th->ec); |
363 | 0 | } |
364 | 0 | else { |
365 | 0 | RUBY_VM_SET_INTERRUPT(th->ec); |
366 | 0 | } |
367 | |
|
368 | 0 | if (th->unblock.func != NULL) { |
369 | 0 | (th->unblock.func)(th->unblock.arg); |
370 | 0 | } |
371 | 0 | else { |
372 | | /* none */ |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | | static void |
377 | | threadptr_set_interrupt(rb_thread_t *th, int trap) |
378 | 0 | { |
379 | 0 | rb_native_mutex_lock(&th->interrupt_lock); |
380 | 0 | { |
381 | 0 | threadptr_set_interrupt_locked(th, trap); |
382 | 0 | } |
383 | 0 | rb_native_mutex_unlock(&th->interrupt_lock); |
384 | 0 | } |
385 | | |
386 | | /* Set interrupt flag on another thread or current thread, and call its UBF if it has one set */ |
387 | | void |
388 | | rb_threadptr_interrupt(rb_thread_t *th) |
389 | 0 | { |
390 | 0 | RUBY_DEBUG_LOG("th:%u", rb_th_serial(th)); |
391 | 0 | threadptr_set_interrupt(th, false); |
392 | 0 | } |
393 | | |
394 | | static void |
395 | | threadptr_trap_interrupt(rb_thread_t *th) |
396 | 0 | { |
397 | 0 | threadptr_set_interrupt(th, true); |
398 | 0 | } |
399 | | |
400 | | static void |
401 | | terminate_all(rb_ractor_t *r, const rb_thread_t *main_thread) |
402 | 0 | { |
403 | 0 | rb_thread_t *th = 0; |
404 | |
|
405 | 0 | ccan_list_for_each(&r->threads.set, th, lt_node) { |
406 | 0 | if (th != main_thread) { |
407 | 0 | RUBY_DEBUG_LOG("terminate start th:%u status:%s", rb_th_serial(th), thread_status_name(th, TRUE)); |
408 | |
|
409 | 0 | rb_threadptr_pending_interrupt_enque(th, RUBY_FATAL_THREAD_TERMINATED); |
410 | 0 | rb_threadptr_interrupt(th); |
411 | |
|
412 | 0 | RUBY_DEBUG_LOG("terminate done th:%u status:%s", rb_th_serial(th), thread_status_name(th, TRUE)); |
413 | 0 | } |
414 | 0 | else { |
415 | 0 | RUBY_DEBUG_LOG("main thread th:%u", rb_th_serial(th)); |
416 | 0 | } |
417 | 0 | } |
418 | 0 | } |
419 | | |
420 | | static void |
421 | | rb_threadptr_join_list_wakeup(rb_thread_t *thread) |
422 | 0 | { |
423 | 0 | while (thread->join_list) { |
424 | 0 | struct rb_waiting_list *join_list = thread->join_list; |
425 | | |
426 | | // Consume the entry from the join list: |
427 | 0 | thread->join_list = join_list->next; |
428 | |
|
429 | 0 | rb_thread_t *target_thread = join_list->thread; |
430 | |
|
431 | 0 | if (target_thread->scheduler != Qnil && join_list->fiber) { |
432 | 0 | rb_fiber_scheduler_unblock(target_thread->scheduler, target_thread->self, rb_fiberptr_self(join_list->fiber)); |
433 | 0 | } |
434 | 0 | else { |
435 | 0 | rb_threadptr_interrupt(target_thread); |
436 | |
|
437 | 0 | switch (target_thread->status) { |
438 | 0 | case THREAD_STOPPED: |
439 | 0 | case THREAD_STOPPED_FOREVER: |
440 | 0 | target_thread->status = THREAD_RUNNABLE; |
441 | 0 | break; |
442 | 0 | default: |
443 | 0 | break; |
444 | 0 | } |
445 | 0 | } |
446 | 0 | } |
447 | 0 | } |
448 | | |
449 | | void |
450 | | rb_threadptr_unlock_all_locking_mutexes(rb_thread_t *th) |
451 | 0 | { |
452 | 0 | while (th->keeping_mutexes) { |
453 | 0 | rb_mutex_t *mutex = th->keeping_mutexes; |
454 | 0 | th->keeping_mutexes = mutex->next_mutex; |
455 | | |
456 | | // rb_warn("mutex #<%p> was not unlocked by thread #<%p>", (void *)mutex, (void*)th); |
457 | 0 | VM_ASSERT(mutex->ec_serial); |
458 | 0 | const char *error_message = rb_mutex_unlock_th(mutex, th, 0); |
459 | 0 | if (error_message) rb_bug("invalid keeping_mutexes: %s", error_message); |
460 | 0 | } |
461 | 0 | } |
462 | | |
463 | | void |
464 | | rb_thread_terminate_all(rb_thread_t *th) |
465 | 0 | { |
466 | 0 | rb_ractor_t *cr = th->ractor; |
467 | 0 | rb_execution_context_t * volatile ec = th->ec; |
468 | 0 | volatile int sleeping = 0; |
469 | |
|
470 | 0 | if (cr->threads.main != th) { |
471 | 0 | rb_bug("rb_thread_terminate_all: called by child thread (%p, %p)", |
472 | 0 | (void *)cr->threads.main, (void *)th); |
473 | 0 | } |
474 | | |
475 | | /* unlock all locking mutexes */ |
476 | 0 | rb_threadptr_unlock_all_locking_mutexes(th); |
477 | |
|
478 | 0 | EC_PUSH_TAG(ec); |
479 | 0 | if (EC_EXEC_TAG() == TAG_NONE) { |
480 | 0 | retry: |
481 | 0 | RUBY_DEBUG_LOG("th:%u", rb_th_serial(th)); |
482 | |
|
483 | 0 | terminate_all(cr, th); |
484 | |
|
485 | 0 | while (rb_ractor_living_thread_num(cr) > 1) { |
486 | 0 | rb_hrtime_t rel = RB_HRTIME_PER_SEC; |
487 | | /*q |
488 | | * Thread exiting routine in thread_start_func_2 notify |
489 | | * me when the last sub-thread exit. |
490 | | */ |
491 | 0 | sleeping = 1; |
492 | 0 | native_sleep(th, &rel); |
493 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(ec); |
494 | 0 | sleeping = 0; |
495 | 0 | } |
496 | 0 | } |
497 | 0 | else { |
498 | | /* |
499 | | * When caught an exception (e.g. Ctrl+C), let's broadcast |
500 | | * kill request again to ensure killing all threads even |
501 | | * if they are blocked on sleep, mutex, etc. |
502 | | */ |
503 | 0 | if (sleeping) { |
504 | 0 | sleeping = 0; |
505 | 0 | goto retry; |
506 | 0 | } |
507 | 0 | } |
508 | 0 | EC_POP_TAG(); |
509 | 0 | } |
510 | | |
511 | | void rb_threadptr_root_fiber_terminate(rb_thread_t *th); |
512 | | static void threadptr_interrupt_exec_cleanup(rb_thread_t *th); |
513 | | |
514 | | static void |
515 | | thread_cleanup_func_before_exec(void *th_ptr) |
516 | 0 | { |
517 | 0 | rb_thread_t *th = th_ptr; |
518 | 0 | th->status = THREAD_KILLED; |
519 | | |
520 | | // The thread stack doesn't exist in the forked process: |
521 | 0 | th->ec->machine.stack_start = th->ec->machine.stack_end = NULL; |
522 | |
|
523 | 0 | threadptr_interrupt_exec_cleanup(th); |
524 | 0 | rb_threadptr_root_fiber_terminate(th); |
525 | 0 | } |
526 | | |
527 | | static void |
528 | | thread_cleanup_func(void *th_ptr, int atfork) |
529 | 0 | { |
530 | 0 | rb_thread_t *th = th_ptr; |
531 | |
|
532 | 0 | th->locking_mutex = Qfalse; |
533 | 0 | thread_cleanup_func_before_exec(th_ptr); |
534 | |
|
535 | 0 | if (atfork) { |
536 | 0 | native_thread_destroy_atfork(th->nt); |
537 | 0 | th->nt = NULL; |
538 | 0 | return; |
539 | 0 | } |
540 | | |
541 | 0 | rb_native_mutex_destroy(&th->interrupt_lock); |
542 | 0 | } |
543 | | |
544 | | void |
545 | | rb_thread_free_native_thread(void *th_ptr) |
546 | 0 | { |
547 | 0 | rb_thread_t *th = th_ptr; |
548 | |
|
549 | 0 | native_thread_destroy_atfork(th->nt); |
550 | 0 | th->nt = NULL; |
551 | 0 | } |
552 | | |
553 | | static VALUE rb_threadptr_raise(rb_thread_t *, int, VALUE *); |
554 | | static VALUE rb_thread_to_s(VALUE thread); |
555 | | |
556 | | void |
557 | | ruby_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame) |
558 | 9 | { |
559 | 9 | native_thread_init_stack(th, local_in_parent_frame); |
560 | 9 | } |
561 | | |
562 | | const VALUE * |
563 | | rb_vm_proc_local_ep(VALUE proc) |
564 | 0 | { |
565 | 0 | const VALUE *ep = vm_proc_ep(proc); |
566 | |
|
567 | 0 | if (ep) { |
568 | 0 | return rb_vm_ep_local_ep(ep); |
569 | 0 | } |
570 | 0 | else { |
571 | 0 | return NULL; |
572 | 0 | } |
573 | 0 | } |
574 | | |
575 | | // for ractor, defined in vm.c |
576 | | VALUE rb_vm_invoke_proc_with_self(rb_execution_context_t *ec, rb_proc_t *proc, VALUE self, |
577 | | int argc, const VALUE *argv, int kw_splat, VALUE passed_block_handler); |
578 | | |
579 | | static VALUE |
580 | | thread_do_start_proc(rb_thread_t *th) |
581 | 0 | { |
582 | 0 | VALUE args = th->invoke_arg.proc.args; |
583 | 0 | const VALUE *args_ptr; |
584 | 0 | int args_len; |
585 | 0 | VALUE procval = th->invoke_arg.proc.proc; |
586 | 0 | rb_proc_t *proc; |
587 | 0 | GetProcPtr(procval, proc); |
588 | |
|
589 | 0 | th->ec->errinfo = Qnil; |
590 | 0 | th->ec->root_lep = rb_vm_proc_local_ep(procval); |
591 | 0 | th->ec->root_svar = Qfalse; |
592 | |
|
593 | 0 | vm_check_ints_blocking(th->ec); |
594 | |
|
595 | 0 | if (th->invoke_type == thread_invoke_type_ractor_proc) { |
596 | 0 | VALUE self = rb_ractor_self(th->ractor); |
597 | 0 | th->thgroup = th->ractor->thgroup_default = rb_obj_alloc(cThGroup); |
598 | |
|
599 | 0 | VM_ASSERT(FIXNUM_P(args)); |
600 | 0 | args_len = FIX2INT(args); |
601 | 0 | args_ptr = ALLOCA_N(VALUE, args_len); |
602 | 0 | rb_ractor_receive_parameters(th->ec, th->ractor, args_len, (VALUE *)args_ptr); |
603 | 0 | vm_check_ints_blocking(th->ec); |
604 | |
|
605 | 0 | return rb_vm_invoke_proc_with_self( |
606 | 0 | th->ec, proc, self, |
607 | 0 | args_len, args_ptr, |
608 | 0 | th->invoke_arg.proc.kw_splat, |
609 | 0 | VM_BLOCK_HANDLER_NONE |
610 | 0 | ); |
611 | 0 | } |
612 | 0 | else { |
613 | 0 | args_len = RARRAY_LENINT(args); |
614 | 0 | if (args_len < 8) { |
615 | | /* free proc.args if the length is enough small */ |
616 | 0 | args_ptr = ALLOCA_N(VALUE, args_len); |
617 | 0 | MEMCPY((VALUE *)args_ptr, RARRAY_CONST_PTR(args), VALUE, args_len); |
618 | 0 | th->invoke_arg.proc.args = Qnil; |
619 | 0 | } |
620 | 0 | else { |
621 | 0 | args_ptr = RARRAY_CONST_PTR(args); |
622 | 0 | } |
623 | |
|
624 | 0 | vm_check_ints_blocking(th->ec); |
625 | |
|
626 | 0 | return rb_vm_invoke_proc( |
627 | 0 | th->ec, proc, |
628 | 0 | args_len, args_ptr, |
629 | 0 | th->invoke_arg.proc.kw_splat, |
630 | 0 | VM_BLOCK_HANDLER_NONE |
631 | 0 | ); |
632 | 0 | } |
633 | 0 | } |
634 | | |
635 | | static VALUE |
636 | | thread_do_start(rb_thread_t *th) |
637 | 0 | { |
638 | 0 | native_set_thread_name(th); |
639 | 0 | VALUE result = Qundef; |
640 | |
|
641 | 0 | switch (th->invoke_type) { |
642 | 0 | case thread_invoke_type_proc: |
643 | 0 | result = thread_do_start_proc(th); |
644 | 0 | break; |
645 | | |
646 | 0 | case thread_invoke_type_ractor_proc: |
647 | 0 | result = thread_do_start_proc(th); |
648 | 0 | rb_ractor_atexit(th->ec, result); |
649 | 0 | break; |
650 | | |
651 | 0 | case thread_invoke_type_func: |
652 | 0 | result = (*th->invoke_arg.func.func)(th->invoke_arg.func.arg); |
653 | 0 | break; |
654 | | |
655 | 0 | case thread_invoke_type_none: |
656 | 0 | rb_bug("unreachable"); |
657 | 0 | } |
658 | | |
659 | 0 | return result; |
660 | 0 | } |
661 | | |
662 | | void rb_ec_clear_current_thread_trace_func(const rb_execution_context_t *ec); |
663 | | |
664 | | static int |
665 | | thread_start_func_2(rb_thread_t *th, VALUE *stack_start) |
666 | 0 | { |
667 | 0 | RUBY_DEBUG_LOG("th:%u", rb_th_serial(th)); |
668 | 0 | VM_ASSERT(th != th->vm->ractor.main_thread); |
669 | |
|
670 | 0 | enum ruby_tag_type state; |
671 | 0 | VALUE errinfo = Qnil; |
672 | 0 | rb_thread_t *ractor_main_th = th->ractor->threads.main; |
673 | | |
674 | | // setup ractor |
675 | 0 | if (rb_ractor_status_p(th->ractor, ractor_blocking)) { |
676 | 0 | RB_VM_LOCK(); |
677 | 0 | { |
678 | 0 | rb_vm_ractor_blocking_cnt_dec(th->vm, th->ractor, __FILE__, __LINE__); |
679 | 0 | rb_ractor_t *r = th->ractor; |
680 | 0 | r->r_stdin = rb_io_prep_stdin(); |
681 | 0 | r->r_stdout = rb_io_prep_stdout(); |
682 | 0 | r->r_stderr = rb_io_prep_stderr(); |
683 | 0 | } |
684 | 0 | RB_VM_UNLOCK(); |
685 | 0 | } |
686 | | |
687 | | // Ensure that we are not joinable. |
688 | 0 | VM_ASSERT(UNDEF_P(th->value)); |
689 | |
|
690 | 0 | int fiber_scheduler_closed = 0, event_thread_end_hooked = 0; |
691 | 0 | VALUE result = Qundef; |
692 | |
|
693 | 0 | EC_PUSH_TAG(th->ec); |
694 | |
|
695 | 0 | if ((state = EC_EXEC_TAG()) == TAG_NONE) { |
696 | 0 | EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_BEGIN, th->self, 0, 0, 0, Qundef); |
697 | |
|
698 | 0 | result = thread_do_start(th); |
699 | 0 | } |
700 | |
|
701 | 0 | if (!fiber_scheduler_closed) { |
702 | 0 | fiber_scheduler_closed = 1; |
703 | 0 | rb_fiber_scheduler_set(Qnil); |
704 | 0 | } |
705 | |
|
706 | 0 | if (!event_thread_end_hooked) { |
707 | 0 | event_thread_end_hooked = 1; |
708 | 0 | EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_END, th->self, 0, 0, 0, Qundef); |
709 | 0 | } |
710 | |
|
711 | 0 | if (state == TAG_NONE) { |
712 | | // This must be set AFTER doing all user-level code. At this point, the thread is effectively finished and calls to `Thread#join` will succeed. |
713 | 0 | th->value = result; |
714 | 0 | } |
715 | 0 | else { |
716 | 0 | errinfo = th->ec->errinfo; |
717 | |
|
718 | 0 | VALUE exc = rb_vm_make_jump_tag_but_local_jump(state, Qundef); |
719 | 0 | if (!NIL_P(exc)) errinfo = exc; |
720 | |
|
721 | 0 | if (state == TAG_FATAL) { |
722 | 0 | if (th->invoke_type == thread_invoke_type_ractor_proc) { |
723 | 0 | rb_ractor_atexit(th->ec, Qnil); |
724 | 0 | } |
725 | | /* fatal error within this thread, need to stop whole script */ |
726 | 0 | } |
727 | 0 | else if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) { |
728 | 0 | if (th->invoke_type == thread_invoke_type_ractor_proc) { |
729 | 0 | rb_ractor_atexit_exception(th->ec); |
730 | 0 | } |
731 | | |
732 | | /* exit on main_thread. */ |
733 | 0 | } |
734 | 0 | else { |
735 | 0 | if (th->report_on_exception) { |
736 | 0 | VALUE mesg = rb_thread_to_s(th->self); |
737 | 0 | rb_str_cat_cstr(mesg, " terminated with exception (report_on_exception is true):\n"); |
738 | 0 | rb_write_error_str(mesg); |
739 | 0 | rb_ec_error_print(th->ec, errinfo); |
740 | 0 | } |
741 | |
|
742 | 0 | if (th->invoke_type == thread_invoke_type_ractor_proc) { |
743 | 0 | rb_ractor_atexit_exception(th->ec); |
744 | 0 | } |
745 | |
|
746 | 0 | if (th->vm->thread_abort_on_exception || |
747 | 0 | th->abort_on_exception || RTEST(ruby_debug)) { |
748 | | /* exit on main_thread */ |
749 | 0 | } |
750 | 0 | else { |
751 | 0 | errinfo = Qnil; |
752 | 0 | } |
753 | 0 | } |
754 | 0 | th->value = Qnil; |
755 | 0 | } |
756 | | |
757 | | // The thread is effectively finished and can be joined. |
758 | 0 | VM_ASSERT(!UNDEF_P(th->value)); |
759 | |
|
760 | 0 | rb_threadptr_join_list_wakeup(th); |
761 | 0 | rb_threadptr_unlock_all_locking_mutexes(th); |
762 | |
|
763 | 0 | if (th->invoke_type == thread_invoke_type_ractor_proc) { |
764 | 0 | rb_thread_terminate_all(th); |
765 | 0 | rb_ractor_teardown(th->ec); |
766 | 0 | } |
767 | |
|
768 | 0 | th->status = THREAD_KILLED; |
769 | 0 | RUBY_DEBUG_LOG("killed th:%u", rb_th_serial(th)); |
770 | |
|
771 | 0 | if (th->vm->ractor.main_thread == th) { |
772 | 0 | ruby_stop(0); |
773 | 0 | } |
774 | | |
775 | 0 | if (RB_TYPE_P(errinfo, T_OBJECT)) { |
776 | | /* treat with normal error object */ |
777 | 0 | rb_threadptr_raise(ractor_main_th, 1, &errinfo); |
778 | 0 | } |
779 | |
|
780 | 0 | EC_POP_TAG(); |
781 | | |
782 | 0 | rb_ec_clear_current_thread_trace_func(th->ec); |
783 | | |
784 | | /* locking_mutex must be Qfalse */ |
785 | 0 | if (th->locking_mutex != Qfalse) { |
786 | 0 | rb_bug("thread_start_func_2: locking_mutex must not be set (%p:%"PRIxVALUE")", |
787 | 0 | (void *)th, th->locking_mutex); |
788 | 0 | } |
789 | | |
790 | 0 | if (ractor_main_th->status == THREAD_KILLED && |
791 | 0 | th->ractor->threads.cnt <= 2 /* main thread and this thread */) { |
792 | | /* I'm last thread. wake up main thread from rb_thread_terminate_all */ |
793 | 0 | rb_threadptr_interrupt(ractor_main_th); |
794 | 0 | } |
795 | |
|
796 | 0 | rb_check_deadlock(th->ractor); |
797 | |
|
798 | 0 | rb_fiber_close(th->ec->fiber_ptr); |
799 | |
|
800 | 0 | thread_cleanup_func(th, FALSE); |
801 | 0 | VM_ASSERT(th->ec->vm_stack == NULL); |
802 | |
|
803 | 0 | if (th->invoke_type == thread_invoke_type_ractor_proc) { |
804 | | // after rb_ractor_living_threads_remove() |
805 | | // GC will happen anytime and this ractor can be collected (and destroy GVL). |
806 | | // So gvl_release() should be before it. |
807 | 0 | thread_sched_to_dead(TH_SCHED(th), th); |
808 | 0 | rb_ractor_living_threads_remove(th->ractor, th); |
809 | 0 | } |
810 | 0 | else { |
811 | 0 | rb_ractor_living_threads_remove(th->ractor, th); |
812 | 0 | thread_sched_to_dead(TH_SCHED(th), th); |
813 | 0 | } |
814 | |
|
815 | 0 | return 0; |
816 | 0 | } |
817 | | |
818 | | struct thread_create_params { |
819 | | enum thread_invoke_type type; |
820 | | |
821 | | // for normal proc thread |
822 | | VALUE args; |
823 | | VALUE proc; |
824 | | |
825 | | // for ractor |
826 | | rb_ractor_t *g; |
827 | | |
828 | | // for func |
829 | | VALUE (*fn)(void *); |
830 | | }; |
831 | | |
832 | | static void thread_specific_storage_alloc(rb_thread_t *th); |
833 | | |
834 | | static VALUE |
835 | | thread_create_core(VALUE thval, struct thread_create_params *params) |
836 | 0 | { |
837 | 0 | rb_execution_context_t *ec = GET_EC(); |
838 | 0 | rb_thread_t *th = rb_thread_ptr(thval), *current_th = rb_ec_thread_ptr(ec); |
839 | 0 | int err; |
840 | |
|
841 | 0 | thread_specific_storage_alloc(th); |
842 | |
|
843 | 0 | if (OBJ_FROZEN(current_th->thgroup)) { |
844 | 0 | rb_raise(rb_eThreadError, |
845 | 0 | "can't start a new thread (frozen ThreadGroup)"); |
846 | 0 | } |
847 | | |
848 | 0 | rb_fiber_inherit_storage(ec, th->ec->fiber_ptr); |
849 | |
|
850 | 0 | switch (params->type) { |
851 | 0 | case thread_invoke_type_proc: |
852 | 0 | th->invoke_type = thread_invoke_type_proc; |
853 | 0 | th->invoke_arg.proc.args = params->args; |
854 | 0 | th->invoke_arg.proc.proc = params->proc; |
855 | 0 | th->invoke_arg.proc.kw_splat = rb_keyword_given_p(); |
856 | 0 | break; |
857 | | |
858 | 0 | case thread_invoke_type_ractor_proc: |
859 | | #if RACTOR_CHECK_MODE > 0 |
860 | | rb_ractor_setup_belonging_to(thval, rb_ractor_id(params->g)); |
861 | | #endif |
862 | 0 | th->invoke_type = thread_invoke_type_ractor_proc; |
863 | 0 | th->ractor = params->g; |
864 | 0 | th->ec->ractor_id = rb_ractor_id(th->ractor); |
865 | 0 | th->ractor->threads.main = th; |
866 | 0 | th->invoke_arg.proc.proc = rb_proc_isolate_bang(params->proc, Qnil); |
867 | 0 | th->invoke_arg.proc.args = INT2FIX(RARRAY_LENINT(params->args)); |
868 | 0 | th->invoke_arg.proc.kw_splat = rb_keyword_given_p(); |
869 | 0 | rb_ractor_send_parameters(ec, params->g, params->args); |
870 | 0 | break; |
871 | | |
872 | 0 | case thread_invoke_type_func: |
873 | 0 | th->invoke_type = thread_invoke_type_func; |
874 | 0 | th->invoke_arg.func.func = params->fn; |
875 | 0 | th->invoke_arg.func.arg = (void *)params->args; |
876 | 0 | break; |
877 | | |
878 | 0 | default: |
879 | 0 | rb_bug("unreachable"); |
880 | 0 | } |
881 | | |
882 | 0 | th->priority = current_th->priority; |
883 | 0 | th->thgroup = current_th->thgroup; |
884 | |
|
885 | 0 | th->pending_interrupt_queue = rb_ary_hidden_new(0); |
886 | 0 | th->pending_interrupt_queue_checked = 0; |
887 | 0 | th->pending_interrupt_mask_stack = rb_ary_dup(current_th->pending_interrupt_mask_stack); |
888 | 0 | RBASIC_CLEAR_CLASS(th->pending_interrupt_mask_stack); |
889 | |
|
890 | 0 | rb_native_mutex_initialize(&th->interrupt_lock); |
891 | |
|
892 | 0 | RUBY_DEBUG_LOG("r:%u th:%u", rb_ractor_id(th->ractor), rb_th_serial(th)); |
893 | |
|
894 | 0 | rb_ractor_living_threads_insert(th->ractor, th); |
895 | | |
896 | | /* kick thread */ |
897 | 0 | err = native_thread_create(th); |
898 | 0 | if (err) { |
899 | 0 | th->status = THREAD_KILLED; |
900 | 0 | rb_ractor_living_threads_remove(th->ractor, th); |
901 | 0 | rb_raise(rb_eThreadError, "can't create Thread: %s", strerror(err)); |
902 | 0 | } |
903 | 0 | return thval; |
904 | 0 | } |
905 | | |
906 | 0 | #define threadptr_initialized(th) ((th)->invoke_type != thread_invoke_type_none) |
907 | | |
908 | | /* |
909 | | * call-seq: |
910 | | * Thread.new { ... } -> thread |
911 | | * Thread.new(*args, &proc) -> thread |
912 | | * Thread.new(*args) { |args| ... } -> thread |
913 | | * |
914 | | * Creates a new thread executing the given block. |
915 | | * |
916 | | * Any +args+ given to ::new will be passed to the block: |
917 | | * |
918 | | * arr = [] |
919 | | * a, b, c = 1, 2, 3 |
920 | | * Thread.new(a,b,c) { |d,e,f| arr << d << e << f }.join |
921 | | * arr #=> [1, 2, 3] |
922 | | * |
923 | | * A ThreadError exception is raised if ::new is called without a block. |
924 | | * |
925 | | * If you're going to subclass Thread, be sure to call super in your |
926 | | * +initialize+ method, otherwise a ThreadError will be raised. |
927 | | */ |
928 | | static VALUE |
929 | | thread_s_new(int argc, VALUE *argv, VALUE klass) |
930 | 0 | { |
931 | 0 | rb_thread_t *th; |
932 | 0 | VALUE thread = rb_thread_alloc(klass); |
933 | |
|
934 | 0 | if (GET_RACTOR()->threads.main->status == THREAD_KILLED) { |
935 | 0 | rb_raise(rb_eThreadError, "can't alloc thread"); |
936 | 0 | } |
937 | | |
938 | 0 | rb_obj_call_init_kw(thread, argc, argv, RB_PASS_CALLED_KEYWORDS); |
939 | 0 | th = rb_thread_ptr(thread); |
940 | 0 | if (!threadptr_initialized(th)) { |
941 | 0 | rb_raise(rb_eThreadError, "uninitialized thread - check '%"PRIsVALUE"#initialize'", |
942 | 0 | klass); |
943 | 0 | } |
944 | 0 | return thread; |
945 | 0 | } |
946 | | |
947 | | /* |
948 | | * call-seq: |
949 | | * Thread.start([args]*) {|args| block } -> thread |
950 | | * Thread.fork([args]*) {|args| block } -> thread |
951 | | * |
952 | | * Basically the same as ::new. However, if class Thread is subclassed, then |
953 | | * calling +start+ in that subclass will not invoke the subclass's |
954 | | * +initialize+ method. |
955 | | */ |
956 | | |
957 | | static VALUE |
958 | | thread_start(VALUE klass, VALUE args) |
959 | 0 | { |
960 | 0 | struct thread_create_params params = { |
961 | 0 | .type = thread_invoke_type_proc, |
962 | 0 | .args = args, |
963 | 0 | .proc = rb_block_proc(), |
964 | 0 | }; |
965 | 0 | return thread_create_core(rb_thread_alloc(klass), ¶ms); |
966 | 0 | } |
967 | | |
968 | | static VALUE |
969 | | threadptr_invoke_proc_location(rb_thread_t *th) |
970 | 0 | { |
971 | 0 | if (th->invoke_type == thread_invoke_type_proc) { |
972 | 0 | return rb_proc_location(th->invoke_arg.proc.proc); |
973 | 0 | } |
974 | 0 | else { |
975 | 0 | return Qnil; |
976 | 0 | } |
977 | 0 | } |
978 | | |
979 | | /* :nodoc: */ |
980 | | static VALUE |
981 | | thread_initialize(VALUE thread, VALUE args) |
982 | 0 | { |
983 | 0 | rb_thread_t *th = rb_thread_ptr(thread); |
984 | |
|
985 | 0 | if (!rb_block_given_p()) { |
986 | 0 | rb_raise(rb_eThreadError, "must be called with a block"); |
987 | 0 | } |
988 | 0 | else if (th->invoke_type != thread_invoke_type_none) { |
989 | 0 | VALUE loc = threadptr_invoke_proc_location(th); |
990 | 0 | if (!NIL_P(loc)) { |
991 | 0 | rb_raise(rb_eThreadError, |
992 | 0 | "already initialized thread - %"PRIsVALUE":%"PRIsVALUE, |
993 | 0 | RARRAY_AREF(loc, 0), RARRAY_AREF(loc, 1)); |
994 | 0 | } |
995 | 0 | else { |
996 | 0 | rb_raise(rb_eThreadError, "already initialized thread"); |
997 | 0 | } |
998 | 0 | } |
999 | 0 | else { |
1000 | 0 | struct thread_create_params params = { |
1001 | 0 | .type = thread_invoke_type_proc, |
1002 | 0 | .args = args, |
1003 | 0 | .proc = rb_block_proc(), |
1004 | 0 | }; |
1005 | 0 | return thread_create_core(thread, ¶ms); |
1006 | 0 | } |
1007 | 0 | } |
1008 | | |
1009 | | VALUE |
1010 | | rb_thread_create(VALUE (*fn)(void *), void *arg) |
1011 | 0 | { |
1012 | 0 | struct thread_create_params params = { |
1013 | 0 | .type = thread_invoke_type_func, |
1014 | 0 | .fn = fn, |
1015 | 0 | .args = (VALUE)arg, |
1016 | 0 | }; |
1017 | 0 | return thread_create_core(rb_thread_alloc(rb_cThread), ¶ms); |
1018 | 0 | } |
1019 | | |
1020 | | VALUE |
1021 | | rb_thread_create_ractor(rb_ractor_t *r, VALUE args, VALUE proc) |
1022 | 0 | { |
1023 | 0 | struct thread_create_params params = { |
1024 | 0 | .type = thread_invoke_type_ractor_proc, |
1025 | 0 | .g = r, |
1026 | 0 | .args = args, |
1027 | 0 | .proc = proc, |
1028 | 0 | }; |
1029 | 0 | return thread_create_core(rb_thread_alloc(rb_cThread), ¶ms); |
1030 | 0 | } |
1031 | | |
1032 | | |
1033 | | struct join_arg { |
1034 | | struct rb_waiting_list *waiter; |
1035 | | rb_thread_t *target; |
1036 | | VALUE timeout; |
1037 | | rb_hrtime_t *limit; |
1038 | | }; |
1039 | | |
1040 | | static VALUE |
1041 | | remove_from_join_list(VALUE arg) |
1042 | 0 | { |
1043 | 0 | struct join_arg *p = (struct join_arg *)arg; |
1044 | 0 | rb_thread_t *target_thread = p->target; |
1045 | |
|
1046 | 0 | if (target_thread->status != THREAD_KILLED) { |
1047 | 0 | struct rb_waiting_list **join_list = &target_thread->join_list; |
1048 | |
|
1049 | 0 | while (*join_list) { |
1050 | 0 | if (*join_list == p->waiter) { |
1051 | 0 | *join_list = (*join_list)->next; |
1052 | 0 | break; |
1053 | 0 | } |
1054 | | |
1055 | 0 | join_list = &(*join_list)->next; |
1056 | 0 | } |
1057 | 0 | } |
1058 | |
|
1059 | 0 | return Qnil; |
1060 | 0 | } |
1061 | | |
1062 | | static int |
1063 | | thread_finished(rb_thread_t *th) |
1064 | 0 | { |
1065 | 0 | return th->status == THREAD_KILLED || !UNDEF_P(th->value); |
1066 | 0 | } |
1067 | | |
1068 | | static VALUE |
1069 | | thread_join_sleep(VALUE arg) |
1070 | 0 | { |
1071 | 0 | struct join_arg *p = (struct join_arg *)arg; |
1072 | 0 | rb_thread_t *target_th = p->target, *th = p->waiter->thread; |
1073 | 0 | rb_hrtime_t end = 0, *limit = p->limit; |
1074 | |
|
1075 | 0 | if (limit) { |
1076 | 0 | end = rb_hrtime_add(*limit, rb_hrtime_now()); |
1077 | 0 | } |
1078 | |
|
1079 | 0 | while (!thread_finished(target_th)) { |
1080 | 0 | VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); |
1081 | |
|
1082 | 0 | if (!limit) { |
1083 | 0 | if (scheduler != Qnil) { |
1084 | 0 | rb_fiber_scheduler_block(scheduler, target_th->self, Qnil); |
1085 | 0 | } |
1086 | 0 | else { |
1087 | 0 | sleep_forever(th, SLEEP_DEADLOCKABLE | SLEEP_ALLOW_SPURIOUS | SLEEP_NO_CHECKINTS); |
1088 | 0 | } |
1089 | 0 | } |
1090 | 0 | else { |
1091 | 0 | if (hrtime_update_expire(limit, end)) { |
1092 | 0 | RUBY_DEBUG_LOG("timeout target_th:%u", rb_th_serial(target_th)); |
1093 | 0 | return Qfalse; |
1094 | 0 | } |
1095 | | |
1096 | 0 | if (scheduler != Qnil) { |
1097 | 0 | VALUE timeout = rb_float_new(hrtime2double(*limit)); |
1098 | 0 | rb_fiber_scheduler_block(scheduler, target_th->self, timeout); |
1099 | 0 | } |
1100 | 0 | else { |
1101 | 0 | th->status = THREAD_STOPPED; |
1102 | 0 | native_sleep(th, limit); |
1103 | 0 | } |
1104 | 0 | } |
1105 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(th->ec); |
1106 | 0 | th->status = THREAD_RUNNABLE; |
1107 | |
|
1108 | 0 | RUBY_DEBUG_LOG("interrupted target_th:%u status:%s", rb_th_serial(target_th), thread_status_name(target_th, TRUE)); |
1109 | 0 | } |
1110 | | |
1111 | 0 | return Qtrue; |
1112 | 0 | } |
1113 | | |
1114 | | static VALUE |
1115 | | thread_join(rb_thread_t *target_th, VALUE timeout, rb_hrtime_t *limit) |
1116 | 0 | { |
1117 | 0 | rb_execution_context_t *ec = GET_EC(); |
1118 | 0 | rb_thread_t *th = ec->thread_ptr; |
1119 | 0 | rb_fiber_t *fiber = ec->fiber_ptr; |
1120 | |
|
1121 | 0 | if (th == target_th) { |
1122 | 0 | rb_raise(rb_eThreadError, "Target thread must not be current thread"); |
1123 | 0 | } |
1124 | | |
1125 | 0 | if (th->ractor->threads.main == target_th) { |
1126 | 0 | rb_raise(rb_eThreadError, "Target thread must not be main thread"); |
1127 | 0 | } |
1128 | | |
1129 | 0 | RUBY_DEBUG_LOG("target_th:%u status:%s", rb_th_serial(target_th), thread_status_name(target_th, TRUE)); |
1130 | |
|
1131 | 0 | if (target_th->status != THREAD_KILLED) { |
1132 | 0 | struct rb_waiting_list waiter; |
1133 | 0 | waiter.next = target_th->join_list; |
1134 | 0 | waiter.thread = th; |
1135 | 0 | waiter.fiber = rb_fiberptr_blocking(fiber) ? NULL : fiber; |
1136 | 0 | target_th->join_list = &waiter; |
1137 | |
|
1138 | 0 | struct join_arg arg; |
1139 | 0 | arg.waiter = &waiter; |
1140 | 0 | arg.target = target_th; |
1141 | 0 | arg.timeout = timeout; |
1142 | 0 | arg.limit = limit; |
1143 | |
|
1144 | 0 | if (!rb_ensure(thread_join_sleep, (VALUE)&arg, remove_from_join_list, (VALUE)&arg)) { |
1145 | 0 | return Qnil; |
1146 | 0 | } |
1147 | 0 | } |
1148 | | |
1149 | 0 | RUBY_DEBUG_LOG("success target_th:%u status:%s", rb_th_serial(target_th), thread_status_name(target_th, TRUE)); |
1150 | |
|
1151 | 0 | if (target_th->ec->errinfo != Qnil) { |
1152 | 0 | VALUE err = target_th->ec->errinfo; |
1153 | |
|
1154 | 0 | if (FIXNUM_P(err)) { |
1155 | 0 | switch (err) { |
1156 | 0 | case INT2FIX(TAG_FATAL): |
1157 | 0 | RUBY_DEBUG_LOG("terminated target_th:%u status:%s", rb_th_serial(target_th), thread_status_name(target_th, TRUE)); |
1158 | | |
1159 | | /* OK. killed. */ |
1160 | 0 | break; |
1161 | 0 | default: |
1162 | 0 | if (err == RUBY_FATAL_FIBER_KILLED) { // not integer constant so can't be a case expression |
1163 | | // root fiber killed in non-main thread |
1164 | 0 | break; |
1165 | 0 | } |
1166 | 0 | rb_bug("thread_join: Fixnum (%d) should not reach here.", FIX2INT(err)); |
1167 | 0 | } |
1168 | 0 | } |
1169 | 0 | else if (THROW_DATA_P(target_th->ec->errinfo)) { |
1170 | 0 | rb_bug("thread_join: THROW_DATA should not reach here."); |
1171 | 0 | } |
1172 | 0 | else { |
1173 | | /* normal exception */ |
1174 | 0 | rb_exc_raise(err); |
1175 | 0 | } |
1176 | 0 | } |
1177 | 0 | return target_th->self; |
1178 | 0 | } |
1179 | | |
1180 | | /* |
1181 | | * call-seq: |
1182 | | * thr.join -> thr |
1183 | | * thr.join(limit) -> thr |
1184 | | * |
1185 | | * The calling thread will suspend execution and run this +thr+. |
1186 | | * |
1187 | | * Does not return until +thr+ exits or until the given +limit+ seconds have |
1188 | | * passed. |
1189 | | * |
1190 | | * If the time limit expires, +nil+ will be returned, otherwise +thr+ is |
1191 | | * returned. |
1192 | | * |
1193 | | * Any threads not joined will be killed when the main program exits. |
1194 | | * |
1195 | | * If +thr+ had previously raised an exception and the ::abort_on_exception or |
1196 | | * $DEBUG flags are not set, (so the exception has not yet been processed), it |
1197 | | * will be processed at this time. |
1198 | | * |
1199 | | * a = Thread.new { print "a"; sleep(10); print "b"; print "c" } |
1200 | | * x = Thread.new { print "x"; Thread.pass; print "y"; print "z" } |
1201 | | * x.join # Let thread x finish, thread a will be killed on exit. |
1202 | | * #=> "axyz" |
1203 | | * |
1204 | | * The following example illustrates the +limit+ parameter. |
1205 | | * |
1206 | | * y = Thread.new { 4.times { sleep 0.1; puts 'tick... ' }} |
1207 | | * puts "Waiting" until y.join(0.15) |
1208 | | * |
1209 | | * This will produce: |
1210 | | * |
1211 | | * tick... |
1212 | | * Waiting |
1213 | | * tick... |
1214 | | * Waiting |
1215 | | * tick... |
1216 | | * tick... |
1217 | | */ |
1218 | | |
1219 | | static VALUE |
1220 | | thread_join_m(int argc, VALUE *argv, VALUE self) |
1221 | 0 | { |
1222 | 0 | VALUE timeout = Qnil; |
1223 | 0 | rb_hrtime_t rel = 0, *limit = 0; |
1224 | |
|
1225 | 0 | if (rb_check_arity(argc, 0, 1)) { |
1226 | 0 | timeout = argv[0]; |
1227 | 0 | } |
1228 | | |
1229 | | // Convert the timeout eagerly, so it's always converted and deterministic |
1230 | | /* |
1231 | | * This supports INFINITY and negative values, so we can't use |
1232 | | * rb_time_interval right now... |
1233 | | */ |
1234 | 0 | if (NIL_P(timeout)) { |
1235 | | /* unlimited */ |
1236 | 0 | } |
1237 | 0 | else if (FIXNUM_P(timeout)) { |
1238 | 0 | rel = rb_sec2hrtime(NUM2TIMET(timeout)); |
1239 | 0 | limit = &rel; |
1240 | 0 | } |
1241 | 0 | else { |
1242 | 0 | limit = double2hrtime(&rel, rb_num2dbl(timeout)); |
1243 | 0 | } |
1244 | |
|
1245 | 0 | return thread_join(rb_thread_ptr(self), timeout, limit); |
1246 | 0 | } |
1247 | | |
1248 | | /* |
1249 | | * call-seq: |
1250 | | * thr.value -> obj |
1251 | | * |
1252 | | * Waits for +thr+ to complete, using #join, and returns its value or raises |
1253 | | * the exception which terminated the thread. |
1254 | | * |
1255 | | * a = Thread.new { 2 + 2 } |
1256 | | * a.value #=> 4 |
1257 | | * |
1258 | | * b = Thread.new { raise 'something went wrong' } |
1259 | | * b.value #=> RuntimeError: something went wrong |
1260 | | */ |
1261 | | |
1262 | | static VALUE |
1263 | | thread_value(VALUE self) |
1264 | 0 | { |
1265 | 0 | rb_thread_t *th = rb_thread_ptr(self); |
1266 | 0 | thread_join(th, Qnil, 0); |
1267 | 0 | if (UNDEF_P(th->value)) { |
1268 | | // If the thread is dead because we forked th->value is still Qundef. |
1269 | 0 | return Qnil; |
1270 | 0 | } |
1271 | 0 | return th->value; |
1272 | 0 | } |
1273 | | |
1274 | | /* |
1275 | | * Thread Scheduling |
1276 | | */ |
1277 | | |
1278 | | static void |
1279 | | getclockofday(struct timespec *ts) |
1280 | 19.0M | { |
1281 | 19.0M | #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) |
1282 | 19.0M | if (clock_gettime(CLOCK_MONOTONIC, ts) == 0) |
1283 | 19.0M | return; |
1284 | 0 | #endif |
1285 | 0 | rb_timespec_now(ts); |
1286 | 0 | } |
1287 | | |
1288 | | /* |
1289 | | * Don't inline this, since library call is already time consuming |
1290 | | * and we don't want "struct timespec" on stack too long for GC |
1291 | | */ |
1292 | | NOINLINE(rb_hrtime_t rb_hrtime_now(void)); |
1293 | | rb_hrtime_t |
1294 | | rb_hrtime_now(void) |
1295 | 19.0M | { |
1296 | 19.0M | struct timespec ts; |
1297 | | |
1298 | 19.0M | getclockofday(&ts); |
1299 | 19.0M | return rb_timespec2hrtime(&ts); |
1300 | 19.0M | } |
1301 | | |
1302 | | /* |
1303 | | * at least gcc 7.2 and 7.3 complains about "rb_hrtime_t end" |
1304 | | * being uninitialized, maybe other versions, too. |
1305 | | */ |
1306 | | COMPILER_WARNING_PUSH |
1307 | | #if defined(__GNUC__) && __GNUC__ == 7 && __GNUC_MINOR__ <= 3 |
1308 | | COMPILER_WARNING_IGNORED(-Wmaybe-uninitialized) |
1309 | | #endif |
1310 | | #ifndef PRIu64 |
1311 | | #define PRIu64 PRI_64_PREFIX "u" |
1312 | | #endif |
1313 | | /* |
1314 | | * @end is the absolute time when @ts is set to expire |
1315 | | * Returns true if @end has past |
1316 | | * Updates @ts and returns false otherwise |
1317 | | */ |
1318 | | static int |
1319 | | hrtime_update_expire(rb_hrtime_t *timeout, const rb_hrtime_t end) |
1320 | 0 | { |
1321 | 0 | rb_hrtime_t now = rb_hrtime_now(); |
1322 | |
|
1323 | 0 | if (now > end) return 1; |
1324 | | |
1325 | 0 | RUBY_DEBUG_LOG("%"PRIu64" > %"PRIu64"", (uint64_t)end, (uint64_t)now); |
1326 | |
|
1327 | 0 | *timeout = end - now; |
1328 | 0 | return 0; |
1329 | 0 | } |
1330 | | COMPILER_WARNING_POP |
1331 | | |
1332 | | static int |
1333 | | sleep_hrtime(rb_thread_t *th, rb_hrtime_t rel, unsigned int fl) |
1334 | 0 | { |
1335 | 0 | enum rb_thread_status prev_status = th->status; |
1336 | 0 | int woke; |
1337 | 0 | rb_hrtime_t end = rb_hrtime_add(rb_hrtime_now(), rel); |
1338 | |
|
1339 | 0 | th->status = THREAD_STOPPED; |
1340 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(th->ec); |
1341 | 0 | while (th->status == THREAD_STOPPED) { |
1342 | 0 | native_sleep(th, &rel); |
1343 | 0 | woke = vm_check_ints_blocking(th->ec); |
1344 | 0 | if (woke && !(fl & SLEEP_SPURIOUS_CHECK)) |
1345 | 0 | break; |
1346 | 0 | if (hrtime_update_expire(&rel, end)) |
1347 | 0 | break; |
1348 | 0 | woke = 1; |
1349 | 0 | } |
1350 | 0 | th->status = prev_status; |
1351 | 0 | return woke; |
1352 | 0 | } |
1353 | | |
1354 | | static int |
1355 | | sleep_hrtime_until(rb_thread_t *th, rb_hrtime_t end, unsigned int fl) |
1356 | 0 | { |
1357 | 0 | enum rb_thread_status prev_status = th->status; |
1358 | 0 | int woke; |
1359 | 0 | rb_hrtime_t rel = rb_hrtime_sub(end, rb_hrtime_now()); |
1360 | |
|
1361 | 0 | th->status = THREAD_STOPPED; |
1362 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(th->ec); |
1363 | 0 | while (th->status == THREAD_STOPPED) { |
1364 | 0 | native_sleep(th, &rel); |
1365 | 0 | woke = vm_check_ints_blocking(th->ec); |
1366 | 0 | if (woke && !(fl & SLEEP_SPURIOUS_CHECK)) |
1367 | 0 | break; |
1368 | 0 | if (hrtime_update_expire(&rel, end)) |
1369 | 0 | break; |
1370 | 0 | woke = 1; |
1371 | 0 | } |
1372 | 0 | th->status = prev_status; |
1373 | 0 | return woke; |
1374 | 0 | } |
1375 | | |
1376 | | static void |
1377 | | sleep_forever(rb_thread_t *th, unsigned int fl) |
1378 | 0 | { |
1379 | 0 | enum rb_thread_status prev_status = th->status; |
1380 | 0 | enum rb_thread_status status; |
1381 | 0 | int woke; |
1382 | |
|
1383 | 0 | status = fl & SLEEP_DEADLOCKABLE ? THREAD_STOPPED_FOREVER : THREAD_STOPPED; |
1384 | 0 | th->status = status; |
1385 | |
|
1386 | 0 | if (!(fl & SLEEP_NO_CHECKINTS)) RUBY_VM_CHECK_INTS_BLOCKING(th->ec); |
1387 | |
|
1388 | 0 | while (th->status == status) { |
1389 | 0 | if (fl & SLEEP_DEADLOCKABLE) { |
1390 | 0 | rb_ractor_sleeper_threads_inc(th->ractor); |
1391 | 0 | rb_check_deadlock(th->ractor); |
1392 | 0 | } |
1393 | 0 | { |
1394 | 0 | native_sleep(th, 0); |
1395 | 0 | } |
1396 | 0 | if (fl & SLEEP_DEADLOCKABLE) { |
1397 | 0 | rb_ractor_sleeper_threads_dec(th->ractor); |
1398 | 0 | } |
1399 | 0 | if (fl & SLEEP_ALLOW_SPURIOUS) { |
1400 | 0 | break; |
1401 | 0 | } |
1402 | | |
1403 | 0 | woke = vm_check_ints_blocking(th->ec); |
1404 | |
|
1405 | 0 | if (woke && !(fl & SLEEP_SPURIOUS_CHECK)) { |
1406 | 0 | break; |
1407 | 0 | } |
1408 | 0 | } |
1409 | 0 | th->status = prev_status; |
1410 | 0 | } |
1411 | | |
1412 | | void |
1413 | | rb_thread_sleep_forever(void) |
1414 | 0 | { |
1415 | 0 | RUBY_DEBUG_LOG("forever"); |
1416 | 0 | sleep_forever(GET_THREAD(), SLEEP_SPURIOUS_CHECK); |
1417 | 0 | } |
1418 | | |
1419 | | void |
1420 | | rb_thread_sleep_deadly(void) |
1421 | 0 | { |
1422 | 0 | RUBY_DEBUG_LOG("deadly"); |
1423 | 0 | sleep_forever(GET_THREAD(), SLEEP_DEADLOCKABLE|SLEEP_SPURIOUS_CHECK); |
1424 | 0 | } |
1425 | | |
1426 | | static void |
1427 | | rb_thread_sleep_deadly_allow_spurious_wakeup(VALUE blocker, VALUE timeout, rb_hrtime_t end) |
1428 | 0 | { |
1429 | 0 | rb_thread_t *th = GET_THREAD(); |
1430 | 0 | VALUE scheduler = rb_fiber_scheduler_current_for_threadptr(th); |
1431 | 0 | if (scheduler != Qnil) { |
1432 | 0 | rb_fiber_scheduler_block(scheduler, blocker, timeout); |
1433 | 0 | } |
1434 | 0 | else { |
1435 | 0 | RUBY_DEBUG_LOG("..."); |
1436 | 0 | if (end) { |
1437 | 0 | sleep_hrtime_until(th, end, SLEEP_SPURIOUS_CHECK); |
1438 | 0 | } |
1439 | 0 | else { |
1440 | 0 | sleep_forever(th, SLEEP_DEADLOCKABLE); |
1441 | 0 | } |
1442 | 0 | } |
1443 | 0 | } |
1444 | | |
1445 | | void |
1446 | | rb_thread_wait_for(struct timeval time) |
1447 | 0 | { |
1448 | 0 | rb_thread_t *th = GET_THREAD(); |
1449 | |
|
1450 | 0 | sleep_hrtime(th, rb_timeval2hrtime(&time), SLEEP_SPURIOUS_CHECK); |
1451 | 0 | } |
1452 | | |
1453 | | void |
1454 | | rb_ec_check_ints(rb_execution_context_t *ec) |
1455 | 41.1M | { |
1456 | 41.1M | RUBY_VM_CHECK_INTS_BLOCKING(ec); |
1457 | 41.1M | } |
1458 | | |
1459 | | /* |
1460 | | * CAUTION: This function causes thread switching. |
1461 | | * rb_thread_check_ints() check ruby's interrupts. |
1462 | | * some interrupt needs thread switching/invoke handlers, |
1463 | | * and so on. |
1464 | | */ |
1465 | | |
1466 | | void |
1467 | | rb_thread_check_ints(void) |
1468 | 41.1M | { |
1469 | 41.1M | rb_ec_check_ints(GET_EC()); |
1470 | 41.1M | } |
1471 | | |
1472 | | /* |
1473 | | * Hidden API for tcl/tk wrapper. |
1474 | | * There is no guarantee to perpetuate it. |
1475 | | */ |
1476 | | int |
1477 | | rb_thread_check_trap_pending(void) |
1478 | 0 | { |
1479 | 0 | return rb_signal_buff_size() != 0; |
1480 | 0 | } |
1481 | | |
1482 | | /* This function can be called in blocking region. */ |
1483 | | int |
1484 | | rb_thread_interrupted(VALUE thval) |
1485 | 0 | { |
1486 | 0 | return (int)RUBY_VM_INTERRUPTED(rb_thread_ptr(thval)->ec); |
1487 | 0 | } |
1488 | | |
1489 | | void |
1490 | | rb_thread_sleep(int sec) |
1491 | 0 | { |
1492 | 0 | rb_thread_wait_for(rb_time_timeval(INT2FIX(sec))); |
1493 | 0 | } |
1494 | | |
1495 | | static void |
1496 | | rb_thread_schedule_limits(uint32_t limits_us) |
1497 | 0 | { |
1498 | 0 | if (!rb_thread_alone()) { |
1499 | 0 | rb_thread_t *th = GET_THREAD(); |
1500 | 0 | RUBY_DEBUG_LOG("us:%u", (unsigned int)limits_us); |
1501 | |
|
1502 | 0 | if (th->running_time_us >= limits_us) { |
1503 | 0 | RUBY_DEBUG_LOG("switch %s", "start"); |
1504 | |
|
1505 | 0 | RB_VM_SAVE_MACHINE_CONTEXT(th); |
1506 | 0 | thread_sched_yield(TH_SCHED(th), th); |
1507 | 0 | rb_ractor_thread_switch(th->ractor, th, true); |
1508 | |
|
1509 | 0 | RUBY_DEBUG_LOG("switch %s", "done"); |
1510 | 0 | } |
1511 | 0 | } |
1512 | 0 | } |
1513 | | |
1514 | | void |
1515 | | rb_thread_schedule(void) |
1516 | 0 | { |
1517 | 0 | rb_thread_schedule_limits(0); |
1518 | 0 | RUBY_VM_CHECK_INTS(GET_EC()); |
1519 | 0 | } |
1520 | | |
1521 | | /* blocking region */ |
1522 | | |
1523 | | static inline int |
1524 | | blocking_region_begin(rb_thread_t *th, struct rb_blocking_region_buffer *region, |
1525 | | rb_unblock_function_t *ubf, void *arg, int fail_if_interrupted) |
1526 | 35 | { |
1527 | | #ifdef RUBY_ASSERT_CRITICAL_SECTION |
1528 | | VM_ASSERT(ruby_assert_critical_section_entered == 0); |
1529 | | #endif |
1530 | 35 | VM_ASSERT(th == GET_THREAD()); |
1531 | | |
1532 | 35 | region->prev_status = th->status; |
1533 | 35 | if (unblock_function_set(th, ubf, arg, fail_if_interrupted)) { |
1534 | 35 | th->blocking_region_buffer = region; |
1535 | 35 | th->status = THREAD_STOPPED; |
1536 | 35 | rb_ractor_blocking_threads_inc(th->ractor, __FILE__, __LINE__); |
1537 | | |
1538 | 35 | RUBY_DEBUG_LOG("thread_id:%p", (void *)th->nt->thread_id); |
1539 | 35 | return TRUE; |
1540 | 35 | } |
1541 | 0 | else { |
1542 | 0 | return FALSE; |
1543 | 0 | } |
1544 | 35 | } |
1545 | | |
1546 | | static inline void |
1547 | | blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region) |
1548 | 35 | { |
1549 | | /* entry to ubf_list still permitted at this point, make it impossible: */ |
1550 | 35 | unblock_function_clear(th); |
1551 | | /* entry to ubf_list impossible at this point, so unregister is safe: */ |
1552 | 35 | unregister_ubf_list(th); |
1553 | | |
1554 | 35 | thread_sched_to_running(TH_SCHED(th), th); |
1555 | 35 | rb_ractor_thread_switch(th->ractor, th, false); |
1556 | | |
1557 | 35 | th->blocking_region_buffer = 0; |
1558 | 35 | rb_ractor_blocking_threads_dec(th->ractor, __FILE__, __LINE__); |
1559 | 35 | if (th->status == THREAD_STOPPED) { |
1560 | 35 | th->status = region->prev_status; |
1561 | 35 | } |
1562 | | |
1563 | 35 | RUBY_DEBUG_LOG("end"); |
1564 | | |
1565 | 35 | #ifndef _WIN32 |
1566 | | // GET_THREAD() clears WSAGetLastError() |
1567 | 35 | VM_ASSERT(th == GET_THREAD()); |
1568 | 35 | #endif |
1569 | 35 | } |
1570 | | |
1571 | | /* |
1572 | | * Resolve sentinel unblock function values to their actual function pointers |
1573 | | * and appropriate data2 values. This centralizes the logic for handling |
1574 | | * RUBY_UBF_IO and RUBY_UBF_PROCESS sentinel values. |
1575 | | * |
1576 | | * @param unblock_function Pointer to unblock function pointer (modified in place) |
1577 | | * @param data2 Pointer to data2 pointer (modified in place) |
1578 | | * @param thread Thread context for resolving data2 when needed |
1579 | | * @return true if sentinel values were resolved, false otherwise |
1580 | | */ |
1581 | | bool |
1582 | | rb_thread_resolve_unblock_function(rb_unblock_function_t **unblock_function, void **data2, struct rb_thread_struct *thread) |
1583 | 35 | { |
1584 | 35 | rb_unblock_function_t *ubf = *unblock_function; |
1585 | | |
1586 | 35 | if ((ubf == RUBY_UBF_IO) || (ubf == RUBY_UBF_PROCESS)) { |
1587 | 35 | *unblock_function = ubf_select; |
1588 | 35 | *data2 = thread; |
1589 | 35 | return true; |
1590 | 35 | } |
1591 | 0 | return false; |
1592 | 35 | } |
1593 | | |
1594 | | void * |
1595 | | rb_nogvl(void *(*func)(void *), void *data1, |
1596 | | rb_unblock_function_t *ubf, void *data2, |
1597 | | int flags) |
1598 | 35 | { |
1599 | 35 | if (flags & RB_NOGVL_OFFLOAD_SAFE) { |
1600 | 35 | VALUE scheduler = rb_fiber_scheduler_current(); |
1601 | 35 | if (scheduler != Qnil) { |
1602 | 0 | struct rb_fiber_scheduler_blocking_operation_state state = {0}; |
1603 | |
|
1604 | 0 | VALUE result = rb_fiber_scheduler_blocking_operation_wait(scheduler, func, data1, ubf, data2, flags, &state); |
1605 | |
|
1606 | 0 | if (!UNDEF_P(result)) { |
1607 | 0 | rb_errno_set(state.saved_errno); |
1608 | 0 | return state.result; |
1609 | 0 | } |
1610 | 0 | } |
1611 | 35 | } |
1612 | | |
1613 | 35 | void *val = 0; |
1614 | 35 | rb_execution_context_t *ec = GET_EC(); |
1615 | 35 | rb_thread_t *th = rb_ec_thread_ptr(ec); |
1616 | 35 | rb_vm_t *vm = rb_ec_vm_ptr(ec); |
1617 | 35 | bool is_main_thread = vm->ractor.main_thread == th; |
1618 | 35 | int saved_errno = 0; |
1619 | | |
1620 | 35 | rb_thread_resolve_unblock_function(&ubf, &data2, th); |
1621 | | |
1622 | 35 | if (ubf && rb_ractor_living_thread_num(th->ractor) == 1 && is_main_thread) { |
1623 | 35 | if (flags & RB_NOGVL_UBF_ASYNC_SAFE) { |
1624 | 0 | vm->ubf_async_safe = 1; |
1625 | 0 | } |
1626 | 35 | } |
1627 | | |
1628 | 35 | rb_vm_t *volatile saved_vm = vm; |
1629 | 35 | BLOCKING_REGION(th, { |
1630 | 35 | val = func(data1); |
1631 | 35 | saved_errno = rb_errno(); |
1632 | 35 | }, ubf, data2, flags & RB_NOGVL_INTR_FAIL); |
1633 | 35 | vm = saved_vm; |
1634 | | |
1635 | 35 | if (is_main_thread) vm->ubf_async_safe = 0; |
1636 | | |
1637 | 35 | if ((flags & RB_NOGVL_INTR_FAIL) == 0) { |
1638 | 35 | RUBY_VM_CHECK_INTS_BLOCKING(ec); |
1639 | 35 | } |
1640 | | |
1641 | 35 | rb_errno_set(saved_errno); |
1642 | | |
1643 | 35 | return val; |
1644 | 35 | } |
1645 | | |
1646 | | /* |
1647 | | * rb_thread_call_without_gvl - permit concurrent/parallel execution. |
1648 | | * rb_thread_call_without_gvl2 - permit concurrent/parallel execution |
1649 | | * without interrupt process. |
1650 | | * |
1651 | | * rb_thread_call_without_gvl() does: |
1652 | | * (1) Check interrupts. |
1653 | | * (2) release GVL. |
1654 | | * Other Ruby threads may run in parallel. |
1655 | | * (3) call func with data1 |
1656 | | * (4) acquire GVL. |
1657 | | * Other Ruby threads can not run in parallel any more. |
1658 | | * (5) Check interrupts. |
1659 | | * |
1660 | | * rb_thread_call_without_gvl2() does: |
1661 | | * (1) Check interrupt and return if interrupted. |
1662 | | * (2) release GVL. |
1663 | | * (3) call func with data1 and a pointer to the flags. |
1664 | | * (4) acquire GVL. |
1665 | | * |
1666 | | * If another thread interrupts this thread (Thread#kill, signal delivery, |
1667 | | * VM-shutdown request, and so on), `ubf()' is called (`ubf()' means |
1668 | | * "un-blocking function"). `ubf()' should interrupt `func()' execution by |
1669 | | * toggling a cancellation flag, canceling the invocation of a call inside |
1670 | | * `func()' or similar. Note that `ubf()' may not be called with the GVL. |
1671 | | * |
1672 | | * There are built-in ubfs and you can specify these ubfs: |
1673 | | * |
1674 | | * * RUBY_UBF_IO: ubf for IO operation |
1675 | | * * RUBY_UBF_PROCESS: ubf for process operation |
1676 | | * |
1677 | | * However, we can not guarantee our built-in ubfs interrupt your `func()' |
1678 | | * correctly. Be careful to use rb_thread_call_without_gvl(). If you don't |
1679 | | * provide proper ubf(), your program will not stop for Control+C or other |
1680 | | * shutdown events. |
1681 | | * |
1682 | | * "Check interrupts" on above list means checking asynchronous |
1683 | | * interrupt events (such as Thread#kill, signal delivery, VM-shutdown |
1684 | | * request, and so on) and calling corresponding procedures |
1685 | | * (such as `trap' for signals, raise an exception for Thread#raise). |
1686 | | * If `func()' finished and received interrupts, you may skip interrupt |
1687 | | * checking. For example, assume the following func() it reads data from file. |
1688 | | * |
1689 | | * read_func(...) { |
1690 | | * // (a) before read |
1691 | | * read(buffer); // (b) reading |
1692 | | * // (c) after read |
1693 | | * } |
1694 | | * |
1695 | | * If an interrupt occurs at (a) or (b), then `ubf()' cancels this |
1696 | | * `read_func()' and interrupts are checked. However, if an interrupt occurs |
1697 | | * at (c), after *read* operation is completed, checking interrupts is harmful |
1698 | | * because it causes irrevocable side-effect, the read data will vanish. To |
1699 | | * avoid such problem, the `read_func()' should be used with |
1700 | | * `rb_thread_call_without_gvl2()'. |
1701 | | * |
1702 | | * If `rb_thread_call_without_gvl2()' detects interrupt, it returns |
1703 | | * immediately. This function does not show when the execution was interrupted. |
1704 | | * For example, there are 4 possible timing (a), (b), (c) and before calling |
1705 | | * read_func(). You need to record progress of a read_func() and check |
1706 | | * the progress after `rb_thread_call_without_gvl2()'. You may need to call |
1707 | | * `rb_thread_check_ints()' correctly or your program can not process proper |
1708 | | * process such as `trap' and so on. |
1709 | | * |
1710 | | * NOTE: You can not execute most of Ruby C API and touch Ruby |
1711 | | * objects in `func()' and `ubf()', including raising an |
1712 | | * exception, because current thread doesn't acquire GVL |
1713 | | * (it causes synchronization problems). If you need to |
1714 | | * call ruby functions either use rb_thread_call_with_gvl() |
1715 | | * or read source code of C APIs and confirm safety by |
1716 | | * yourself. |
1717 | | * |
1718 | | * NOTE: In short, this API is difficult to use safely. I recommend you |
1719 | | * use other ways if you have. We lack experiences to use this API. |
1720 | | * Please report your problem related on it. |
1721 | | * |
1722 | | * NOTE: Releasing GVL and re-acquiring GVL may be expensive operations |
1723 | | * for a short running `func()'. Be sure to benchmark and use this |
1724 | | * mechanism when `func()' consumes enough time. |
1725 | | * |
1726 | | * Safe C API: |
1727 | | * * rb_thread_interrupted() - check interrupt flag |
1728 | | * * ruby_xmalloc(), ruby_xrealloc(), ruby_xfree() - |
1729 | | * they will work without GVL, and may acquire GVL when GC is needed. |
1730 | | */ |
1731 | | void * |
1732 | | rb_thread_call_without_gvl2(void *(*func)(void *), void *data1, |
1733 | | rb_unblock_function_t *ubf, void *data2) |
1734 | 0 | { |
1735 | 0 | return rb_nogvl(func, data1, ubf, data2, RB_NOGVL_INTR_FAIL); |
1736 | 0 | } |
1737 | | |
1738 | | void * |
1739 | | rb_thread_call_without_gvl(void *(*func)(void *data), void *data1, |
1740 | | rb_unblock_function_t *ubf, void *data2) |
1741 | 0 | { |
1742 | 0 | return rb_nogvl(func, data1, ubf, data2, 0); |
1743 | 0 | } |
1744 | | |
1745 | | static int |
1746 | | waitfd_to_waiting_flag(int wfd_event) |
1747 | 0 | { |
1748 | 0 | return wfd_event << 1; |
1749 | 0 | } |
1750 | | |
1751 | | static struct ccan_list_head * |
1752 | | rb_io_blocking_operations(struct rb_io *io) |
1753 | 0 | { |
1754 | 0 | rb_serial_t fork_generation = GET_VM()->fork_gen; |
1755 | | |
1756 | | // On fork, all existing entries in this list (which are stack allocated) become invalid. |
1757 | | // Therefore, we re-initialize the list which clears it. |
1758 | 0 | if (io->fork_generation != fork_generation) { |
1759 | 0 | ccan_list_head_init(&io->blocking_operations); |
1760 | 0 | io->fork_generation = fork_generation; |
1761 | 0 | } |
1762 | |
|
1763 | 0 | return &io->blocking_operations; |
1764 | 0 | } |
1765 | | |
1766 | | /* |
1767 | | * Registers a blocking operation for an IO object. This is used to track all threads and fibers |
1768 | | * that are currently blocked on this IO for reading, writing or other operations. |
1769 | | * |
1770 | | * When the IO is closed, all blocking operations will be notified via rb_fiber_scheduler_fiber_interrupt |
1771 | | * for fibers with a scheduler, or via rb_threadptr_interrupt for threads without a scheduler. |
1772 | | * |
1773 | | * @parameter io The IO object on which the operation will block |
1774 | | * @parameter blocking_operation The operation details including the execution context that will be blocked |
1775 | | */ |
1776 | | static void |
1777 | | rb_io_blocking_operation_enter(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation) |
1778 | 0 | { |
1779 | 0 | ccan_list_add(rb_io_blocking_operations(io), &blocking_operation->list); |
1780 | 0 | } |
1781 | | |
1782 | | static void |
1783 | | rb_io_blocking_operation_pop(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation) |
1784 | 0 | { |
1785 | 0 | ccan_list_del(&blocking_operation->list); |
1786 | 0 | } |
1787 | | |
1788 | | struct io_blocking_operation_arguments { |
1789 | | struct rb_io *io; |
1790 | | struct rb_io_blocking_operation *blocking_operation; |
1791 | | }; |
1792 | | |
1793 | | static VALUE |
1794 | | io_blocking_operation_exit(VALUE _arguments) |
1795 | 0 | { |
1796 | 0 | struct io_blocking_operation_arguments *arguments = (void*)_arguments; |
1797 | 0 | struct rb_io_blocking_operation *blocking_operation = arguments->blocking_operation; |
1798 | |
|
1799 | 0 | rb_io_blocking_operation_pop(arguments->io, blocking_operation); |
1800 | |
|
1801 | 0 | rb_io_t *io = arguments->io; |
1802 | 0 | rb_thread_t *thread = io->closing_ec->thread_ptr; |
1803 | 0 | rb_fiber_t *fiber = io->closing_ec->fiber_ptr; |
1804 | |
|
1805 | 0 | if (thread->scheduler != Qnil) { |
1806 | | // This can cause spurious wakeups... |
1807 | 0 | rb_fiber_scheduler_unblock(thread->scheduler, io->self, rb_fiberptr_self(fiber)); |
1808 | 0 | } |
1809 | 0 | else { |
1810 | 0 | rb_thread_wakeup(thread->self); |
1811 | 0 | } |
1812 | |
|
1813 | 0 | return Qnil; |
1814 | 0 | } |
1815 | | |
1816 | | /* |
1817 | | * Called when a blocking operation completes or is interrupted. Removes the operation from |
1818 | | * the IO's blocking_operations list and wakes up any waiting threads/fibers. |
1819 | | * |
1820 | | * If there's a wakeup_mutex (meaning an IO close is in progress), synchronizes the cleanup |
1821 | | * through that mutex to ensure proper coordination with the closing thread. |
1822 | | * |
1823 | | * @parameter io The IO object the operation was performed on |
1824 | | * @parameter blocking_operation The completed operation to clean up |
1825 | | */ |
1826 | | static void |
1827 | | rb_io_blocking_operation_exit(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation) |
1828 | 0 | { |
1829 | 0 | VALUE wakeup_mutex = io->wakeup_mutex; |
1830 | | |
1831 | | // Indicate that the blocking operation is no longer active: |
1832 | 0 | blocking_operation->ec = NULL; |
1833 | |
|
1834 | 0 | if (RB_TEST(wakeup_mutex)) { |
1835 | 0 | struct io_blocking_operation_arguments arguments = { |
1836 | 0 | .io = io, |
1837 | 0 | .blocking_operation = blocking_operation |
1838 | 0 | }; |
1839 | |
|
1840 | 0 | rb_mutex_synchronize(wakeup_mutex, io_blocking_operation_exit, (VALUE)&arguments); |
1841 | 0 | } |
1842 | 0 | else { |
1843 | | // If there's no wakeup_mutex, we can safely remove the operation directly: |
1844 | 0 | rb_io_blocking_operation_pop(io, blocking_operation); |
1845 | 0 | } |
1846 | 0 | } |
1847 | | |
1848 | | static VALUE |
1849 | | rb_thread_io_blocking_operation_ensure(VALUE _argument) |
1850 | 0 | { |
1851 | 0 | struct io_blocking_operation_arguments *arguments = (void*)_argument; |
1852 | |
|
1853 | 0 | rb_io_blocking_operation_exit(arguments->io, arguments->blocking_operation); |
1854 | |
|
1855 | 0 | return Qnil; |
1856 | 0 | } |
1857 | | |
1858 | | /* |
1859 | | * Executes a function that performs a blocking IO operation, while properly tracking |
1860 | | * the operation in the IO's blocking_operations list. This ensures proper cleanup |
1861 | | * and interruption handling if the IO is closed while blocked. |
1862 | | * |
1863 | | * The operation is automatically removed from the blocking_operations list when the function |
1864 | | * returns, whether normally or due to an exception. |
1865 | | * |
1866 | | * @parameter self The IO object |
1867 | | * @parameter function The function to execute that will perform the blocking operation |
1868 | | * @parameter argument The argument to pass to the function |
1869 | | * @returns The result of the blocking operation function |
1870 | | */ |
1871 | | VALUE |
1872 | | rb_thread_io_blocking_operation(VALUE self, VALUE(*function)(VALUE), VALUE argument) |
1873 | 0 | { |
1874 | 0 | struct rb_io *io; |
1875 | 0 | RB_IO_POINTER(self, io); |
1876 | |
|
1877 | 0 | rb_execution_context_t *ec = GET_EC(); |
1878 | 0 | struct rb_io_blocking_operation blocking_operation = { |
1879 | 0 | .ec = ec, |
1880 | 0 | }; |
1881 | 0 | rb_io_blocking_operation_enter(io, &blocking_operation); |
1882 | |
|
1883 | 0 | struct io_blocking_operation_arguments io_blocking_operation_arguments = { |
1884 | 0 | .io = io, |
1885 | 0 | .blocking_operation = &blocking_operation |
1886 | 0 | }; |
1887 | |
|
1888 | 0 | return rb_ensure(function, argument, rb_thread_io_blocking_operation_ensure, (VALUE)&io_blocking_operation_arguments); |
1889 | 0 | } |
1890 | | |
1891 | | static bool |
1892 | | thread_io_mn_schedulable(rb_thread_t *th, int events, const struct timeval *timeout) |
1893 | 0 | { |
1894 | 0 | #if defined(USE_MN_THREADS) && USE_MN_THREADS |
1895 | 0 | return !th_has_dedicated_nt(th) && (events || timeout) && th->blocking; |
1896 | | #else |
1897 | | return false; |
1898 | | #endif |
1899 | 0 | } |
1900 | | |
1901 | | // true if need retry |
1902 | | static bool |
1903 | | thread_io_wait_events(rb_thread_t *th, int fd, int events, const struct timeval *timeout) |
1904 | 0 | { |
1905 | 0 | #if defined(USE_MN_THREADS) && USE_MN_THREADS |
1906 | 0 | if (thread_io_mn_schedulable(th, events, timeout)) { |
1907 | 0 | rb_hrtime_t rel, *prel; |
1908 | |
|
1909 | 0 | if (timeout) { |
1910 | 0 | rel = rb_timeval2hrtime(timeout); |
1911 | 0 | prel = &rel; |
1912 | 0 | } |
1913 | 0 | else { |
1914 | 0 | prel = NULL; |
1915 | 0 | } |
1916 | |
|
1917 | 0 | VM_ASSERT(prel || (events & (RB_WAITFD_IN | RB_WAITFD_OUT))); |
1918 | |
|
1919 | 0 | if (thread_sched_wait_events(TH_SCHED(th), th, fd, waitfd_to_waiting_flag(events), prel)) { |
1920 | | // timeout |
1921 | 0 | return false; |
1922 | 0 | } |
1923 | 0 | else { |
1924 | 0 | return true; |
1925 | 0 | } |
1926 | 0 | } |
1927 | 0 | #endif // defined(USE_MN_THREADS) && USE_MN_THREADS |
1928 | 0 | return false; |
1929 | 0 | } |
1930 | | |
1931 | | // assume read/write |
1932 | | static bool |
1933 | | blocking_call_retryable_p(int r, int eno) |
1934 | 0 | { |
1935 | 0 | if (r != -1) return false; |
1936 | | |
1937 | 0 | switch (eno) { |
1938 | 0 | case EAGAIN: |
1939 | | #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN |
1940 | | case EWOULDBLOCK: |
1941 | | #endif |
1942 | 0 | return true; |
1943 | 0 | default: |
1944 | 0 | return false; |
1945 | 0 | } |
1946 | 0 | } |
1947 | | |
1948 | | bool |
1949 | | rb_thread_mn_schedulable(VALUE thval) |
1950 | 0 | { |
1951 | 0 | rb_thread_t *th = rb_thread_ptr(thval); |
1952 | 0 | return th->mn_schedulable; |
1953 | 0 | } |
1954 | | |
1955 | | VALUE |
1956 | | rb_thread_io_blocking_call(struct rb_io* io, rb_blocking_function_t *func, void *data1, int events) |
1957 | 0 | { |
1958 | 0 | rb_execution_context_t * volatile ec = GET_EC(); |
1959 | 0 | rb_thread_t * volatile th = rb_ec_thread_ptr(ec); |
1960 | |
|
1961 | 0 | RUBY_DEBUG_LOG("th:%u fd:%d ev:%d", rb_th_serial(th), io->fd, events); |
1962 | |
|
1963 | 0 | volatile VALUE val = Qundef; /* shouldn't be used */ |
1964 | 0 | volatile int saved_errno = 0; |
1965 | 0 | enum ruby_tag_type state; |
1966 | 0 | volatile bool prev_mn_schedulable = th->mn_schedulable; |
1967 | 0 | th->mn_schedulable = thread_io_mn_schedulable(th, events, NULL); |
1968 | |
|
1969 | 0 | int fd = io->fd; |
1970 | | |
1971 | | // `errno` is only valid when there is an actual error - but we can't |
1972 | | // extract that from the return value of `func` alone, so we clear any |
1973 | | // prior `errno` value here so that we can later check if it was set by |
1974 | | // `func` or not (as opposed to some previously set value). |
1975 | 0 | errno = 0; |
1976 | |
|
1977 | 0 | struct rb_io_blocking_operation blocking_operation = { |
1978 | 0 | .ec = ec, |
1979 | 0 | }; |
1980 | 0 | rb_io_blocking_operation_enter(io, &blocking_operation); |
1981 | |
|
1982 | 0 | { |
1983 | 0 | EC_PUSH_TAG(ec); |
1984 | 0 | if ((state = EC_EXEC_TAG()) == TAG_NONE) { |
1985 | 0 | volatile enum ruby_tag_type saved_state = state; /* for BLOCKING_REGION */ |
1986 | 0 | retry: |
1987 | 0 | BLOCKING_REGION(th, { |
1988 | 0 | val = func(data1); |
1989 | 0 | saved_errno = errno; |
1990 | 0 | }, ubf_select, th, FALSE); |
1991 | |
|
1992 | 0 | RUBY_ASSERT(th == rb_ec_thread_ptr(ec)); |
1993 | 0 | if (events && |
1994 | 0 | blocking_call_retryable_p((int)val, saved_errno) && |
1995 | 0 | thread_io_wait_events(th, fd, events, NULL)) { |
1996 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(ec); |
1997 | 0 | goto retry; |
1998 | 0 | } |
1999 | | |
2000 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(ec); |
2001 | |
|
2002 | 0 | state = saved_state; |
2003 | 0 | } |
2004 | 0 | EC_POP_TAG(); |
2005 | | |
2006 | 0 | th = rb_ec_thread_ptr(ec); |
2007 | 0 | th->mn_schedulable = prev_mn_schedulable; |
2008 | 0 | } |
2009 | | |
2010 | 0 | rb_io_blocking_operation_exit(io, &blocking_operation); |
2011 | |
|
2012 | 0 | if (state) { |
2013 | 0 | EC_JUMP_TAG(ec, state); |
2014 | 0 | } |
2015 | | |
2016 | | // If the error was a timeout, we raise a specific exception for that: |
2017 | 0 | if (saved_errno == ETIMEDOUT) { |
2018 | 0 | rb_raise(rb_eIOTimeoutError, "Blocking operation timed out!"); |
2019 | 0 | } |
2020 | | |
2021 | 0 | errno = saved_errno; |
2022 | |
|
2023 | 0 | return val; |
2024 | 0 | } |
2025 | | |
2026 | | VALUE |
2027 | | rb_thread_io_blocking_region(struct rb_io *io, rb_blocking_function_t *func, void *data1) |
2028 | 0 | { |
2029 | 0 | return rb_thread_io_blocking_call(io, func, data1, 0); |
2030 | 0 | } |
2031 | | |
2032 | | /* |
2033 | | * rb_thread_call_with_gvl - re-enter the Ruby world after GVL release. |
2034 | | * |
2035 | | * After releasing GVL using |
2036 | | * rb_thread_call_without_gvl() you can not access Ruby values or invoke |
2037 | | * methods. If you need to access Ruby you must use this function |
2038 | | * rb_thread_call_with_gvl(). |
2039 | | * |
2040 | | * This function rb_thread_call_with_gvl() does: |
2041 | | * (1) acquire GVL. |
2042 | | * (2) call passed function `func'. |
2043 | | * (3) release GVL. |
2044 | | * (4) return a value which is returned at (2). |
2045 | | * |
2046 | | * NOTE: You should not return Ruby object at (2) because such Object |
2047 | | * will not be marked. |
2048 | | * |
2049 | | * NOTE: If an exception is raised in `func', this function DOES NOT |
2050 | | * protect (catch) the exception. If you have any resources |
2051 | | * which should free before throwing exception, you need use |
2052 | | * rb_protect() in `func' and return a value which represents |
2053 | | * exception was raised. |
2054 | | * |
2055 | | * NOTE: This function should not be called by a thread which was not |
2056 | | * created as Ruby thread (created by Thread.new or so). In other |
2057 | | * words, this function *DOES NOT* associate or convert a NON-Ruby |
2058 | | * thread to a Ruby thread. |
2059 | | * |
2060 | | * NOTE: If this thread has already acquired the GVL, then the method call |
2061 | | * is performed without acquiring or releasing the GVL (from Ruby 4.0). |
2062 | | */ |
2063 | | void * |
2064 | | rb_thread_call_with_gvl(void *(*func)(void *), void *data1) |
2065 | 0 | { |
2066 | 0 | rb_thread_t *th = ruby_thread_from_native(); |
2067 | 0 | struct rb_blocking_region_buffer *brb; |
2068 | 0 | struct rb_unblock_callback prev_unblock; |
2069 | 0 | void *r; |
2070 | |
|
2071 | 0 | if (th == 0) { |
2072 | | /* Error has occurred, but we can't use rb_bug() |
2073 | | * because this thread is not Ruby's thread. |
2074 | | * What should we do? |
2075 | | */ |
2076 | 0 | bp(); |
2077 | 0 | fprintf(stderr, "[BUG] rb_thread_call_with_gvl() is called by non-ruby thread\n"); |
2078 | 0 | exit(EXIT_FAILURE); |
2079 | 0 | } |
2080 | | |
2081 | 0 | brb = (struct rb_blocking_region_buffer *)th->blocking_region_buffer; |
2082 | 0 | prev_unblock = th->unblock; |
2083 | |
|
2084 | 0 | if (brb == 0) { |
2085 | | /* the GVL is already acquired, call method directly */ |
2086 | 0 | return (*func)(data1); |
2087 | 0 | } |
2088 | | |
2089 | 0 | blocking_region_end(th, brb); |
2090 | | /* enter to Ruby world: You can access Ruby values, methods and so on. */ |
2091 | 0 | r = (*func)(data1); |
2092 | | /* leave from Ruby world: You can not access Ruby values, etc. */ |
2093 | 0 | int released = blocking_region_begin(th, brb, prev_unblock.func, prev_unblock.arg, FALSE); |
2094 | 0 | RUBY_ASSERT_ALWAYS(released); |
2095 | 0 | RB_VM_SAVE_MACHINE_CONTEXT(th); |
2096 | 0 | thread_sched_to_waiting(TH_SCHED(th), th, true); |
2097 | 0 | return r; |
2098 | 0 | } |
2099 | | |
2100 | | /* |
2101 | | * ruby_thread_has_gvl_p - check if current native thread has GVL. |
2102 | | */ |
2103 | | |
2104 | | int |
2105 | | ruby_thread_has_gvl_p(void) |
2106 | 12 | { |
2107 | 12 | rb_thread_t *th = ruby_thread_from_native(); |
2108 | | |
2109 | 12 | if (th && th->blocking_region_buffer == 0) { |
2110 | 12 | return 1; |
2111 | 12 | } |
2112 | 0 | else { |
2113 | 0 | return 0; |
2114 | 0 | } |
2115 | 12 | } |
2116 | | |
2117 | | /* |
2118 | | * call-seq: |
2119 | | * Thread.pass -> nil |
2120 | | * |
2121 | | * Give the thread scheduler a hint to pass execution to another thread. |
2122 | | * A running thread may or may not switch, it depends on OS and processor. |
2123 | | */ |
2124 | | |
2125 | | static VALUE |
2126 | | thread_s_pass(VALUE klass) |
2127 | 0 | { |
2128 | 0 | rb_thread_schedule(); |
2129 | 0 | return Qnil; |
2130 | 0 | } |
2131 | | |
2132 | | /*****************************************************/ |
2133 | | |
2134 | | /* |
2135 | | * rb_threadptr_pending_interrupt_* - manage asynchronous error queue |
2136 | | * |
2137 | | * Async events such as an exception thrown by Thread#raise, |
2138 | | * Thread#kill and thread termination (after main thread termination) |
2139 | | * will be queued to th->pending_interrupt_queue. |
2140 | | * - clear: clear the queue. |
2141 | | * - enque: enqueue err object into queue. |
2142 | | * - deque: dequeue err object from queue. |
2143 | | * - active_p: return 1 if the queue should be checked. |
2144 | | * |
2145 | | * All rb_threadptr_pending_interrupt_* functions are called by |
2146 | | * a GVL acquired thread, of course. |
2147 | | * Note that all "rb_" prefix APIs need GVL to call. |
2148 | | */ |
2149 | | |
2150 | | void |
2151 | | rb_threadptr_pending_interrupt_clear(rb_thread_t *th) |
2152 | 0 | { |
2153 | 0 | rb_ary_clear(th->pending_interrupt_queue); |
2154 | 0 | } |
2155 | | |
2156 | | void |
2157 | | rb_threadptr_pending_interrupt_enque(rb_thread_t *th, VALUE v) |
2158 | 0 | { |
2159 | 0 | rb_ary_push(th->pending_interrupt_queue, v); |
2160 | 0 | th->pending_interrupt_queue_checked = 0; |
2161 | 0 | } |
2162 | | |
2163 | | static void |
2164 | | threadptr_check_pending_interrupt_queue(rb_thread_t *th) |
2165 | 0 | { |
2166 | 0 | if (!th->pending_interrupt_queue) { |
2167 | 0 | rb_raise(rb_eThreadError, "uninitialized thread"); |
2168 | 0 | } |
2169 | 0 | } |
2170 | | |
2171 | | enum handle_interrupt_timing { |
2172 | | INTERRUPT_NONE, |
2173 | | INTERRUPT_IMMEDIATE, |
2174 | | INTERRUPT_ON_BLOCKING, |
2175 | | INTERRUPT_NEVER |
2176 | | }; |
2177 | | |
2178 | | static enum handle_interrupt_timing |
2179 | | rb_threadptr_pending_interrupt_from_symbol(rb_thread_t *th, VALUE sym) |
2180 | 0 | { |
2181 | 0 | if (sym == sym_immediate) { |
2182 | 0 | return INTERRUPT_IMMEDIATE; |
2183 | 0 | } |
2184 | 0 | else if (sym == sym_on_blocking) { |
2185 | 0 | return INTERRUPT_ON_BLOCKING; |
2186 | 0 | } |
2187 | 0 | else if (sym == sym_never) { |
2188 | 0 | return INTERRUPT_NEVER; |
2189 | 0 | } |
2190 | 0 | else { |
2191 | 0 | rb_raise(rb_eThreadError, "unknown mask signature"); |
2192 | 0 | } |
2193 | 0 | } |
2194 | | |
2195 | | static enum handle_interrupt_timing |
2196 | | rb_threadptr_pending_interrupt_check_mask(rb_thread_t *th, VALUE err) |
2197 | 0 | { |
2198 | 0 | VALUE mask; |
2199 | 0 | long mask_stack_len = RARRAY_LEN(th->pending_interrupt_mask_stack); |
2200 | 0 | const VALUE *mask_stack = RARRAY_CONST_PTR(th->pending_interrupt_mask_stack); |
2201 | 0 | VALUE mod; |
2202 | 0 | long i; |
2203 | |
|
2204 | 0 | for (i=0; i<mask_stack_len; i++) { |
2205 | 0 | mask = mask_stack[mask_stack_len-(i+1)]; |
2206 | |
|
2207 | 0 | if (SYMBOL_P(mask)) { |
2208 | | /* do not match RUBY_FATAL_THREAD_KILLED etc */ |
2209 | 0 | if (err != rb_cInteger) { |
2210 | 0 | return rb_threadptr_pending_interrupt_from_symbol(th, mask); |
2211 | 0 | } |
2212 | 0 | else { |
2213 | 0 | continue; |
2214 | 0 | } |
2215 | 0 | } |
2216 | | |
2217 | 0 | for (mod = err; mod; mod = RCLASS_SUPER(mod)) { |
2218 | 0 | VALUE klass = mod; |
2219 | 0 | VALUE sym; |
2220 | |
|
2221 | 0 | if (BUILTIN_TYPE(mod) == T_ICLASS) { |
2222 | 0 | klass = RBASIC(mod)->klass; |
2223 | 0 | } |
2224 | 0 | else if (mod != RCLASS_ORIGIN(mod)) { |
2225 | 0 | continue; |
2226 | 0 | } |
2227 | | |
2228 | 0 | if ((sym = rb_hash_aref(mask, klass)) != Qnil) { |
2229 | 0 | return rb_threadptr_pending_interrupt_from_symbol(th, sym); |
2230 | 0 | } |
2231 | 0 | } |
2232 | | /* try to next mask */ |
2233 | 0 | } |
2234 | 0 | return INTERRUPT_NONE; |
2235 | 0 | } |
2236 | | |
2237 | | static int |
2238 | | rb_threadptr_pending_interrupt_empty_p(const rb_thread_t *th) |
2239 | 41.1M | { |
2240 | 41.1M | return RARRAY_LEN(th->pending_interrupt_queue) == 0; |
2241 | 41.1M | } |
2242 | | |
2243 | | static int |
2244 | | rb_threadptr_pending_interrupt_include_p(rb_thread_t *th, VALUE err) |
2245 | 0 | { |
2246 | 0 | int i; |
2247 | 0 | for (i=0; i<RARRAY_LEN(th->pending_interrupt_queue); i++) { |
2248 | 0 | VALUE e = RARRAY_AREF(th->pending_interrupt_queue, i); |
2249 | 0 | if (rb_obj_is_kind_of(e, err)) { |
2250 | 0 | return TRUE; |
2251 | 0 | } |
2252 | 0 | } |
2253 | 0 | return FALSE; |
2254 | 0 | } |
2255 | | |
2256 | | static VALUE |
2257 | | rb_threadptr_pending_interrupt_deque(rb_thread_t *th, enum handle_interrupt_timing timing) |
2258 | 0 | { |
2259 | 0 | #if 1 /* 1 to enable Thread#handle_interrupt, 0 to ignore it */ |
2260 | 0 | int i; |
2261 | |
|
2262 | 0 | for (i=0; i<RARRAY_LEN(th->pending_interrupt_queue); i++) { |
2263 | 0 | VALUE err = RARRAY_AREF(th->pending_interrupt_queue, i); |
2264 | |
|
2265 | 0 | enum handle_interrupt_timing mask_timing = rb_threadptr_pending_interrupt_check_mask(th, CLASS_OF(err)); |
2266 | |
|
2267 | 0 | switch (mask_timing) { |
2268 | 0 | case INTERRUPT_ON_BLOCKING: |
2269 | 0 | if (timing != INTERRUPT_ON_BLOCKING) { |
2270 | 0 | break; |
2271 | 0 | } |
2272 | | /* fall through */ |
2273 | 0 | case INTERRUPT_NONE: /* default: IMMEDIATE */ |
2274 | 0 | case INTERRUPT_IMMEDIATE: |
2275 | 0 | rb_ary_delete_at(th->pending_interrupt_queue, i); |
2276 | 0 | return err; |
2277 | 0 | case INTERRUPT_NEVER: |
2278 | 0 | break; |
2279 | 0 | } |
2280 | 0 | } |
2281 | | |
2282 | 0 | th->pending_interrupt_queue_checked = 1; |
2283 | 0 | return Qundef; |
2284 | | #else |
2285 | | VALUE err = rb_ary_shift(th->pending_interrupt_queue); |
2286 | | if (rb_threadptr_pending_interrupt_empty_p(th)) { |
2287 | | th->pending_interrupt_queue_checked = 1; |
2288 | | } |
2289 | | return err; |
2290 | | #endif |
2291 | 0 | } |
2292 | | |
2293 | | static int |
2294 | | threadptr_pending_interrupt_active_p(rb_thread_t *th) |
2295 | 0 | { |
2296 | | /* |
2297 | | * For optimization, we don't check async errinfo queue |
2298 | | * if the queue and the thread interrupt mask were not changed |
2299 | | * since last check. |
2300 | | */ |
2301 | 0 | if (th->pending_interrupt_queue_checked) { |
2302 | 0 | return 0; |
2303 | 0 | } |
2304 | | |
2305 | 0 | if (rb_threadptr_pending_interrupt_empty_p(th)) { |
2306 | 0 | return 0; |
2307 | 0 | } |
2308 | | |
2309 | 0 | return 1; |
2310 | 0 | } |
2311 | | |
2312 | | static int |
2313 | | handle_interrupt_arg_check_i(VALUE key, VALUE val, VALUE args) |
2314 | 0 | { |
2315 | 0 | VALUE *maskp = (VALUE *)args; |
2316 | |
|
2317 | 0 | if (val != sym_immediate && val != sym_on_blocking && val != sym_never) { |
2318 | 0 | rb_raise(rb_eArgError, "unknown mask signature"); |
2319 | 0 | } |
2320 | | |
2321 | 0 | if (key == rb_eException && (UNDEF_P(*maskp) || NIL_P(*maskp))) { |
2322 | 0 | *maskp = val; |
2323 | 0 | return ST_CONTINUE; |
2324 | 0 | } |
2325 | | |
2326 | 0 | if (RTEST(*maskp)) { |
2327 | 0 | if (!RB_TYPE_P(*maskp, T_HASH)) { |
2328 | 0 | VALUE prev = *maskp; |
2329 | 0 | *maskp = rb_ident_hash_new(); |
2330 | 0 | if (SYMBOL_P(prev)) { |
2331 | 0 | rb_hash_aset(*maskp, rb_eException, prev); |
2332 | 0 | } |
2333 | 0 | } |
2334 | 0 | rb_hash_aset(*maskp, key, val); |
2335 | 0 | } |
2336 | 0 | else { |
2337 | 0 | *maskp = Qfalse; |
2338 | 0 | } |
2339 | |
|
2340 | 0 | return ST_CONTINUE; |
2341 | 0 | } |
2342 | | |
2343 | | /* |
2344 | | * call-seq: |
2345 | | * Thread.handle_interrupt(hash) { ... } -> result of the block |
2346 | | * |
2347 | | * Changes asynchronous interrupt timing. |
2348 | | * |
2349 | | * _interrupt_ means asynchronous event and corresponding procedure |
2350 | | * by Thread#raise, Thread#kill, signal trap (not supported yet) |
2351 | | * and main thread termination (if main thread terminates, then all |
2352 | | * other thread will be killed). |
2353 | | * |
2354 | | * The given +hash+ has pairs like <code>ExceptionClass => |
2355 | | * :TimingSymbol</code>. Where the ExceptionClass is the interrupt handled by |
2356 | | * the given block. The TimingSymbol can be one of the following symbols: |
2357 | | * |
2358 | | * [+:immediate+] Invoke interrupts immediately. |
2359 | | * [+:on_blocking+] Invoke interrupts while _BlockingOperation_. |
2360 | | * [+:never+] Never invoke all interrupts. |
2361 | | * |
2362 | | * _BlockingOperation_ means that the operation will block the calling thread, |
2363 | | * such as read and write. On CRuby implementation, _BlockingOperation_ is any |
2364 | | * operation executed without GVL. |
2365 | | * |
2366 | | * Masked asynchronous interrupts are delayed until they are enabled. |
2367 | | * This method is similar to sigprocmask(3). |
2368 | | * |
2369 | | * === NOTE |
2370 | | * |
2371 | | * Asynchronous interrupts are difficult to use. |
2372 | | * |
2373 | | * If you need to communicate between threads, please consider to use another way such as Queue. |
2374 | | * |
2375 | | * Or use them with deep understanding about this method. |
2376 | | * |
2377 | | * === Usage |
2378 | | * |
2379 | | * In this example, we can guard from Thread#raise exceptions. |
2380 | | * |
2381 | | * Using the +:never+ TimingSymbol the RuntimeError exception will always be |
2382 | | * ignored in the first block of the main thread. In the second |
2383 | | * ::handle_interrupt block we can purposefully handle RuntimeError exceptions. |
2384 | | * |
2385 | | * th = Thread.new do |
2386 | | * Thread.handle_interrupt(RuntimeError => :never) { |
2387 | | * begin |
2388 | | * # You can write resource allocation code safely. |
2389 | | * Thread.handle_interrupt(RuntimeError => :immediate) { |
2390 | | * # ... |
2391 | | * } |
2392 | | * ensure |
2393 | | * # You can write resource deallocation code safely. |
2394 | | * end |
2395 | | * } |
2396 | | * end |
2397 | | * Thread.pass |
2398 | | * # ... |
2399 | | * th.raise "stop" |
2400 | | * |
2401 | | * While we are ignoring the RuntimeError exception, it's safe to write our |
2402 | | * resource allocation code. Then, the ensure block is where we can safely |
2403 | | * deallocate your resources. |
2404 | | * |
2405 | | * ==== Stack control settings |
2406 | | * |
2407 | | * It's possible to stack multiple levels of ::handle_interrupt blocks in order |
2408 | | * to control more than one ExceptionClass and TimingSymbol at a time. |
2409 | | * |
2410 | | * Thread.handle_interrupt(FooError => :never) { |
2411 | | * Thread.handle_interrupt(BarError => :never) { |
2412 | | * # FooError and BarError are prohibited. |
2413 | | * } |
2414 | | * } |
2415 | | * |
2416 | | * ==== Inheritance with ExceptionClass |
2417 | | * |
2418 | | * All exceptions inherited from the ExceptionClass parameter will be considered. |
2419 | | * |
2420 | | * Thread.handle_interrupt(Exception => :never) { |
2421 | | * # all exceptions inherited from Exception are prohibited. |
2422 | | * } |
2423 | | * |
2424 | | * For handling all interrupts, use +Object+ and not +Exception+ |
2425 | | * as the ExceptionClass, as kill/terminate interrupts are not handled by +Exception+. |
2426 | | */ |
2427 | | static VALUE |
2428 | | rb_thread_s_handle_interrupt(VALUE self, VALUE mask_arg) |
2429 | 0 | { |
2430 | 0 | VALUE mask = Qundef; |
2431 | 0 | rb_execution_context_t * volatile ec = GET_EC(); |
2432 | 0 | rb_thread_t * volatile th = rb_ec_thread_ptr(ec); |
2433 | 0 | volatile VALUE r = Qnil; |
2434 | 0 | enum ruby_tag_type state; |
2435 | |
|
2436 | 0 | if (!rb_block_given_p()) { |
2437 | 0 | rb_raise(rb_eArgError, "block is needed."); |
2438 | 0 | } |
2439 | | |
2440 | 0 | mask_arg = rb_to_hash_type(mask_arg); |
2441 | |
|
2442 | 0 | if (OBJ_FROZEN(mask_arg) && rb_hash_compare_by_id_p(mask_arg)) { |
2443 | 0 | mask = Qnil; |
2444 | 0 | } |
2445 | |
|
2446 | 0 | rb_hash_foreach(mask_arg, handle_interrupt_arg_check_i, (VALUE)&mask); |
2447 | |
|
2448 | 0 | if (UNDEF_P(mask)) { |
2449 | 0 | return rb_yield(Qnil); |
2450 | 0 | } |
2451 | | |
2452 | 0 | if (!RTEST(mask)) { |
2453 | 0 | mask = mask_arg; |
2454 | 0 | } |
2455 | 0 | else if (RB_TYPE_P(mask, T_HASH)) { |
2456 | 0 | OBJ_FREEZE(mask); |
2457 | 0 | } |
2458 | |
|
2459 | 0 | rb_ary_push(th->pending_interrupt_mask_stack, mask); |
2460 | 0 | if (!rb_threadptr_pending_interrupt_empty_p(th)) { |
2461 | 0 | th->pending_interrupt_queue_checked = 0; |
2462 | 0 | RUBY_VM_SET_INTERRUPT(th->ec); |
2463 | 0 | } |
2464 | |
|
2465 | 0 | EC_PUSH_TAG(th->ec); |
2466 | 0 | if ((state = EC_EXEC_TAG()) == TAG_NONE) { |
2467 | 0 | r = rb_yield(Qnil); |
2468 | 0 | } |
2469 | 0 | EC_POP_TAG(); |
2470 | |
|
2471 | 0 | rb_ary_pop(th->pending_interrupt_mask_stack); |
2472 | 0 | if (!rb_threadptr_pending_interrupt_empty_p(th)) { |
2473 | 0 | th->pending_interrupt_queue_checked = 0; |
2474 | 0 | RUBY_VM_SET_INTERRUPT(th->ec); |
2475 | 0 | } |
2476 | |
|
2477 | 0 | RUBY_VM_CHECK_INTS(th->ec); |
2478 | |
|
2479 | 0 | if (state) { |
2480 | 0 | EC_JUMP_TAG(th->ec, state); |
2481 | 0 | } |
2482 | | |
2483 | 0 | return r; |
2484 | 0 | } |
2485 | | |
2486 | | /* |
2487 | | * call-seq: |
2488 | | * target_thread.pending_interrupt?(error = nil) -> true/false |
2489 | | * |
2490 | | * Returns whether or not the asynchronous queue is empty for the target thread. |
2491 | | * |
2492 | | * If +error+ is given, then check only for +error+ type deferred events. |
2493 | | * |
2494 | | * See ::pending_interrupt? for more information. |
2495 | | */ |
2496 | | static VALUE |
2497 | | rb_thread_pending_interrupt_p(int argc, VALUE *argv, VALUE target_thread) |
2498 | 0 | { |
2499 | 0 | rb_thread_t *target_th = rb_thread_ptr(target_thread); |
2500 | |
|
2501 | 0 | if (!target_th->pending_interrupt_queue) { |
2502 | 0 | return Qfalse; |
2503 | 0 | } |
2504 | 0 | if (rb_threadptr_pending_interrupt_empty_p(target_th)) { |
2505 | 0 | return Qfalse; |
2506 | 0 | } |
2507 | 0 | if (rb_check_arity(argc, 0, 1)) { |
2508 | 0 | VALUE err = argv[0]; |
2509 | 0 | if (!rb_obj_is_kind_of(err, rb_cModule)) { |
2510 | 0 | rb_raise(rb_eTypeError, "class or module required for rescue clause"); |
2511 | 0 | } |
2512 | 0 | return RBOOL(rb_threadptr_pending_interrupt_include_p(target_th, err)); |
2513 | 0 | } |
2514 | 0 | else { |
2515 | 0 | return Qtrue; |
2516 | 0 | } |
2517 | 0 | } |
2518 | | |
2519 | | /* |
2520 | | * call-seq: |
2521 | | * Thread.pending_interrupt?(error = nil) -> true/false |
2522 | | * |
2523 | | * Returns whether or not the asynchronous queue is empty. |
2524 | | * |
2525 | | * Since Thread::handle_interrupt can be used to defer asynchronous events, |
2526 | | * this method can be used to determine if there are any deferred events. |
2527 | | * |
2528 | | * If you find this method returns true, then you may finish +:never+ blocks. |
2529 | | * |
2530 | | * For example, the following method processes deferred asynchronous events |
2531 | | * immediately. |
2532 | | * |
2533 | | * def Thread.kick_interrupt_immediately |
2534 | | * Thread.handle_interrupt(Object => :immediate) { |
2535 | | * Thread.pass |
2536 | | * } |
2537 | | * end |
2538 | | * |
2539 | | * If +error+ is given, then check only for +error+ type deferred events. |
2540 | | * |
2541 | | * === Usage |
2542 | | * |
2543 | | * th = Thread.new{ |
2544 | | * Thread.handle_interrupt(RuntimeError => :on_blocking){ |
2545 | | * while true |
2546 | | * ... |
2547 | | * # reach safe point to invoke interrupt |
2548 | | * if Thread.pending_interrupt? |
2549 | | * Thread.handle_interrupt(Object => :immediate){} |
2550 | | * end |
2551 | | * ... |
2552 | | * end |
2553 | | * } |
2554 | | * } |
2555 | | * ... |
2556 | | * th.raise # stop thread |
2557 | | * |
2558 | | * This example can also be written as the following, which you should use to |
2559 | | * avoid asynchronous interrupts. |
2560 | | * |
2561 | | * flag = true |
2562 | | * th = Thread.new{ |
2563 | | * Thread.handle_interrupt(RuntimeError => :on_blocking){ |
2564 | | * while true |
2565 | | * ... |
2566 | | * # reach safe point to invoke interrupt |
2567 | | * break if flag == false |
2568 | | * ... |
2569 | | * end |
2570 | | * } |
2571 | | * } |
2572 | | * ... |
2573 | | * flag = false # stop thread |
2574 | | */ |
2575 | | |
2576 | | static VALUE |
2577 | | rb_thread_s_pending_interrupt_p(int argc, VALUE *argv, VALUE self) |
2578 | 0 | { |
2579 | 0 | return rb_thread_pending_interrupt_p(argc, argv, GET_THREAD()->self); |
2580 | 0 | } |
2581 | | |
2582 | | NORETURN(static void rb_threadptr_to_kill(rb_thread_t *th)); |
2583 | | |
2584 | | static void |
2585 | | rb_threadptr_to_kill(rb_thread_t *th) |
2586 | 0 | { |
2587 | 0 | VM_ASSERT(GET_THREAD() == th); |
2588 | 0 | rb_threadptr_pending_interrupt_clear(th); |
2589 | 0 | th->status = THREAD_RUNNABLE; |
2590 | 0 | th->to_kill = 1; |
2591 | 0 | th->ec->errinfo = INT2FIX(TAG_FATAL); |
2592 | 0 | EC_JUMP_TAG(th->ec, TAG_FATAL); |
2593 | 0 | } |
2594 | | |
2595 | | static inline rb_atomic_t |
2596 | | threadptr_get_interrupts(rb_thread_t *th) |
2597 | 0 | { |
2598 | 0 | rb_execution_context_t *ec = th->ec; |
2599 | 0 | rb_atomic_t interrupt; |
2600 | 0 | rb_atomic_t old; |
2601 | |
|
2602 | 0 | old = ATOMIC_LOAD_RELAXED(ec->interrupt_flag); |
2603 | 0 | do { |
2604 | 0 | interrupt = old; |
2605 | 0 | old = ATOMIC_CAS(ec->interrupt_flag, interrupt, interrupt & ec->interrupt_mask); |
2606 | 0 | } while (old != interrupt); |
2607 | 0 | return interrupt & (rb_atomic_t)~ec->interrupt_mask; |
2608 | 0 | } |
2609 | | |
2610 | | static void threadptr_interrupt_exec_exec(rb_thread_t *th); |
2611 | | |
2612 | | // Execute interrupts on currently running thread |
2613 | | // In certain situations, calling this function will raise an exception. Some examples are: |
2614 | | // * during VM shutdown (`rb_ractor_terminate_all`) |
2615 | | // * Call to Thread#exit for current thread (`rb_thread_kill`) |
2616 | | // * Call to Thread#raise for current thread |
2617 | | int |
2618 | | rb_threadptr_execute_interrupts(rb_thread_t *th, int blocking_timing) |
2619 | 0 | { |
2620 | 0 | rb_atomic_t interrupt; |
2621 | 0 | int postponed_job_interrupt = 0; |
2622 | 0 | int ret = FALSE; |
2623 | |
|
2624 | 0 | VM_ASSERT(GET_THREAD() == th); |
2625 | |
|
2626 | 0 | if (th->ec->raised_flag) return ret; |
2627 | | |
2628 | 0 | while ((interrupt = threadptr_get_interrupts(th)) != 0) { |
2629 | 0 | int sig; |
2630 | 0 | int timer_interrupt; |
2631 | 0 | int pending_interrupt; |
2632 | 0 | int trap_interrupt; |
2633 | 0 | int terminate_interrupt; |
2634 | |
|
2635 | 0 | timer_interrupt = interrupt & TIMER_INTERRUPT_MASK; |
2636 | 0 | pending_interrupt = interrupt & PENDING_INTERRUPT_MASK; |
2637 | 0 | postponed_job_interrupt = interrupt & POSTPONED_JOB_INTERRUPT_MASK; |
2638 | 0 | trap_interrupt = interrupt & TRAP_INTERRUPT_MASK; |
2639 | 0 | terminate_interrupt = interrupt & TERMINATE_INTERRUPT_MASK; // request from other ractors |
2640 | |
|
2641 | 0 | if (interrupt & VM_BARRIER_INTERRUPT_MASK) { |
2642 | 0 | RB_VM_LOCKING(); |
2643 | 0 | } |
2644 | |
|
2645 | 0 | if (postponed_job_interrupt) { |
2646 | 0 | rb_postponed_job_flush(th->vm); |
2647 | 0 | } |
2648 | |
|
2649 | 0 | if (trap_interrupt) { |
2650 | | /* signal handling */ |
2651 | 0 | if (th == th->vm->ractor.main_thread) { |
2652 | 0 | enum rb_thread_status prev_status = th->status; |
2653 | |
|
2654 | 0 | th->status = THREAD_RUNNABLE; |
2655 | 0 | { |
2656 | 0 | while ((sig = rb_get_next_signal()) != 0) { |
2657 | 0 | ret |= rb_signal_exec(th, sig); |
2658 | 0 | } |
2659 | 0 | } |
2660 | 0 | th->status = prev_status; |
2661 | 0 | } |
2662 | |
|
2663 | 0 | if (!ccan_list_empty(&th->interrupt_exec_tasks)) { |
2664 | 0 | enum rb_thread_status prev_status = th->status; |
2665 | |
|
2666 | 0 | th->status = THREAD_RUNNABLE; |
2667 | 0 | { |
2668 | 0 | threadptr_interrupt_exec_exec(th); |
2669 | 0 | } |
2670 | 0 | th->status = prev_status; |
2671 | 0 | } |
2672 | 0 | } |
2673 | | |
2674 | | /* exception from another thread */ |
2675 | 0 | if (pending_interrupt && threadptr_pending_interrupt_active_p(th)) { |
2676 | 0 | VALUE err = rb_threadptr_pending_interrupt_deque(th, blocking_timing ? INTERRUPT_ON_BLOCKING : INTERRUPT_NONE); |
2677 | 0 | RUBY_DEBUG_LOG("err:%"PRIdVALUE, err); |
2678 | 0 | ret = TRUE; |
2679 | |
|
2680 | 0 | if (UNDEF_P(err)) { |
2681 | | /* no error */ |
2682 | 0 | } |
2683 | 0 | else if (err == RUBY_FATAL_THREAD_KILLED /* Thread#kill received */ || |
2684 | 0 | err == RUBY_FATAL_THREAD_TERMINATED /* Terminate thread */ || |
2685 | 0 | err == INT2FIX(TAG_FATAL) /* Thread.exit etc. */ ) { |
2686 | 0 | terminate_interrupt = 1; |
2687 | 0 | } |
2688 | 0 | else { |
2689 | 0 | if (err == th->vm->special_exceptions[ruby_error_stream_closed]) { |
2690 | | /* the only special exception to be queued across thread */ |
2691 | 0 | err = ruby_vm_special_exception_copy(err); |
2692 | 0 | } |
2693 | | /* set runnable if th was slept. */ |
2694 | 0 | if (th->status == THREAD_STOPPED || |
2695 | 0 | th->status == THREAD_STOPPED_FOREVER) |
2696 | 0 | th->status = THREAD_RUNNABLE; |
2697 | 0 | rb_exc_raise(err); |
2698 | 0 | } |
2699 | 0 | } |
2700 | | |
2701 | 0 | if (terminate_interrupt) { |
2702 | 0 | rb_threadptr_to_kill(th); |
2703 | 0 | } |
2704 | | |
2705 | 0 | if (timer_interrupt) { |
2706 | 0 | uint32_t limits_us = thread_default_quantum_ms * 1000; |
2707 | |
|
2708 | 0 | if (th->priority > 0) |
2709 | 0 | limits_us <<= th->priority; |
2710 | 0 | else |
2711 | 0 | limits_us >>= -th->priority; |
2712 | |
|
2713 | 0 | if (th->status == THREAD_RUNNABLE) |
2714 | 0 | th->running_time_us += 10 * 1000; // 10ms = 10_000us // TODO: use macro |
2715 | |
|
2716 | 0 | VM_ASSERT(th->ec->cfp); |
2717 | 0 | EXEC_EVENT_HOOK(th->ec, RUBY_INTERNAL_EVENT_SWITCH, th->ec->cfp->self, |
2718 | 0 | 0, 0, 0, Qundef); |
2719 | |
|
2720 | 0 | rb_thread_schedule_limits(limits_us); |
2721 | 0 | } |
2722 | 0 | } |
2723 | 0 | return ret; |
2724 | 0 | } |
2725 | | |
2726 | | void |
2727 | | rb_thread_execute_interrupts(VALUE thval) |
2728 | 0 | { |
2729 | 0 | rb_threadptr_execute_interrupts(rb_thread_ptr(thval), 1); |
2730 | 0 | } |
2731 | | |
2732 | | static void |
2733 | | rb_threadptr_ready(rb_thread_t *th) |
2734 | 0 | { |
2735 | 0 | rb_threadptr_interrupt(th); |
2736 | 0 | } |
2737 | | |
2738 | | static VALUE |
2739 | | rb_threadptr_raise(rb_thread_t *target_th, int argc, VALUE *argv) |
2740 | 0 | { |
2741 | 0 | VALUE exc; |
2742 | |
|
2743 | 0 | if (rb_threadptr_dead(target_th)) { |
2744 | 0 | return Qnil; |
2745 | 0 | } |
2746 | | |
2747 | 0 | if (argc == 0) { |
2748 | 0 | exc = rb_exc_new(rb_eRuntimeError, 0, 0); |
2749 | 0 | } |
2750 | 0 | else { |
2751 | 0 | exc = rb_make_exception(argc, argv); |
2752 | 0 | } |
2753 | | |
2754 | | /* making an exception object can switch thread, |
2755 | | so we need to check thread deadness again */ |
2756 | 0 | if (rb_threadptr_dead(target_th)) { |
2757 | 0 | return Qnil; |
2758 | 0 | } |
2759 | | |
2760 | 0 | rb_ec_setup_exception(GET_EC(), exc, Qundef); |
2761 | 0 | rb_threadptr_pending_interrupt_enque(target_th, exc); |
2762 | 0 | rb_threadptr_interrupt(target_th); |
2763 | |
|
2764 | 0 | return Qnil; |
2765 | 0 | } |
2766 | | |
2767 | | void |
2768 | | rb_threadptr_signal_raise(rb_thread_t *th, int sig) |
2769 | 0 | { |
2770 | 0 | VALUE argv[2]; |
2771 | |
|
2772 | 0 | argv[0] = rb_eSignal; |
2773 | 0 | argv[1] = INT2FIX(sig); |
2774 | 0 | rb_threadptr_raise(th->vm->ractor.main_thread, 2, argv); |
2775 | 0 | } |
2776 | | |
2777 | | void |
2778 | | rb_threadptr_signal_exit(rb_thread_t *th) |
2779 | 0 | { |
2780 | 0 | VALUE argv[2]; |
2781 | |
|
2782 | 0 | argv[0] = rb_eSystemExit; |
2783 | 0 | argv[1] = rb_str_new2("exit"); |
2784 | | |
2785 | | // TODO: check signal raise deliverly |
2786 | 0 | rb_threadptr_raise(th->vm->ractor.main_thread, 2, argv); |
2787 | 0 | } |
2788 | | |
2789 | | int |
2790 | | rb_ec_set_raised(rb_execution_context_t *ec) |
2791 | 38.1k | { |
2792 | 38.1k | if (ec->raised_flag & RAISED_EXCEPTION) { |
2793 | 0 | return 1; |
2794 | 0 | } |
2795 | 38.1k | ec->raised_flag |= RAISED_EXCEPTION; |
2796 | 38.1k | return 0; |
2797 | 38.1k | } |
2798 | | |
2799 | | int |
2800 | | rb_ec_reset_raised(rb_execution_context_t *ec) |
2801 | 35.9k | { |
2802 | 35.9k | if (!(ec->raised_flag & RAISED_EXCEPTION)) { |
2803 | 16.8k | return 0; |
2804 | 16.8k | } |
2805 | 19.0k | ec->raised_flag &= ~RAISED_EXCEPTION; |
2806 | 19.0k | return 1; |
2807 | 35.9k | } |
2808 | | |
2809 | | /* |
2810 | | * Thread-safe IO closing mechanism. |
2811 | | * |
2812 | | * When an IO is closed while other threads or fibers are blocked on it, we need to: |
2813 | | * 1. Track and notify all blocking operations through io->blocking_operations |
2814 | | * 2. Ensure only one thread can close at a time using io->closing_ec |
2815 | | * 3. Synchronize cleanup using wakeup_mutex |
2816 | | * |
2817 | | * The close process works as follows: |
2818 | | * - First check if any thread is already closing (io->closing_ec) |
2819 | | * - Set up wakeup_mutex for synchronization |
2820 | | * - Iterate through all blocking operations in io->blocking_operations |
2821 | | * - For each blocked fiber with a scheduler: |
2822 | | * - Notify via rb_fiber_scheduler_fiber_interrupt |
2823 | | * - For each blocked thread without a scheduler: |
2824 | | * - Enqueue IOError via rb_threadptr_pending_interrupt_enque |
2825 | | * - Wake via rb_threadptr_interrupt |
2826 | | * - Wait on wakeup_mutex until all operations are cleaned up |
2827 | | * - Only then clear closing state and allow actual close to proceed |
2828 | | */ |
2829 | | static VALUE |
2830 | | thread_io_close_notify_all(VALUE _io) |
2831 | 0 | { |
2832 | 0 | struct rb_io *io = (struct rb_io *)_io; |
2833 | |
|
2834 | 0 | size_t count = 0; |
2835 | 0 | rb_vm_t *vm = io->closing_ec->thread_ptr->vm; |
2836 | 0 | VALUE error = vm->special_exceptions[ruby_error_stream_closed]; |
2837 | |
|
2838 | 0 | struct rb_io_blocking_operation *blocking_operation; |
2839 | 0 | ccan_list_for_each(rb_io_blocking_operations(io), blocking_operation, list) { |
2840 | 0 | rb_execution_context_t *ec = blocking_operation->ec; |
2841 | | |
2842 | | // If the operation is in progress, we need to interrupt it: |
2843 | 0 | if (ec) { |
2844 | 0 | rb_thread_t *thread = ec->thread_ptr; |
2845 | |
|
2846 | 0 | VALUE result = RUBY_Qundef; |
2847 | 0 | if (thread->scheduler != Qnil) { |
2848 | 0 | result = rb_fiber_scheduler_fiber_interrupt(thread->scheduler, rb_fiberptr_self(ec->fiber_ptr), error); |
2849 | 0 | } |
2850 | |
|
2851 | 0 | if (result == RUBY_Qundef) { |
2852 | | // If the thread is not the current thread, we need to enqueue an error: |
2853 | 0 | rb_threadptr_pending_interrupt_enque(thread, error); |
2854 | 0 | rb_threadptr_interrupt(thread); |
2855 | 0 | } |
2856 | 0 | } |
2857 | |
|
2858 | 0 | count += 1; |
2859 | 0 | } |
2860 | |
|
2861 | 0 | return (VALUE)count; |
2862 | 0 | } |
2863 | | |
2864 | | size_t |
2865 | | rb_thread_io_close_interrupt(struct rb_io *io) |
2866 | 0 | { |
2867 | | // We guard this operation based on `io->closing_ec` -> only one thread will ever enter this function. |
2868 | 0 | if (io->closing_ec) { |
2869 | 0 | return 0; |
2870 | 0 | } |
2871 | | |
2872 | | // If there are no blocking operations, we are done: |
2873 | 0 | if (ccan_list_empty(rb_io_blocking_operations(io))) { |
2874 | 0 | return 0; |
2875 | 0 | } |
2876 | | |
2877 | | // Otherwise, we are now closing the IO: |
2878 | 0 | rb_execution_context_t *ec = GET_EC(); |
2879 | 0 | io->closing_ec = ec; |
2880 | | |
2881 | | // This is used to ensure the correct execution context is woken up after the blocking operation is interrupted: |
2882 | 0 | io->wakeup_mutex = rb_mutex_new(); |
2883 | 0 | rb_mutex_allow_trap(io->wakeup_mutex, 1); |
2884 | | |
2885 | | // We need to use a mutex here as entering the fiber scheduler may cause a context switch: |
2886 | 0 | VALUE result = rb_mutex_synchronize(io->wakeup_mutex, thread_io_close_notify_all, (VALUE)io); |
2887 | |
|
2888 | 0 | return (size_t)result; |
2889 | 0 | } |
2890 | | |
2891 | | void |
2892 | | rb_thread_io_close_wait(struct rb_io* io) |
2893 | 0 | { |
2894 | 0 | VALUE wakeup_mutex = io->wakeup_mutex; |
2895 | |
|
2896 | 0 | if (!RB_TEST(wakeup_mutex)) { |
2897 | | // There was nobody else using this file when we closed it, so we never bothered to allocate a mutex: |
2898 | 0 | return; |
2899 | 0 | } |
2900 | | |
2901 | 0 | rb_mutex_lock(wakeup_mutex); |
2902 | 0 | while (!ccan_list_empty(rb_io_blocking_operations(io))) { |
2903 | 0 | rb_mutex_sleep(wakeup_mutex, Qnil); |
2904 | 0 | } |
2905 | 0 | rb_mutex_unlock(wakeup_mutex); |
2906 | | |
2907 | | // We are done closing: |
2908 | 0 | io->wakeup_mutex = Qnil; |
2909 | 0 | io->closing_ec = NULL; |
2910 | 0 | } |
2911 | | |
2912 | | void |
2913 | | rb_thread_fd_close(int fd) |
2914 | 0 | { |
2915 | 0 | rb_warn("rb_thread_fd_close is deprecated (and is now a no-op)."); |
2916 | 0 | } |
2917 | | |
2918 | | /* |
2919 | | * call-seq: |
2920 | | * raise(exception, message = exception.to_s, backtrace = nil, cause: $!) |
2921 | | * raise(message = nil, cause: $!) |
2922 | | * |
2923 | | * Raises an exception from the given thread. The caller does not have to be |
2924 | | * +thr+. See Kernel#raise for more information on arguments. |
2925 | | * |
2926 | | * Thread.abort_on_exception = true |
2927 | | * a = Thread.new { sleep(200) } |
2928 | | * a.raise("Gotcha") |
2929 | | * |
2930 | | * This will produce: |
2931 | | * |
2932 | | * prog.rb:3: Gotcha (RuntimeError) |
2933 | | * from prog.rb:2:in `initialize' |
2934 | | * from prog.rb:2:in `new' |
2935 | | * from prog.rb:2 |
2936 | | */ |
2937 | | |
2938 | | static VALUE |
2939 | | thread_raise_m(int argc, VALUE *argv, VALUE self) |
2940 | 0 | { |
2941 | 0 | rb_thread_t *target_th = rb_thread_ptr(self); |
2942 | 0 | const rb_thread_t *current_th = GET_THREAD(); |
2943 | |
|
2944 | 0 | threadptr_check_pending_interrupt_queue(target_th); |
2945 | |
|
2946 | 0 | if (rb_threadptr_dead(target_th)) { |
2947 | 0 | return Qnil; |
2948 | 0 | } |
2949 | | |
2950 | 0 | VALUE exception = rb_exception_setup(argc, argv); |
2951 | 0 | rb_threadptr_pending_interrupt_enque(target_th, exception); |
2952 | 0 | rb_threadptr_interrupt(target_th); |
2953 | | |
2954 | | /* To perform Thread.current.raise as Kernel.raise */ |
2955 | 0 | if (current_th == target_th) { |
2956 | 0 | RUBY_VM_CHECK_INTS(target_th->ec); |
2957 | 0 | } |
2958 | 0 | return Qnil; |
2959 | 0 | } |
2960 | | |
2961 | | |
2962 | | /* |
2963 | | * call-seq: |
2964 | | * thr.exit -> thr |
2965 | | * thr.kill -> thr |
2966 | | * thr.terminate -> thr |
2967 | | * |
2968 | | * Terminates +thr+ and schedules another thread to be run, returning |
2969 | | * the terminated Thread. If this is the main thread, or the last |
2970 | | * thread, exits the process. Note that the caller does not wait for |
2971 | | * the thread to terminate if the receiver is different from the currently |
2972 | | * running thread. The termination is asynchronous, and the thread can still |
2973 | | * run a small amount of ruby code before exiting. |
2974 | | */ |
2975 | | |
2976 | | VALUE |
2977 | | rb_thread_kill(VALUE thread) |
2978 | 0 | { |
2979 | 0 | rb_thread_t *target_th = rb_thread_ptr(thread); |
2980 | |
|
2981 | 0 | if (target_th->to_kill || target_th->status == THREAD_KILLED) { |
2982 | 0 | return thread; |
2983 | 0 | } |
2984 | 0 | if (target_th == target_th->vm->ractor.main_thread) { |
2985 | 0 | rb_exit(EXIT_SUCCESS); |
2986 | 0 | } |
2987 | | |
2988 | 0 | RUBY_DEBUG_LOG("target_th:%u", rb_th_serial(target_th)); |
2989 | |
|
2990 | 0 | if (target_th == GET_THREAD()) { |
2991 | | /* kill myself immediately */ |
2992 | 0 | rb_threadptr_to_kill(target_th); |
2993 | 0 | } |
2994 | 0 | else { |
2995 | 0 | threadptr_check_pending_interrupt_queue(target_th); |
2996 | 0 | rb_threadptr_pending_interrupt_enque(target_th, RUBY_FATAL_THREAD_KILLED); |
2997 | 0 | rb_threadptr_interrupt(target_th); |
2998 | 0 | } |
2999 | | |
3000 | 0 | return thread; |
3001 | 0 | } |
3002 | | |
3003 | | int |
3004 | | rb_thread_to_be_killed(VALUE thread) |
3005 | 0 | { |
3006 | 0 | rb_thread_t *target_th = rb_thread_ptr(thread); |
3007 | |
|
3008 | 0 | if (target_th->to_kill || target_th->status == THREAD_KILLED) { |
3009 | 0 | return TRUE; |
3010 | 0 | } |
3011 | 0 | return FALSE; |
3012 | 0 | } |
3013 | | |
3014 | | /* |
3015 | | * call-seq: |
3016 | | * Thread.kill(thread) -> thread |
3017 | | * |
3018 | | * Causes the given +thread+ to exit, see also Thread::exit. |
3019 | | * |
3020 | | * count = 0 |
3021 | | * a = Thread.new { loop { count += 1 } } |
3022 | | * sleep(0.1) #=> 0 |
3023 | | * Thread.kill(a) #=> #<Thread:0x401b3d30 dead> |
3024 | | * count #=> 93947 |
3025 | | * a.alive? #=> false |
3026 | | */ |
3027 | | |
3028 | | static VALUE |
3029 | | rb_thread_s_kill(VALUE obj, VALUE th) |
3030 | 0 | { |
3031 | 0 | return rb_thread_kill(th); |
3032 | 0 | } |
3033 | | |
3034 | | |
3035 | | /* |
3036 | | * call-seq: |
3037 | | * Thread.exit -> thread |
3038 | | * |
3039 | | * Terminates the currently running thread and schedules another thread to be |
3040 | | * run. |
3041 | | * |
3042 | | * If this thread is already marked to be killed, ::exit returns the Thread. |
3043 | | * |
3044 | | * If this is the main thread, or the last thread, exit the process. |
3045 | | */ |
3046 | | |
3047 | | static VALUE |
3048 | | rb_thread_exit(VALUE _) |
3049 | 0 | { |
3050 | 0 | rb_thread_t *th = GET_THREAD(); |
3051 | 0 | return rb_thread_kill(th->self); |
3052 | 0 | } |
3053 | | |
3054 | | |
3055 | | /* |
3056 | | * call-seq: |
3057 | | * thr.wakeup -> thr |
3058 | | * |
3059 | | * Marks a given thread as eligible for scheduling, however it may still |
3060 | | * remain blocked on I/O. |
3061 | | * |
3062 | | * *Note:* This does not invoke the scheduler, see #run for more information. |
3063 | | * |
3064 | | * c = Thread.new { Thread.stop; puts "hey!" } |
3065 | | * sleep 0.1 while c.status!='sleep' |
3066 | | * c.wakeup |
3067 | | * c.join |
3068 | | * #=> "hey!" |
3069 | | */ |
3070 | | |
3071 | | VALUE |
3072 | | rb_thread_wakeup(VALUE thread) |
3073 | 0 | { |
3074 | 0 | if (!RTEST(rb_thread_wakeup_alive(thread))) { |
3075 | 0 | rb_raise(rb_eThreadError, "killed thread"); |
3076 | 0 | } |
3077 | 0 | return thread; |
3078 | 0 | } |
3079 | | |
3080 | | VALUE |
3081 | | rb_thread_wakeup_alive(VALUE thread) |
3082 | 0 | { |
3083 | 0 | rb_thread_t *target_th = rb_thread_ptr(thread); |
3084 | 0 | if (target_th->status == THREAD_KILLED) return Qnil; |
3085 | | |
3086 | 0 | rb_threadptr_ready(target_th); |
3087 | |
|
3088 | 0 | if (target_th->status == THREAD_STOPPED || |
3089 | 0 | target_th->status == THREAD_STOPPED_FOREVER) { |
3090 | 0 | target_th->status = THREAD_RUNNABLE; |
3091 | 0 | } |
3092 | |
|
3093 | 0 | return thread; |
3094 | 0 | } |
3095 | | |
3096 | | |
3097 | | /* |
3098 | | * call-seq: |
3099 | | * thr.run -> thr |
3100 | | * |
3101 | | * Wakes up +thr+, making it eligible for scheduling. |
3102 | | * |
3103 | | * a = Thread.new { puts "a"; Thread.stop; puts "c" } |
3104 | | * sleep 0.1 while a.status!='sleep' |
3105 | | * puts "Got here" |
3106 | | * a.run |
3107 | | * a.join |
3108 | | * |
3109 | | * This will produce: |
3110 | | * |
3111 | | * a |
3112 | | * Got here |
3113 | | * c |
3114 | | * |
3115 | | * See also the instance method #wakeup. |
3116 | | */ |
3117 | | |
3118 | | VALUE |
3119 | | rb_thread_run(VALUE thread) |
3120 | 0 | { |
3121 | 0 | rb_thread_wakeup(thread); |
3122 | 0 | rb_thread_schedule(); |
3123 | 0 | return thread; |
3124 | 0 | } |
3125 | | |
3126 | | |
3127 | | VALUE |
3128 | | rb_thread_stop(void) |
3129 | 0 | { |
3130 | 0 | if (rb_thread_alone()) { |
3131 | 0 | rb_raise(rb_eThreadError, |
3132 | 0 | "stopping only thread\n\tnote: use sleep to stop forever"); |
3133 | 0 | } |
3134 | 0 | rb_thread_sleep_deadly(); |
3135 | 0 | return Qnil; |
3136 | 0 | } |
3137 | | |
3138 | | /* |
3139 | | * call-seq: |
3140 | | * Thread.stop -> nil |
3141 | | * |
3142 | | * Stops execution of the current thread, putting it into a ``sleep'' state, |
3143 | | * and schedules execution of another thread. |
3144 | | * |
3145 | | * a = Thread.new { print "a"; Thread.stop; print "c" } |
3146 | | * sleep 0.1 while a.status!='sleep' |
3147 | | * print "b" |
3148 | | * a.run |
3149 | | * a.join |
3150 | | * #=> "abc" |
3151 | | */ |
3152 | | |
3153 | | static VALUE |
3154 | | thread_stop(VALUE _) |
3155 | 0 | { |
3156 | 0 | return rb_thread_stop(); |
3157 | 0 | } |
3158 | | |
3159 | | /********************************************************************/ |
3160 | | |
3161 | | VALUE |
3162 | | rb_thread_list(void) |
3163 | 0 | { |
3164 | | // TODO |
3165 | 0 | return rb_ractor_thread_list(); |
3166 | 0 | } |
3167 | | |
3168 | | /* |
3169 | | * call-seq: |
3170 | | * Thread.list -> array |
3171 | | * |
3172 | | * Returns an array of Thread objects for all threads that are either runnable |
3173 | | * or stopped. |
3174 | | * |
3175 | | * Thread.new { sleep(200) } |
3176 | | * Thread.new { 1000000.times {|i| i*i } } |
3177 | | * Thread.new { Thread.stop } |
3178 | | * Thread.list.each {|t| p t} |
3179 | | * |
3180 | | * This will produce: |
3181 | | * |
3182 | | * #<Thread:0x401b3e84 sleep> |
3183 | | * #<Thread:0x401b3f38 run> |
3184 | | * #<Thread:0x401b3fb0 sleep> |
3185 | | * #<Thread:0x401bdf4c run> |
3186 | | */ |
3187 | | |
3188 | | static VALUE |
3189 | | thread_list(VALUE _) |
3190 | 0 | { |
3191 | 0 | return rb_thread_list(); |
3192 | 0 | } |
3193 | | |
3194 | | VALUE |
3195 | | rb_thread_current(void) |
3196 | 0 | { |
3197 | 0 | return GET_THREAD()->self; |
3198 | 0 | } |
3199 | | |
3200 | | /* |
3201 | | * call-seq: |
3202 | | * Thread.current -> thread |
3203 | | * |
3204 | | * Returns the currently executing thread. |
3205 | | * |
3206 | | * Thread.current #=> #<Thread:0x401bdf4c run> |
3207 | | */ |
3208 | | |
3209 | | static VALUE |
3210 | | thread_s_current(VALUE klass) |
3211 | 0 | { |
3212 | 0 | return rb_thread_current(); |
3213 | 0 | } |
3214 | | |
3215 | | VALUE |
3216 | | rb_thread_main(void) |
3217 | 0 | { |
3218 | 0 | return GET_RACTOR()->threads.main->self; |
3219 | 0 | } |
3220 | | |
3221 | | /* |
3222 | | * call-seq: |
3223 | | * Thread.main -> thread |
3224 | | * |
3225 | | * Returns the main thread. |
3226 | | */ |
3227 | | |
3228 | | static VALUE |
3229 | | rb_thread_s_main(VALUE klass) |
3230 | 0 | { |
3231 | 0 | return rb_thread_main(); |
3232 | 0 | } |
3233 | | |
3234 | | |
3235 | | /* |
3236 | | * call-seq: |
3237 | | * Thread.abort_on_exception -> true or false |
3238 | | * |
3239 | | * Returns the status of the global ``abort on exception'' condition. |
3240 | | * |
3241 | | * The default is +false+. |
3242 | | * |
3243 | | * When set to +true+, if any thread is aborted by an exception, the |
3244 | | * raised exception will be re-raised in the main thread. |
3245 | | * |
3246 | | * Can also be specified by the global $DEBUG flag or command line option |
3247 | | * +-d+. |
3248 | | * |
3249 | | * See also ::abort_on_exception=. |
3250 | | * |
3251 | | * There is also an instance level method to set this for a specific thread, |
3252 | | * see #abort_on_exception. |
3253 | | */ |
3254 | | |
3255 | | static VALUE |
3256 | | rb_thread_s_abort_exc(VALUE _) |
3257 | 0 | { |
3258 | 0 | return RBOOL(GET_THREAD()->vm->thread_abort_on_exception); |
3259 | 0 | } |
3260 | | |
3261 | | |
3262 | | /* |
3263 | | * call-seq: |
3264 | | * Thread.abort_on_exception= boolean -> true or false |
3265 | | * |
3266 | | * When set to +true+, if any thread is aborted by an exception, the |
3267 | | * raised exception will be re-raised in the main thread. |
3268 | | * Returns the new state. |
3269 | | * |
3270 | | * Thread.abort_on_exception = true |
3271 | | * t1 = Thread.new do |
3272 | | * puts "In new thread" |
3273 | | * raise "Exception from thread" |
3274 | | * end |
3275 | | * sleep(1) |
3276 | | * puts "not reached" |
3277 | | * |
3278 | | * This will produce: |
3279 | | * |
3280 | | * In new thread |
3281 | | * prog.rb:4: Exception from thread (RuntimeError) |
3282 | | * from prog.rb:2:in `initialize' |
3283 | | * from prog.rb:2:in `new' |
3284 | | * from prog.rb:2 |
3285 | | * |
3286 | | * See also ::abort_on_exception. |
3287 | | * |
3288 | | * There is also an instance level method to set this for a specific thread, |
3289 | | * see #abort_on_exception=. |
3290 | | */ |
3291 | | |
3292 | | static VALUE |
3293 | | rb_thread_s_abort_exc_set(VALUE self, VALUE val) |
3294 | 0 | { |
3295 | 0 | GET_THREAD()->vm->thread_abort_on_exception = RTEST(val); |
3296 | 0 | return val; |
3297 | 0 | } |
3298 | | |
3299 | | |
3300 | | /* |
3301 | | * call-seq: |
3302 | | * thr.abort_on_exception -> true or false |
3303 | | * |
3304 | | * Returns the status of the thread-local ``abort on exception'' condition for |
3305 | | * this +thr+. |
3306 | | * |
3307 | | * The default is +false+. |
3308 | | * |
3309 | | * See also #abort_on_exception=. |
3310 | | * |
3311 | | * There is also a class level method to set this for all threads, see |
3312 | | * ::abort_on_exception. |
3313 | | */ |
3314 | | |
3315 | | static VALUE |
3316 | | rb_thread_abort_exc(VALUE thread) |
3317 | 0 | { |
3318 | 0 | return RBOOL(rb_thread_ptr(thread)->abort_on_exception); |
3319 | 0 | } |
3320 | | |
3321 | | |
3322 | | /* |
3323 | | * call-seq: |
3324 | | * thr.abort_on_exception= boolean -> true or false |
3325 | | * |
3326 | | * When set to +true+, if this +thr+ is aborted by an exception, the |
3327 | | * raised exception will be re-raised in the main thread. |
3328 | | * |
3329 | | * See also #abort_on_exception. |
3330 | | * |
3331 | | * There is also a class level method to set this for all threads, see |
3332 | | * ::abort_on_exception=. |
3333 | | */ |
3334 | | |
3335 | | static VALUE |
3336 | | rb_thread_abort_exc_set(VALUE thread, VALUE val) |
3337 | 0 | { |
3338 | 0 | rb_thread_ptr(thread)->abort_on_exception = RTEST(val); |
3339 | 0 | return val; |
3340 | 0 | } |
3341 | | |
3342 | | |
3343 | | /* |
3344 | | * call-seq: |
3345 | | * Thread.report_on_exception -> true or false |
3346 | | * |
3347 | | * Returns the status of the global ``report on exception'' condition. |
3348 | | * |
3349 | | * The default is +true+ since Ruby 2.5. |
3350 | | * |
3351 | | * All threads created when this flag is true will report |
3352 | | * a message on $stderr if an exception kills the thread. |
3353 | | * |
3354 | | * Thread.new { 1.times { raise } } |
3355 | | * |
3356 | | * will produce this output on $stderr: |
3357 | | * |
3358 | | * #<Thread:...> terminated with exception (report_on_exception is true): |
3359 | | * Traceback (most recent call last): |
3360 | | * 2: from -e:1:in `block in <main>' |
3361 | | * 1: from -e:1:in `times' |
3362 | | * |
3363 | | * This is done to catch errors in threads early. |
3364 | | * In some cases, you might not want this output. |
3365 | | * There are multiple ways to avoid the extra output: |
3366 | | * |
3367 | | * * If the exception is not intended, the best is to fix the cause of |
3368 | | * the exception so it does not happen anymore. |
3369 | | * * If the exception is intended, it might be better to rescue it closer to |
3370 | | * where it is raised rather then let it kill the Thread. |
3371 | | * * If it is guaranteed the Thread will be joined with Thread#join or |
3372 | | * Thread#value, then it is safe to disable this report with |
3373 | | * <code>Thread.current.report_on_exception = false</code> |
3374 | | * when starting the Thread. |
3375 | | * However, this might handle the exception much later, or not at all |
3376 | | * if the Thread is never joined due to the parent thread being blocked, etc. |
3377 | | * |
3378 | | * See also ::report_on_exception=. |
3379 | | * |
3380 | | * There is also an instance level method to set this for a specific thread, |
3381 | | * see #report_on_exception=. |
3382 | | * |
3383 | | */ |
3384 | | |
3385 | | static VALUE |
3386 | | rb_thread_s_report_exc(VALUE _) |
3387 | 0 | { |
3388 | 0 | return RBOOL(GET_THREAD()->vm->thread_report_on_exception); |
3389 | 0 | } |
3390 | | |
3391 | | |
3392 | | /* |
3393 | | * call-seq: |
3394 | | * Thread.report_on_exception= boolean -> true or false |
3395 | | * |
3396 | | * Returns the new state. |
3397 | | * When set to +true+, all threads created afterwards will inherit the |
3398 | | * condition and report a message on $stderr if an exception kills a thread: |
3399 | | * |
3400 | | * Thread.report_on_exception = true |
3401 | | * t1 = Thread.new do |
3402 | | * puts "In new thread" |
3403 | | * raise "Exception from thread" |
3404 | | * end |
3405 | | * sleep(1) |
3406 | | * puts "In the main thread" |
3407 | | * |
3408 | | * This will produce: |
3409 | | * |
3410 | | * In new thread |
3411 | | * #<Thread:...prog.rb:2> terminated with exception (report_on_exception is true): |
3412 | | * Traceback (most recent call last): |
3413 | | * prog.rb:4:in `block in <main>': Exception from thread (RuntimeError) |
3414 | | * In the main thread |
3415 | | * |
3416 | | * See also ::report_on_exception. |
3417 | | * |
3418 | | * There is also an instance level method to set this for a specific thread, |
3419 | | * see #report_on_exception=. |
3420 | | */ |
3421 | | |
3422 | | static VALUE |
3423 | | rb_thread_s_report_exc_set(VALUE self, VALUE val) |
3424 | 0 | { |
3425 | 0 | GET_THREAD()->vm->thread_report_on_exception = RTEST(val); |
3426 | 0 | return val; |
3427 | 0 | } |
3428 | | |
3429 | | |
3430 | | /* |
3431 | | * call-seq: |
3432 | | * Thread.ignore_deadlock -> true or false |
3433 | | * |
3434 | | * Returns the status of the global ``ignore deadlock'' condition. |
3435 | | * The default is +false+, so that deadlock conditions are not ignored. |
3436 | | * |
3437 | | * See also ::ignore_deadlock=. |
3438 | | * |
3439 | | */ |
3440 | | |
3441 | | static VALUE |
3442 | | rb_thread_s_ignore_deadlock(VALUE _) |
3443 | 0 | { |
3444 | 0 | return RBOOL(GET_THREAD()->vm->thread_ignore_deadlock); |
3445 | 0 | } |
3446 | | |
3447 | | |
3448 | | /* |
3449 | | * call-seq: |
3450 | | * Thread.ignore_deadlock = boolean -> true or false |
3451 | | * |
3452 | | * Returns the new state. |
3453 | | * When set to +true+, the VM will not check for deadlock conditions. |
3454 | | * It is only useful to set this if your application can break a |
3455 | | * deadlock condition via some other means, such as a signal. |
3456 | | * |
3457 | | * Thread.ignore_deadlock = true |
3458 | | * queue = Thread::Queue.new |
3459 | | * |
3460 | | * trap(:SIGUSR1){queue.push "Received signal"} |
3461 | | * |
3462 | | * # raises fatal error unless ignoring deadlock |
3463 | | * puts queue.pop |
3464 | | * |
3465 | | * See also ::ignore_deadlock. |
3466 | | */ |
3467 | | |
3468 | | static VALUE |
3469 | | rb_thread_s_ignore_deadlock_set(VALUE self, VALUE val) |
3470 | 0 | { |
3471 | 0 | GET_THREAD()->vm->thread_ignore_deadlock = RTEST(val); |
3472 | 0 | return val; |
3473 | 0 | } |
3474 | | |
3475 | | |
3476 | | /* |
3477 | | * call-seq: |
3478 | | * thr.report_on_exception -> true or false |
3479 | | * |
3480 | | * Returns the status of the thread-local ``report on exception'' condition for |
3481 | | * this +thr+. |
3482 | | * |
3483 | | * The default value when creating a Thread is the value of |
3484 | | * the global flag Thread.report_on_exception. |
3485 | | * |
3486 | | * See also #report_on_exception=. |
3487 | | * |
3488 | | * There is also a class level method to set this for all new threads, see |
3489 | | * ::report_on_exception=. |
3490 | | */ |
3491 | | |
3492 | | static VALUE |
3493 | | rb_thread_report_exc(VALUE thread) |
3494 | 0 | { |
3495 | 0 | return RBOOL(rb_thread_ptr(thread)->report_on_exception); |
3496 | 0 | } |
3497 | | |
3498 | | |
3499 | | /* |
3500 | | * call-seq: |
3501 | | * thr.report_on_exception= boolean -> true or false |
3502 | | * |
3503 | | * When set to +true+, a message is printed on $stderr if an exception |
3504 | | * kills this +thr+. See ::report_on_exception for details. |
3505 | | * |
3506 | | * See also #report_on_exception. |
3507 | | * |
3508 | | * There is also a class level method to set this for all new threads, see |
3509 | | * ::report_on_exception=. |
3510 | | */ |
3511 | | |
3512 | | static VALUE |
3513 | | rb_thread_report_exc_set(VALUE thread, VALUE val) |
3514 | 0 | { |
3515 | 0 | rb_thread_ptr(thread)->report_on_exception = RTEST(val); |
3516 | 0 | return val; |
3517 | 0 | } |
3518 | | |
3519 | | |
3520 | | /* |
3521 | | * call-seq: |
3522 | | * thr.group -> thgrp or nil |
3523 | | * |
3524 | | * Returns the ThreadGroup which contains the given thread. |
3525 | | * |
3526 | | * Thread.main.group #=> #<ThreadGroup:0x4029d914> |
3527 | | */ |
3528 | | |
3529 | | VALUE |
3530 | | rb_thread_group(VALUE thread) |
3531 | 0 | { |
3532 | 0 | return rb_thread_ptr(thread)->thgroup; |
3533 | 0 | } |
3534 | | |
3535 | | static const char * |
3536 | | thread_status_name(rb_thread_t *th, int detail) |
3537 | 0 | { |
3538 | 0 | switch (th->status) { |
3539 | 0 | case THREAD_RUNNABLE: |
3540 | 0 | return th->to_kill ? "aborting" : "run"; |
3541 | 0 | case THREAD_STOPPED_FOREVER: |
3542 | 0 | if (detail) return "sleep_forever"; |
3543 | 0 | case THREAD_STOPPED: |
3544 | 0 | return "sleep"; |
3545 | 0 | case THREAD_KILLED: |
3546 | 0 | return "dead"; |
3547 | 0 | default: |
3548 | 0 | return "unknown"; |
3549 | 0 | } |
3550 | 0 | } |
3551 | | |
3552 | | static int |
3553 | | rb_threadptr_dead(rb_thread_t *th) |
3554 | 0 | { |
3555 | 0 | return th->status == THREAD_KILLED; |
3556 | 0 | } |
3557 | | |
3558 | | |
3559 | | /* |
3560 | | * call-seq: |
3561 | | * thr.status -> string, false or nil |
3562 | | * |
3563 | | * Returns the status of +thr+. |
3564 | | * |
3565 | | * [<tt>"sleep"</tt>] |
3566 | | * Returned if this thread is sleeping or waiting on I/O |
3567 | | * [<tt>"run"</tt>] |
3568 | | * When this thread is executing |
3569 | | * [<tt>"aborting"</tt>] |
3570 | | * If this thread is aborting |
3571 | | * [+false+] |
3572 | | * When this thread is terminated normally |
3573 | | * [+nil+] |
3574 | | * If terminated with an exception. |
3575 | | * |
3576 | | * a = Thread.new { raise("die now") } |
3577 | | * b = Thread.new { Thread.stop } |
3578 | | * c = Thread.new { Thread.exit } |
3579 | | * d = Thread.new { sleep } |
3580 | | * d.kill #=> #<Thread:0x401b3678 aborting> |
3581 | | * a.status #=> nil |
3582 | | * b.status #=> "sleep" |
3583 | | * c.status #=> false |
3584 | | * d.status #=> "aborting" |
3585 | | * Thread.current.status #=> "run" |
3586 | | * |
3587 | | * See also the instance methods #alive? and #stop? |
3588 | | */ |
3589 | | |
3590 | | static VALUE |
3591 | | rb_thread_status(VALUE thread) |
3592 | 0 | { |
3593 | 0 | rb_thread_t *target_th = rb_thread_ptr(thread); |
3594 | |
|
3595 | 0 | if (rb_threadptr_dead(target_th)) { |
3596 | 0 | if (!NIL_P(target_th->ec->errinfo) && |
3597 | 0 | !FIXNUM_P(target_th->ec->errinfo)) { |
3598 | 0 | return Qnil; |
3599 | 0 | } |
3600 | 0 | else { |
3601 | 0 | return Qfalse; |
3602 | 0 | } |
3603 | 0 | } |
3604 | 0 | else { |
3605 | 0 | return rb_str_new2(thread_status_name(target_th, FALSE)); |
3606 | 0 | } |
3607 | 0 | } |
3608 | | |
3609 | | |
3610 | | /* |
3611 | | * call-seq: |
3612 | | * thr.alive? -> true or false |
3613 | | * |
3614 | | * Returns +true+ if +thr+ is running or sleeping. |
3615 | | * |
3616 | | * thr = Thread.new { } |
3617 | | * thr.join #=> #<Thread:0x401b3fb0 dead> |
3618 | | * Thread.current.alive? #=> true |
3619 | | * thr.alive? #=> false |
3620 | | * |
3621 | | * See also #stop? and #status. |
3622 | | */ |
3623 | | |
3624 | | static VALUE |
3625 | | rb_thread_alive_p(VALUE thread) |
3626 | 0 | { |
3627 | 0 | return RBOOL(!thread_finished(rb_thread_ptr(thread))); |
3628 | 0 | } |
3629 | | |
3630 | | /* |
3631 | | * call-seq: |
3632 | | * thr.stop? -> true or false |
3633 | | * |
3634 | | * Returns +true+ if +thr+ is dead or sleeping. |
3635 | | * |
3636 | | * a = Thread.new { Thread.stop } |
3637 | | * b = Thread.current |
3638 | | * a.stop? #=> true |
3639 | | * b.stop? #=> false |
3640 | | * |
3641 | | * See also #alive? and #status. |
3642 | | */ |
3643 | | |
3644 | | static VALUE |
3645 | | rb_thread_stop_p(VALUE thread) |
3646 | 0 | { |
3647 | 0 | rb_thread_t *th = rb_thread_ptr(thread); |
3648 | |
|
3649 | 0 | if (rb_threadptr_dead(th)) { |
3650 | 0 | return Qtrue; |
3651 | 0 | } |
3652 | 0 | return RBOOL(th->status == THREAD_STOPPED || th->status == THREAD_STOPPED_FOREVER); |
3653 | 0 | } |
3654 | | |
3655 | | /* |
3656 | | * call-seq: |
3657 | | * thr.name -> string |
3658 | | * |
3659 | | * show the name of the thread. |
3660 | | */ |
3661 | | |
3662 | | static VALUE |
3663 | | rb_thread_getname(VALUE thread) |
3664 | 0 | { |
3665 | 0 | return rb_thread_ptr(thread)->name; |
3666 | 0 | } |
3667 | | |
3668 | | /* |
3669 | | * call-seq: |
3670 | | * thr.name=(name) -> string |
3671 | | * |
3672 | | * set given name to the ruby thread. |
3673 | | * On some platform, it may set the name to pthread and/or kernel. |
3674 | | */ |
3675 | | |
3676 | | static VALUE |
3677 | | rb_thread_setname(VALUE thread, VALUE name) |
3678 | 0 | { |
3679 | 0 | rb_thread_t *target_th = rb_thread_ptr(thread); |
3680 | |
|
3681 | 0 | if (!NIL_P(name)) { |
3682 | 0 | rb_encoding *enc; |
3683 | 0 | StringValueCStr(name); |
3684 | 0 | enc = rb_enc_get(name); |
3685 | 0 | if (!rb_enc_asciicompat(enc)) { |
3686 | 0 | rb_raise(rb_eArgError, "ASCII incompatible encoding (%s)", |
3687 | 0 | rb_enc_name(enc)); |
3688 | 0 | } |
3689 | 0 | name = rb_str_new_frozen(name); |
3690 | 0 | } |
3691 | 0 | target_th->name = name; |
3692 | 0 | if (threadptr_initialized(target_th) && target_th->has_dedicated_nt) { |
3693 | 0 | native_set_another_thread_name(target_th->nt->thread_id, name); |
3694 | 0 | } |
3695 | 0 | return name; |
3696 | 0 | } |
3697 | | |
3698 | | #if USE_NATIVE_THREAD_NATIVE_THREAD_ID |
3699 | | /* |
3700 | | * call-seq: |
3701 | | * thr.native_thread_id -> integer |
3702 | | * |
3703 | | * Return the native thread ID which is used by the Ruby thread. |
3704 | | * |
3705 | | * The ID depends on the OS. (not POSIX thread ID returned by pthread_self(3)) |
3706 | | * * On Linux it is TID returned by gettid(2). |
3707 | | * * On macOS it is the system-wide unique integral ID of thread returned |
3708 | | * by pthread_threadid_np(3). |
3709 | | * * On FreeBSD it is the unique integral ID of the thread returned by |
3710 | | * pthread_getthreadid_np(3). |
3711 | | * * On Windows it is the thread identifier returned by GetThreadId(). |
3712 | | * * On other platforms, it raises NotImplementedError. |
3713 | | * |
3714 | | * NOTE: |
3715 | | * If the thread is not associated yet or already deassociated with a native |
3716 | | * thread, it returns _nil_. |
3717 | | * If the Ruby implementation uses M:N thread model, the ID may change |
3718 | | * depending on the timing. |
3719 | | */ |
3720 | | |
3721 | | static VALUE |
3722 | | rb_thread_native_thread_id(VALUE thread) |
3723 | 0 | { |
3724 | 0 | rb_thread_t *target_th = rb_thread_ptr(thread); |
3725 | 0 | if (rb_threadptr_dead(target_th)) return Qnil; |
3726 | 0 | return native_thread_native_thread_id(target_th); |
3727 | 0 | } |
3728 | | #else |
3729 | | # define rb_thread_native_thread_id rb_f_notimplement |
3730 | | #endif |
3731 | | |
3732 | | /* |
3733 | | * call-seq: |
3734 | | * thr.to_s -> string |
3735 | | * |
3736 | | * Dump the name, id, and status of _thr_ to a string. |
3737 | | */ |
3738 | | |
3739 | | static VALUE |
3740 | | rb_thread_to_s(VALUE thread) |
3741 | 0 | { |
3742 | 0 | VALUE cname = rb_class_path(rb_obj_class(thread)); |
3743 | 0 | rb_thread_t *target_th = rb_thread_ptr(thread); |
3744 | 0 | const char *status; |
3745 | 0 | VALUE str, loc; |
3746 | |
|
3747 | 0 | status = thread_status_name(target_th, TRUE); |
3748 | 0 | str = rb_sprintf("#<%"PRIsVALUE":%p", cname, (void *)thread); |
3749 | 0 | if (!NIL_P(target_th->name)) { |
3750 | 0 | rb_str_catf(str, "@%"PRIsVALUE, target_th->name); |
3751 | 0 | } |
3752 | 0 | if ((loc = threadptr_invoke_proc_location(target_th)) != Qnil) { |
3753 | 0 | rb_str_catf(str, " %"PRIsVALUE":%"PRIsVALUE, |
3754 | 0 | RARRAY_AREF(loc, 0), RARRAY_AREF(loc, 1)); |
3755 | 0 | } |
3756 | 0 | rb_str_catf(str, " %s>", status); |
3757 | |
|
3758 | 0 | return str; |
3759 | 0 | } |
3760 | | |
3761 | | /* variables for recursive traversals */ |
3762 | 0 | #define recursive_key id__recursive_key__ |
3763 | | |
3764 | | static VALUE |
3765 | | threadptr_local_aref(rb_thread_t *th, ID id) |
3766 | 0 | { |
3767 | 0 | if (id == recursive_key) { |
3768 | 0 | return th->ec->local_storage_recursive_hash; |
3769 | 0 | } |
3770 | 0 | else { |
3771 | 0 | VALUE val; |
3772 | 0 | struct rb_id_table *local_storage = th->ec->local_storage; |
3773 | |
|
3774 | 0 | if (local_storage != NULL && rb_id_table_lookup(local_storage, id, &val)) { |
3775 | 0 | return val; |
3776 | 0 | } |
3777 | 0 | else { |
3778 | 0 | return Qnil; |
3779 | 0 | } |
3780 | 0 | } |
3781 | 0 | } |
3782 | | |
3783 | | VALUE |
3784 | | rb_thread_local_aref(VALUE thread, ID id) |
3785 | 0 | { |
3786 | 0 | return threadptr_local_aref(rb_thread_ptr(thread), id); |
3787 | 0 | } |
3788 | | |
3789 | | /* |
3790 | | * call-seq: |
3791 | | * thr[sym] -> obj or nil |
3792 | | * |
3793 | | * Attribute Reference---Returns the value of a fiber-local variable (current thread's root fiber |
3794 | | * if not explicitly inside a Fiber), using either a symbol or a string name. |
3795 | | * If the specified variable does not exist, returns +nil+. |
3796 | | * |
3797 | | * [ |
3798 | | * Thread.new { Thread.current["name"] = "A" }, |
3799 | | * Thread.new { Thread.current[:name] = "B" }, |
3800 | | * Thread.new { Thread.current["name"] = "C" } |
3801 | | * ].each do |th| |
3802 | | * th.join |
3803 | | * puts "#{th.inspect}: #{th[:name]}" |
3804 | | * end |
3805 | | * |
3806 | | * This will produce: |
3807 | | * |
3808 | | * #<Thread:0x00000002a54220 dead>: A |
3809 | | * #<Thread:0x00000002a541a8 dead>: B |
3810 | | * #<Thread:0x00000002a54130 dead>: C |
3811 | | * |
3812 | | * Thread#[] and Thread#[]= are not thread-local but fiber-local. |
3813 | | * This confusion did not exist in Ruby 1.8 because |
3814 | | * fibers are only available since Ruby 1.9. |
3815 | | * Ruby 1.9 chooses that the methods behaves fiber-local to save |
3816 | | * following idiom for dynamic scope. |
3817 | | * |
3818 | | * def meth(newvalue) |
3819 | | * begin |
3820 | | * oldvalue = Thread.current[:name] |
3821 | | * Thread.current[:name] = newvalue |
3822 | | * yield |
3823 | | * ensure |
3824 | | * Thread.current[:name] = oldvalue |
3825 | | * end |
3826 | | * end |
3827 | | * |
3828 | | * The idiom may not work as dynamic scope if the methods are thread-local |
3829 | | * and a given block switches fiber. |
3830 | | * |
3831 | | * f = Fiber.new { |
3832 | | * meth(1) { |
3833 | | * Fiber.yield |
3834 | | * } |
3835 | | * } |
3836 | | * meth(2) { |
3837 | | * f.resume |
3838 | | * } |
3839 | | * f.resume |
3840 | | * p Thread.current[:name] |
3841 | | * #=> nil if fiber-local |
3842 | | * #=> 2 if thread-local (The value 2 is leaked to outside of meth method.) |
3843 | | * |
3844 | | * For thread-local variables, please see #thread_variable_get and |
3845 | | * #thread_variable_set. |
3846 | | * |
3847 | | */ |
3848 | | |
3849 | | static VALUE |
3850 | | rb_thread_aref(VALUE thread, VALUE key) |
3851 | 0 | { |
3852 | 0 | ID id = rb_check_id(&key); |
3853 | 0 | if (!id) return Qnil; |
3854 | 0 | return rb_thread_local_aref(thread, id); |
3855 | 0 | } |
3856 | | |
3857 | | /* |
3858 | | * call-seq: |
3859 | | * thr.fetch(sym) -> obj |
3860 | | * thr.fetch(sym) { } -> obj |
3861 | | * thr.fetch(sym, default) -> obj |
3862 | | * |
3863 | | * Returns a fiber-local for the given key. If the key can't be |
3864 | | * found, there are several options: With no other arguments, it will |
3865 | | * raise a KeyError exception; if <i>default</i> is given, then that |
3866 | | * will be returned; if the optional code block is specified, then |
3867 | | * that will be run and its result returned. See Thread#[] and |
3868 | | * Hash#fetch. |
3869 | | */ |
3870 | | static VALUE |
3871 | | rb_thread_fetch(int argc, VALUE *argv, VALUE self) |
3872 | 0 | { |
3873 | 0 | VALUE key, val; |
3874 | 0 | ID id; |
3875 | 0 | rb_thread_t *target_th = rb_thread_ptr(self); |
3876 | 0 | int block_given; |
3877 | |
|
3878 | 0 | rb_check_arity(argc, 1, 2); |
3879 | 0 | key = argv[0]; |
3880 | |
|
3881 | 0 | block_given = rb_block_given_p(); |
3882 | 0 | if (block_given && argc == 2) { |
3883 | 0 | rb_warn("block supersedes default value argument"); |
3884 | 0 | } |
3885 | |
|
3886 | 0 | id = rb_check_id(&key); |
3887 | |
|
3888 | 0 | if (id == recursive_key) { |
3889 | 0 | return target_th->ec->local_storage_recursive_hash; |
3890 | 0 | } |
3891 | 0 | else if (id && target_th->ec->local_storage && |
3892 | 0 | rb_id_table_lookup(target_th->ec->local_storage, id, &val)) { |
3893 | 0 | return val; |
3894 | 0 | } |
3895 | 0 | else if (block_given) { |
3896 | 0 | return rb_yield(key); |
3897 | 0 | } |
3898 | 0 | else if (argc == 1) { |
3899 | 0 | rb_key_err_raise(rb_sprintf("key not found: %+"PRIsVALUE, key), self, key); |
3900 | 0 | } |
3901 | 0 | else { |
3902 | 0 | return argv[1]; |
3903 | 0 | } |
3904 | 0 | } |
3905 | | |
3906 | | static VALUE |
3907 | | threadptr_local_aset(rb_thread_t *th, ID id, VALUE val) |
3908 | 0 | { |
3909 | 0 | if (id == recursive_key) { |
3910 | 0 | th->ec->local_storage_recursive_hash = val; |
3911 | 0 | return val; |
3912 | 0 | } |
3913 | 0 | else { |
3914 | 0 | struct rb_id_table *local_storage = th->ec->local_storage; |
3915 | |
|
3916 | 0 | if (NIL_P(val)) { |
3917 | 0 | if (!local_storage) return Qnil; |
3918 | 0 | rb_id_table_delete(local_storage, id); |
3919 | 0 | return Qnil; |
3920 | 0 | } |
3921 | 0 | else { |
3922 | 0 | if (local_storage == NULL) { |
3923 | 0 | th->ec->local_storage = local_storage = rb_id_table_create(0); |
3924 | 0 | } |
3925 | 0 | rb_id_table_insert(local_storage, id, val); |
3926 | 0 | return val; |
3927 | 0 | } |
3928 | 0 | } |
3929 | 0 | } |
3930 | | |
3931 | | VALUE |
3932 | | rb_thread_local_aset(VALUE thread, ID id, VALUE val) |
3933 | 0 | { |
3934 | 0 | if (OBJ_FROZEN(thread)) { |
3935 | 0 | rb_frozen_error_raise(thread, "can't modify frozen thread locals"); |
3936 | 0 | } |
3937 | | |
3938 | 0 | return threadptr_local_aset(rb_thread_ptr(thread), id, val); |
3939 | 0 | } |
3940 | | |
3941 | | /* |
3942 | | * call-seq: |
3943 | | * thr[sym] = obj -> obj |
3944 | | * |
3945 | | * Attribute Assignment---Sets or creates the value of a fiber-local variable, |
3946 | | * using either a symbol or a string. |
3947 | | * |
3948 | | * See also Thread#[]. |
3949 | | * |
3950 | | * For thread-local variables, please see #thread_variable_set and |
3951 | | * #thread_variable_get. |
3952 | | */ |
3953 | | |
3954 | | static VALUE |
3955 | | rb_thread_aset(VALUE self, VALUE id, VALUE val) |
3956 | 0 | { |
3957 | 0 | return rb_thread_local_aset(self, rb_to_id(id), val); |
3958 | 0 | } |
3959 | | |
3960 | | /* |
3961 | | * call-seq: |
3962 | | * thr.thread_variable_get(key) -> obj or nil |
3963 | | * |
3964 | | * Returns the value of a thread local variable that has been set. Note that |
3965 | | * these are different than fiber local values. For fiber local values, |
3966 | | * please see Thread#[] and Thread#[]=. |
3967 | | * |
3968 | | * Thread local values are carried along with threads, and do not respect |
3969 | | * fibers. For example: |
3970 | | * |
3971 | | * Thread.new { |
3972 | | * Thread.current.thread_variable_set("foo", "bar") # set a thread local |
3973 | | * Thread.current["foo"] = "bar" # set a fiber local |
3974 | | * |
3975 | | * Fiber.new { |
3976 | | * Fiber.yield [ |
3977 | | * Thread.current.thread_variable_get("foo"), # get the thread local |
3978 | | * Thread.current["foo"], # get the fiber local |
3979 | | * ] |
3980 | | * }.resume |
3981 | | * }.join.value # => ['bar', nil] |
3982 | | * |
3983 | | * The value "bar" is returned for the thread local, where nil is returned |
3984 | | * for the fiber local. The fiber is executed in the same thread, so the |
3985 | | * thread local values are available. |
3986 | | */ |
3987 | | |
3988 | | static VALUE |
3989 | | rb_thread_variable_get(VALUE thread, VALUE key) |
3990 | 0 | { |
3991 | 0 | VALUE locals; |
3992 | 0 | VALUE symbol = rb_to_symbol(key); |
3993 | |
|
3994 | 0 | if (LIKELY(!THREAD_LOCAL_STORAGE_INITIALISED_P(thread))) { |
3995 | 0 | return Qnil; |
3996 | 0 | } |
3997 | 0 | locals = rb_thread_local_storage(thread); |
3998 | 0 | return rb_hash_aref(locals, symbol); |
3999 | 0 | } |
4000 | | |
4001 | | /* |
4002 | | * call-seq: |
4003 | | * thr.thread_variable_set(key, value) |
4004 | | * |
4005 | | * Sets a thread local with +key+ to +value+. Note that these are local to |
4006 | | * threads, and not to fibers. Please see Thread#thread_variable_get and |
4007 | | * Thread#[] for more information. |
4008 | | */ |
4009 | | |
4010 | | static VALUE |
4011 | | rb_thread_variable_set(VALUE thread, VALUE key, VALUE val) |
4012 | 0 | { |
4013 | 0 | VALUE locals; |
4014 | |
|
4015 | 0 | if (OBJ_FROZEN(thread)) { |
4016 | 0 | rb_frozen_error_raise(thread, "can't modify frozen thread locals"); |
4017 | 0 | } |
4018 | | |
4019 | 0 | locals = rb_thread_local_storage(thread); |
4020 | 0 | return rb_hash_aset(locals, rb_to_symbol(key), val); |
4021 | 0 | } |
4022 | | |
4023 | | /* |
4024 | | * call-seq: |
4025 | | * thr.key?(sym) -> true or false |
4026 | | * |
4027 | | * Returns +true+ if the given string (or symbol) exists as a fiber-local |
4028 | | * variable. |
4029 | | * |
4030 | | * me = Thread.current |
4031 | | * me[:oliver] = "a" |
4032 | | * me.key?(:oliver) #=> true |
4033 | | * me.key?(:stanley) #=> false |
4034 | | */ |
4035 | | |
4036 | | static VALUE |
4037 | | rb_thread_key_p(VALUE self, VALUE key) |
4038 | 0 | { |
4039 | 0 | VALUE val; |
4040 | 0 | ID id = rb_check_id(&key); |
4041 | 0 | struct rb_id_table *local_storage = rb_thread_ptr(self)->ec->local_storage; |
4042 | |
|
4043 | 0 | if (!id || local_storage == NULL) { |
4044 | 0 | return Qfalse; |
4045 | 0 | } |
4046 | 0 | return RBOOL(rb_id_table_lookup(local_storage, id, &val)); |
4047 | 0 | } |
4048 | | |
4049 | | static enum rb_id_table_iterator_result |
4050 | | thread_keys_i(ID key, VALUE value, void *ary) |
4051 | 0 | { |
4052 | 0 | rb_ary_push((VALUE)ary, ID2SYM(key)); |
4053 | 0 | return ID_TABLE_CONTINUE; |
4054 | 0 | } |
4055 | | |
4056 | | int |
4057 | | rb_thread_alone(void) |
4058 | 0 | { |
4059 | | // TODO |
4060 | 0 | return rb_ractor_living_thread_num(GET_RACTOR()) == 1; |
4061 | 0 | } |
4062 | | |
4063 | | /* |
4064 | | * call-seq: |
4065 | | * thr.keys -> array |
4066 | | * |
4067 | | * Returns an array of the names of the fiber-local variables (as Symbols). |
4068 | | * |
4069 | | * thr = Thread.new do |
4070 | | * Thread.current[:cat] = 'meow' |
4071 | | * Thread.current["dog"] = 'woof' |
4072 | | * end |
4073 | | * thr.join #=> #<Thread:0x401b3f10 dead> |
4074 | | * thr.keys #=> [:dog, :cat] |
4075 | | */ |
4076 | | |
4077 | | static VALUE |
4078 | | rb_thread_keys(VALUE self) |
4079 | 0 | { |
4080 | 0 | struct rb_id_table *local_storage = rb_thread_ptr(self)->ec->local_storage; |
4081 | 0 | VALUE ary = rb_ary_new(); |
4082 | |
|
4083 | 0 | if (local_storage) { |
4084 | 0 | rb_id_table_foreach(local_storage, thread_keys_i, (void *)ary); |
4085 | 0 | } |
4086 | 0 | return ary; |
4087 | 0 | } |
4088 | | |
4089 | | static int |
4090 | | keys_i(VALUE key, VALUE value, VALUE ary) |
4091 | 0 | { |
4092 | 0 | rb_ary_push(ary, key); |
4093 | 0 | return ST_CONTINUE; |
4094 | 0 | } |
4095 | | |
4096 | | /* |
4097 | | * call-seq: |
4098 | | * thr.thread_variables -> array |
4099 | | * |
4100 | | * Returns an array of the names of the thread-local variables (as Symbols). |
4101 | | * |
4102 | | * thr = Thread.new do |
4103 | | * Thread.current.thread_variable_set(:cat, 'meow') |
4104 | | * Thread.current.thread_variable_set("dog", 'woof') |
4105 | | * end |
4106 | | * thr.join #=> #<Thread:0x401b3f10 dead> |
4107 | | * thr.thread_variables #=> [:dog, :cat] |
4108 | | * |
4109 | | * Note that these are not fiber local variables. Please see Thread#[] and |
4110 | | * Thread#thread_variable_get for more details. |
4111 | | */ |
4112 | | |
4113 | | static VALUE |
4114 | | rb_thread_variables(VALUE thread) |
4115 | 0 | { |
4116 | 0 | VALUE locals; |
4117 | 0 | VALUE ary; |
4118 | |
|
4119 | 0 | ary = rb_ary_new(); |
4120 | 0 | if (LIKELY(!THREAD_LOCAL_STORAGE_INITIALISED_P(thread))) { |
4121 | 0 | return ary; |
4122 | 0 | } |
4123 | 0 | locals = rb_thread_local_storage(thread); |
4124 | 0 | rb_hash_foreach(locals, keys_i, ary); |
4125 | |
|
4126 | 0 | return ary; |
4127 | 0 | } |
4128 | | |
4129 | | /* |
4130 | | * call-seq: |
4131 | | * thr.thread_variable?(key) -> true or false |
4132 | | * |
4133 | | * Returns +true+ if the given string (or symbol) exists as a thread-local |
4134 | | * variable. |
4135 | | * |
4136 | | * me = Thread.current |
4137 | | * me.thread_variable_set(:oliver, "a") |
4138 | | * me.thread_variable?(:oliver) #=> true |
4139 | | * me.thread_variable?(:stanley) #=> false |
4140 | | * |
4141 | | * Note that these are not fiber local variables. Please see Thread#[] and |
4142 | | * Thread#thread_variable_get for more details. |
4143 | | */ |
4144 | | |
4145 | | static VALUE |
4146 | | rb_thread_variable_p(VALUE thread, VALUE key) |
4147 | 0 | { |
4148 | 0 | VALUE locals; |
4149 | 0 | VALUE symbol = rb_to_symbol(key); |
4150 | |
|
4151 | 0 | if (LIKELY(!THREAD_LOCAL_STORAGE_INITIALISED_P(thread))) { |
4152 | 0 | return Qfalse; |
4153 | 0 | } |
4154 | 0 | locals = rb_thread_local_storage(thread); |
4155 | |
|
4156 | 0 | return RBOOL(rb_hash_lookup(locals, symbol) != Qnil); |
4157 | 0 | } |
4158 | | |
4159 | | /* |
4160 | | * call-seq: |
4161 | | * thr.priority -> integer |
4162 | | * |
4163 | | * Returns the priority of <i>thr</i>. Default is inherited from the |
4164 | | * current thread which creating the new thread, or zero for the |
4165 | | * initial main thread; higher-priority thread will run more frequently |
4166 | | * than lower-priority threads (but lower-priority threads can also run). |
4167 | | * |
4168 | | * This is just hint for Ruby thread scheduler. It may be ignored on some |
4169 | | * platform. |
4170 | | * |
4171 | | * Thread.current.priority #=> 0 |
4172 | | */ |
4173 | | |
4174 | | static VALUE |
4175 | | rb_thread_priority(VALUE thread) |
4176 | 0 | { |
4177 | 0 | return INT2NUM(rb_thread_ptr(thread)->priority); |
4178 | 0 | } |
4179 | | |
4180 | | |
4181 | | /* |
4182 | | * call-seq: |
4183 | | * thr.priority= integer -> thr |
4184 | | * |
4185 | | * Sets the priority of <i>thr</i> to <i>integer</i>. Higher-priority threads |
4186 | | * will run more frequently than lower-priority threads (but lower-priority |
4187 | | * threads can also run). |
4188 | | * |
4189 | | * This is just hint for Ruby thread scheduler. It may be ignored on some |
4190 | | * platform. |
4191 | | * |
4192 | | * count1 = count2 = 0 |
4193 | | * a = Thread.new do |
4194 | | * loop { count1 += 1 } |
4195 | | * end |
4196 | | * a.priority = -1 |
4197 | | * |
4198 | | * b = Thread.new do |
4199 | | * loop { count2 += 1 } |
4200 | | * end |
4201 | | * b.priority = -2 |
4202 | | * sleep 1 #=> 1 |
4203 | | * count1 #=> 622504 |
4204 | | * count2 #=> 5832 |
4205 | | */ |
4206 | | |
4207 | | static VALUE |
4208 | | rb_thread_priority_set(VALUE thread, VALUE prio) |
4209 | 0 | { |
4210 | 0 | rb_thread_t *target_th = rb_thread_ptr(thread); |
4211 | 0 | int priority; |
4212 | |
|
4213 | | #if USE_NATIVE_THREAD_PRIORITY |
4214 | | target_th->priority = NUM2INT(prio); |
4215 | | native_thread_apply_priority(th); |
4216 | | #else |
4217 | 0 | priority = NUM2INT(prio); |
4218 | 0 | if (priority > RUBY_THREAD_PRIORITY_MAX) { |
4219 | 0 | priority = RUBY_THREAD_PRIORITY_MAX; |
4220 | 0 | } |
4221 | 0 | else if (priority < RUBY_THREAD_PRIORITY_MIN) { |
4222 | 0 | priority = RUBY_THREAD_PRIORITY_MIN; |
4223 | 0 | } |
4224 | 0 | target_th->priority = (int8_t)priority; |
4225 | 0 | #endif |
4226 | 0 | return INT2NUM(target_th->priority); |
4227 | 0 | } |
4228 | | |
4229 | | /* for IO */ |
4230 | | |
4231 | | #if defined(NFDBITS) && defined(HAVE_RB_FD_INIT) |
4232 | | |
4233 | | /* |
4234 | | * several Unix platforms support file descriptors bigger than FD_SETSIZE |
4235 | | * in select(2) system call. |
4236 | | * |
4237 | | * - Linux 2.2.12 (?) |
4238 | | * - NetBSD 1.2 (src/sys/kern/sys_generic.c:1.25) |
4239 | | * select(2) documents how to allocate fd_set dynamically. |
4240 | | * http://netbsd.gw.com/cgi-bin/man-cgi?select++NetBSD-4.0 |
4241 | | * - FreeBSD 2.2 (src/sys/kern/sys_generic.c:1.19) |
4242 | | * - OpenBSD 2.0 (src/sys/kern/sys_generic.c:1.4) |
4243 | | * select(2) documents how to allocate fd_set dynamically. |
4244 | | * http://www.openbsd.org/cgi-bin/man.cgi?query=select&manpath=OpenBSD+4.4 |
4245 | | * - Solaris 8 has select_large_fdset |
4246 | | * - Mac OS X 10.7 (Lion) |
4247 | | * select(2) returns EINVAL if nfds is greater than FD_SET_SIZE and |
4248 | | * _DARWIN_UNLIMITED_SELECT (or _DARWIN_C_SOURCE) isn't defined. |
4249 | | * https://developer.apple.com/library/archive/releasenotes/Darwin/SymbolVariantsRelNotes/index.html |
4250 | | * |
4251 | | * When fd_set is not big enough to hold big file descriptors, |
4252 | | * it should be allocated dynamically. |
4253 | | * Note that this assumes fd_set is structured as bitmap. |
4254 | | * |
4255 | | * rb_fd_init allocates the memory. |
4256 | | * rb_fd_term free the memory. |
4257 | | * rb_fd_set may re-allocates bitmap. |
4258 | | * |
4259 | | * So rb_fd_set doesn't reject file descriptors bigger than FD_SETSIZE. |
4260 | | */ |
4261 | | |
4262 | | void |
4263 | | rb_fd_init(rb_fdset_t *fds) |
4264 | 0 | { |
4265 | 0 | fds->maxfd = 0; |
4266 | 0 | fds->fdset = ALLOC(fd_set); |
4267 | 0 | FD_ZERO(fds->fdset); |
4268 | 0 | } |
4269 | | |
4270 | | static inline size_t |
4271 | | fdset_memsize(int maxfd) |
4272 | 0 | { |
4273 | 0 | size_t o = howmany(maxfd, NFDBITS) * sizeof(fd_mask); |
4274 | 0 | if (o < sizeof(fd_set)) { |
4275 | 0 | return sizeof(fd_set); |
4276 | 0 | } |
4277 | 0 | return o; |
4278 | 0 | } |
4279 | | |
4280 | | void |
4281 | | rb_fd_init_copy(rb_fdset_t *dst, rb_fdset_t *src) |
4282 | 0 | { |
4283 | 0 | size_t size = fdset_memsize(rb_fd_max(src)); |
4284 | 0 | dst->maxfd = src->maxfd; |
4285 | 0 | dst->fdset = xmalloc(size); |
4286 | 0 | memcpy(dst->fdset, src->fdset, size); |
4287 | 0 | } |
4288 | | |
4289 | | void |
4290 | | rb_fd_term(rb_fdset_t *fds) |
4291 | 0 | { |
4292 | 0 | ruby_sized_xfree(fds->fdset, fdset_memsize(fds->maxfd)); |
4293 | 0 | fds->maxfd = 0; |
4294 | 0 | fds->fdset = 0; |
4295 | 0 | } |
4296 | | |
4297 | | void |
4298 | | rb_fd_zero(rb_fdset_t *fds) |
4299 | 0 | { |
4300 | 0 | if (fds->fdset) |
4301 | 0 | MEMZERO(fds->fdset, fd_mask, howmany(fds->maxfd, NFDBITS)); |
4302 | 0 | } |
4303 | | |
4304 | | static void |
4305 | | rb_fd_resize(int n, rb_fdset_t *fds) |
4306 | 0 | { |
4307 | 0 | size_t m = fdset_memsize(n + 1); |
4308 | 0 | size_t o = fdset_memsize(fds->maxfd); |
4309 | |
|
4310 | 0 | if (m > o) { |
4311 | 0 | fds->fdset = ruby_sized_xrealloc(fds->fdset, m, o); |
4312 | 0 | memset((char *)fds->fdset + o, 0, m - o); |
4313 | 0 | } |
4314 | 0 | if (n >= fds->maxfd) fds->maxfd = n + 1; |
4315 | 0 | } |
4316 | | |
4317 | | void |
4318 | | rb_fd_set(int n, rb_fdset_t *fds) |
4319 | 0 | { |
4320 | 0 | rb_fd_resize(n, fds); |
4321 | 0 | FD_SET(n, fds->fdset); |
4322 | 0 | } |
4323 | | |
4324 | | void |
4325 | | rb_fd_clr(int n, rb_fdset_t *fds) |
4326 | 0 | { |
4327 | 0 | if (n >= fds->maxfd) return; |
4328 | 0 | FD_CLR(n, fds->fdset); |
4329 | 0 | } |
4330 | | |
4331 | | int |
4332 | | rb_fd_isset(int n, const rb_fdset_t *fds) |
4333 | 0 | { |
4334 | 0 | if (n >= fds->maxfd) return 0; |
4335 | 0 | return FD_ISSET(n, fds->fdset) != 0; /* "!= 0" avoids FreeBSD PR 91421 */ |
4336 | 0 | } |
4337 | | |
4338 | | void |
4339 | | rb_fd_copy(rb_fdset_t *dst, const fd_set *src, int max) |
4340 | 0 | { |
4341 | 0 | size_t size = fdset_memsize(max); |
4342 | 0 | dst->fdset = ruby_sized_xrealloc(dst->fdset, size, fdset_memsize(dst->maxfd)); |
4343 | 0 | dst->maxfd = max; |
4344 | 0 | memcpy(dst->fdset, src, size); |
4345 | 0 | } |
4346 | | |
4347 | | void |
4348 | | rb_fd_dup(rb_fdset_t *dst, const rb_fdset_t *src) |
4349 | 0 | { |
4350 | 0 | size_t size = fdset_memsize(rb_fd_max(src)); |
4351 | 0 | dst->fdset = ruby_sized_xrealloc(dst->fdset, size, fdset_memsize(dst->maxfd)); |
4352 | 0 | dst->maxfd = src->maxfd; |
4353 | 0 | memcpy(dst->fdset, src->fdset, size); |
4354 | 0 | } |
4355 | | |
4356 | | int |
4357 | | rb_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, struct timeval *timeout) |
4358 | 0 | { |
4359 | 0 | fd_set *r = NULL, *w = NULL, *e = NULL; |
4360 | 0 | if (readfds) { |
4361 | 0 | rb_fd_resize(n - 1, readfds); |
4362 | 0 | r = rb_fd_ptr(readfds); |
4363 | 0 | } |
4364 | 0 | if (writefds) { |
4365 | 0 | rb_fd_resize(n - 1, writefds); |
4366 | 0 | w = rb_fd_ptr(writefds); |
4367 | 0 | } |
4368 | 0 | if (exceptfds) { |
4369 | 0 | rb_fd_resize(n - 1, exceptfds); |
4370 | 0 | e = rb_fd_ptr(exceptfds); |
4371 | 0 | } |
4372 | 0 | return select(n, r, w, e, timeout); |
4373 | 0 | } |
4374 | | |
4375 | 0 | #define rb_fd_no_init(fds) ((void)((fds)->fdset = 0), (void)((fds)->maxfd = 0)) |
4376 | | |
4377 | | #undef FD_ZERO |
4378 | | #undef FD_SET |
4379 | | #undef FD_CLR |
4380 | | #undef FD_ISSET |
4381 | | |
4382 | | #define FD_ZERO(f) rb_fd_zero(f) |
4383 | | #define FD_SET(i, f) rb_fd_set((i), (f)) |
4384 | | #define FD_CLR(i, f) rb_fd_clr((i), (f)) |
4385 | | #define FD_ISSET(i, f) rb_fd_isset((i), (f)) |
4386 | | |
4387 | | #elif defined(_WIN32) |
4388 | | |
4389 | | void |
4390 | | rb_fd_init(rb_fdset_t *set) |
4391 | | { |
4392 | | set->capa = FD_SETSIZE; |
4393 | | set->fdset = ALLOC(fd_set); |
4394 | | FD_ZERO(set->fdset); |
4395 | | } |
4396 | | |
4397 | | void |
4398 | | rb_fd_init_copy(rb_fdset_t *dst, rb_fdset_t *src) |
4399 | | { |
4400 | | rb_fd_init(dst); |
4401 | | rb_fd_dup(dst, src); |
4402 | | } |
4403 | | |
4404 | | static inline size_t |
4405 | | fdset_memsize(int capa) |
4406 | | { |
4407 | | if (capa == FD_SETSIZE) { |
4408 | | return sizeof(fd_set); |
4409 | | } |
4410 | | return sizeof(unsigned int) + (capa * sizeof(SOCKET)); |
4411 | | } |
4412 | | |
4413 | | void |
4414 | | rb_fd_term(rb_fdset_t *set) |
4415 | | { |
4416 | | ruby_sized_xfree(set->fdset, fdset_memsize(set->capa)); |
4417 | | set->fdset = NULL; |
4418 | | set->capa = 0; |
4419 | | } |
4420 | | |
4421 | | void |
4422 | | rb_fd_set(int fd, rb_fdset_t *set) |
4423 | | { |
4424 | | unsigned int i; |
4425 | | SOCKET s = rb_w32_get_osfhandle(fd); |
4426 | | |
4427 | | for (i = 0; i < set->fdset->fd_count; i++) { |
4428 | | if (set->fdset->fd_array[i] == s) { |
4429 | | return; |
4430 | | } |
4431 | | } |
4432 | | if (set->fdset->fd_count >= (unsigned)set->capa) { |
4433 | | set->capa = (set->fdset->fd_count / FD_SETSIZE + 1) * FD_SETSIZE; |
4434 | | set->fdset = |
4435 | | rb_xrealloc_mul_add( |
4436 | | set->fdset, set->capa, sizeof(SOCKET), sizeof(unsigned int)); |
4437 | | } |
4438 | | set->fdset->fd_array[set->fdset->fd_count++] = s; |
4439 | | } |
4440 | | |
4441 | | #undef FD_ZERO |
4442 | | #undef FD_SET |
4443 | | #undef FD_CLR |
4444 | | #undef FD_ISSET |
4445 | | |
4446 | | #define FD_ZERO(f) rb_fd_zero(f) |
4447 | | #define FD_SET(i, f) rb_fd_set((i), (f)) |
4448 | | #define FD_CLR(i, f) rb_fd_clr((i), (f)) |
4449 | | #define FD_ISSET(i, f) rb_fd_isset((i), (f)) |
4450 | | |
4451 | | #define rb_fd_no_init(fds) (void)((fds)->fdset = 0) |
4452 | | |
4453 | | #endif |
4454 | | |
4455 | | #ifndef rb_fd_no_init |
4456 | | #define rb_fd_no_init(fds) (void)(fds) |
4457 | | #endif |
4458 | | |
4459 | | static int |
4460 | | wait_retryable(volatile int *result, int errnum, rb_hrtime_t *rel, rb_hrtime_t end) |
4461 | 0 | { |
4462 | 0 | int r = *result; |
4463 | 0 | if (r < 0) { |
4464 | 0 | switch (errnum) { |
4465 | 0 | case EINTR: |
4466 | 0 | #ifdef ERESTART |
4467 | 0 | case ERESTART: |
4468 | 0 | #endif |
4469 | 0 | *result = 0; |
4470 | 0 | if (rel && hrtime_update_expire(rel, end)) { |
4471 | 0 | *rel = 0; |
4472 | 0 | } |
4473 | 0 | return TRUE; |
4474 | 0 | } |
4475 | 0 | return FALSE; |
4476 | 0 | } |
4477 | 0 | else if (r == 0) { |
4478 | | /* check for spurious wakeup */ |
4479 | 0 | if (rel) { |
4480 | 0 | return !hrtime_update_expire(rel, end); |
4481 | 0 | } |
4482 | 0 | return TRUE; |
4483 | 0 | } |
4484 | 0 | return FALSE; |
4485 | 0 | } |
4486 | | |
4487 | | struct select_set { |
4488 | | int max; |
4489 | | rb_thread_t *th; |
4490 | | rb_fdset_t *rset; |
4491 | | rb_fdset_t *wset; |
4492 | | rb_fdset_t *eset; |
4493 | | rb_fdset_t orig_rset; |
4494 | | rb_fdset_t orig_wset; |
4495 | | rb_fdset_t orig_eset; |
4496 | | struct timeval *timeout; |
4497 | | }; |
4498 | | |
4499 | | static VALUE |
4500 | | select_set_free(VALUE p) |
4501 | 0 | { |
4502 | 0 | struct select_set *set = (struct select_set *)p; |
4503 | |
|
4504 | 0 | rb_fd_term(&set->orig_rset); |
4505 | 0 | rb_fd_term(&set->orig_wset); |
4506 | 0 | rb_fd_term(&set->orig_eset); |
4507 | |
|
4508 | 0 | return Qfalse; |
4509 | 0 | } |
4510 | | |
4511 | | static VALUE |
4512 | | do_select(VALUE p) |
4513 | 0 | { |
4514 | 0 | struct select_set *set = (struct select_set *)p; |
4515 | 0 | volatile int result = 0; |
4516 | 0 | int lerrno; |
4517 | 0 | rb_hrtime_t *to, rel, end = 0; |
4518 | |
|
4519 | 0 | timeout_prepare(&to, &rel, &end, set->timeout); |
4520 | 0 | volatile rb_hrtime_t endtime = end; |
4521 | 0 | #define restore_fdset(dst, src) \ |
4522 | 0 | ((dst) ? rb_fd_dup(dst, src) : (void)0) |
4523 | 0 | #define do_select_update() \ |
4524 | 0 | (restore_fdset(set->rset, &set->orig_rset), \ |
4525 | 0 | restore_fdset(set->wset, &set->orig_wset), \ |
4526 | 0 | restore_fdset(set->eset, &set->orig_eset), \ |
4527 | 0 | TRUE) |
4528 | |
|
4529 | 0 | do { |
4530 | 0 | lerrno = 0; |
4531 | |
|
4532 | 0 | BLOCKING_REGION(set->th, { |
4533 | 0 | struct timeval tv; |
4534 | |
|
4535 | 0 | if (!RUBY_VM_INTERRUPTED(set->th->ec)) { |
4536 | 0 | result = native_fd_select(set->max, |
4537 | 0 | set->rset, set->wset, set->eset, |
4538 | 0 | rb_hrtime2timeval(&tv, to), set->th); |
4539 | 0 | if (result < 0) lerrno = errno; |
4540 | 0 | } |
4541 | 0 | }, ubf_select, set->th, TRUE); |
4542 | |
|
4543 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(set->th->ec); /* may raise */ |
4544 | 0 | } while (wait_retryable(&result, lerrno, to, endtime) && do_select_update()); |
4545 | |
|
4546 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(set->th->ec); |
4547 | |
|
4548 | 0 | if (result < 0) { |
4549 | 0 | errno = lerrno; |
4550 | 0 | } |
4551 | |
|
4552 | 0 | return (VALUE)result; |
4553 | 0 | } |
4554 | | |
4555 | | int |
4556 | | rb_thread_fd_select(int max, rb_fdset_t * read, rb_fdset_t * write, rb_fdset_t * except, |
4557 | | struct timeval *timeout) |
4558 | 0 | { |
4559 | 0 | struct select_set set; |
4560 | |
|
4561 | 0 | set.th = GET_THREAD(); |
4562 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(set.th->ec); |
4563 | 0 | set.max = max; |
4564 | 0 | set.rset = read; |
4565 | 0 | set.wset = write; |
4566 | 0 | set.eset = except; |
4567 | 0 | set.timeout = timeout; |
4568 | |
|
4569 | 0 | if (!set.rset && !set.wset && !set.eset) { |
4570 | 0 | if (!timeout) { |
4571 | 0 | rb_thread_sleep_forever(); |
4572 | 0 | return 0; |
4573 | 0 | } |
4574 | 0 | rb_thread_wait_for(*timeout); |
4575 | 0 | return 0; |
4576 | 0 | } |
4577 | | |
4578 | 0 | #define fd_init_copy(f) do { \ |
4579 | 0 | if (set.f) { \ |
4580 | 0 | rb_fd_resize(set.max - 1, set.f); \ |
4581 | 0 | if (&set.orig_##f != set.f) { /* sigwait_fd */ \ |
4582 | 0 | rb_fd_init_copy(&set.orig_##f, set.f); \ |
4583 | 0 | } \ |
4584 | 0 | } \ |
4585 | 0 | else { \ |
4586 | 0 | rb_fd_no_init(&set.orig_##f); \ |
4587 | 0 | } \ |
4588 | 0 | } while (0) |
4589 | 0 | fd_init_copy(rset); |
4590 | 0 | fd_init_copy(wset); |
4591 | 0 | fd_init_copy(eset); |
4592 | 0 | #undef fd_init_copy |
4593 | |
|
4594 | 0 | return (int)rb_ensure(do_select, (VALUE)&set, select_set_free, (VALUE)&set); |
4595 | 0 | } |
4596 | | |
4597 | | #ifdef USE_POLL |
4598 | | |
4599 | | /* The same with linux kernel. TODO: make platform independent definition. */ |
4600 | 0 | #define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR) |
4601 | 0 | #define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR) |
4602 | 0 | #define POLLEX_SET (POLLPRI) |
4603 | | |
4604 | | #ifndef POLLERR_SET /* defined for FreeBSD for now */ |
4605 | 0 | # define POLLERR_SET (0) |
4606 | | #endif |
4607 | | |
4608 | | static int |
4609 | | wait_for_single_fd_blocking_region(rb_thread_t *th, struct pollfd *fds, nfds_t nfds, |
4610 | | rb_hrtime_t *const to, volatile int *lerrno) |
4611 | 0 | { |
4612 | 0 | struct timespec ts; |
4613 | 0 | volatile int result = 0; |
4614 | |
|
4615 | 0 | *lerrno = 0; |
4616 | 0 | BLOCKING_REGION(th, { |
4617 | 0 | if (!RUBY_VM_INTERRUPTED(th->ec)) { |
4618 | 0 | result = ppoll(fds, nfds, rb_hrtime2timespec(&ts, to), 0); |
4619 | 0 | if (result < 0) *lerrno = errno; |
4620 | 0 | } |
4621 | 0 | }, ubf_select, th, TRUE); |
4622 | 0 | return result; |
4623 | 0 | } |
4624 | | |
4625 | | /* |
4626 | | * returns a mask of events |
4627 | | */ |
4628 | | static int |
4629 | | thread_io_wait(rb_thread_t *th, struct rb_io *io, int fd, int events, struct timeval *timeout) |
4630 | 0 | { |
4631 | 0 | struct pollfd fds[1] = {{ |
4632 | 0 | .fd = fd, |
4633 | 0 | .events = (short)events, |
4634 | 0 | .revents = 0, |
4635 | 0 | }}; |
4636 | 0 | volatile int result = 0; |
4637 | 0 | nfds_t nfds; |
4638 | 0 | struct rb_io_blocking_operation blocking_operation; |
4639 | 0 | enum ruby_tag_type state; |
4640 | 0 | volatile int lerrno; |
4641 | |
|
4642 | 0 | RUBY_ASSERT(th); |
4643 | 0 | rb_execution_context_t *ec = th->ec; |
4644 | |
|
4645 | 0 | if (io) { |
4646 | 0 | blocking_operation.ec = ec; |
4647 | 0 | rb_io_blocking_operation_enter(io, &blocking_operation); |
4648 | 0 | } |
4649 | |
|
4650 | 0 | if (timeout == NULL && thread_io_wait_events(th, fd, events, NULL)) { |
4651 | | // fd is readable |
4652 | 0 | state = 0; |
4653 | 0 | fds[0].revents = events; |
4654 | 0 | errno = 0; |
4655 | 0 | } |
4656 | 0 | else { |
4657 | 0 | EC_PUSH_TAG(ec); |
4658 | 0 | if ((state = EC_EXEC_TAG()) == TAG_NONE) { |
4659 | 0 | rb_hrtime_t *to, rel, end = 0; |
4660 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(ec); |
4661 | 0 | timeout_prepare(&to, &rel, &end, timeout); |
4662 | 0 | do { |
4663 | 0 | nfds = numberof(fds); |
4664 | 0 | result = wait_for_single_fd_blocking_region(th, fds, nfds, to, &lerrno); |
4665 | |
|
4666 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(ec); |
4667 | 0 | } while (wait_retryable(&result, lerrno, to, end)); |
4668 | |
|
4669 | 0 | RUBY_VM_CHECK_INTS_BLOCKING(ec); |
4670 | 0 | } |
4671 | |
|
4672 | 0 | EC_POP_TAG(); |
4673 | 0 | } |
4674 | |
|
4675 | 0 | if (io) { |
4676 | 0 | rb_io_blocking_operation_exit(io, &blocking_operation); |
4677 | 0 | } |
4678 | |
|
4679 | 0 | if (state) { |
4680 | 0 | EC_JUMP_TAG(ec, state); |
4681 | 0 | } |
4682 | | |
4683 | 0 | if (result < 0) { |
4684 | 0 | errno = lerrno; |
4685 | 0 | return -1; |
4686 | 0 | } |
4687 | | |
4688 | 0 | if (fds[0].revents & POLLNVAL) { |
4689 | 0 | errno = EBADF; |
4690 | 0 | return -1; |
4691 | 0 | } |
4692 | | |
4693 | | /* |
4694 | | * POLLIN, POLLOUT have a different meanings from select(2)'s read/write bit. |
4695 | | * Therefore we need to fix it up. |
4696 | | */ |
4697 | 0 | result = 0; |
4698 | 0 | if (fds[0].revents & POLLIN_SET) |
4699 | 0 | result |= RB_WAITFD_IN; |
4700 | 0 | if (fds[0].revents & POLLOUT_SET) |
4701 | 0 | result |= RB_WAITFD_OUT; |
4702 | 0 | if (fds[0].revents & POLLEX_SET) |
4703 | 0 | result |= RB_WAITFD_PRI; |
4704 | | |
4705 | | /* all requested events are ready if there is an error */ |
4706 | 0 | if (fds[0].revents & POLLERR_SET) |
4707 | 0 | result |= events; |
4708 | |
|
4709 | 0 | return result; |
4710 | 0 | } |
4711 | | #else /* ! USE_POLL - implement rb_io_poll_fd() using select() */ |
4712 | | struct select_args { |
4713 | | struct rb_io *io; |
4714 | | struct rb_io_blocking_operation *blocking_operation; |
4715 | | |
4716 | | union { |
4717 | | int fd; |
4718 | | int error; |
4719 | | } as; |
4720 | | rb_fdset_t *read; |
4721 | | rb_fdset_t *write; |
4722 | | rb_fdset_t *except; |
4723 | | struct timeval *tv; |
4724 | | }; |
4725 | | |
4726 | | static VALUE |
4727 | | select_single(VALUE ptr) |
4728 | | { |
4729 | | struct select_args *args = (struct select_args *)ptr; |
4730 | | int r; |
4731 | | |
4732 | | r = rb_thread_fd_select(args->as.fd + 1, |
4733 | | args->read, args->write, args->except, args->tv); |
4734 | | if (r == -1) |
4735 | | args->as.error = errno; |
4736 | | if (r > 0) { |
4737 | | r = 0; |
4738 | | if (args->read && rb_fd_isset(args->as.fd, args->read)) |
4739 | | r |= RB_WAITFD_IN; |
4740 | | if (args->write && rb_fd_isset(args->as.fd, args->write)) |
4741 | | r |= RB_WAITFD_OUT; |
4742 | | if (args->except && rb_fd_isset(args->as.fd, args->except)) |
4743 | | r |= RB_WAITFD_PRI; |
4744 | | } |
4745 | | return (VALUE)r; |
4746 | | } |
4747 | | |
4748 | | static VALUE |
4749 | | select_single_cleanup(VALUE ptr) |
4750 | | { |
4751 | | struct select_args *args = (struct select_args *)ptr; |
4752 | | |
4753 | | if (args->blocking_operation) { |
4754 | | rb_io_blocking_operation_exit(args->io, args->blocking_operation); |
4755 | | } |
4756 | | |
4757 | | if (args->read) rb_fd_term(args->read); |
4758 | | if (args->write) rb_fd_term(args->write); |
4759 | | if (args->except) rb_fd_term(args->except); |
4760 | | |
4761 | | return (VALUE)-1; |
4762 | | } |
4763 | | |
4764 | | static rb_fdset_t * |
4765 | | init_set_fd(int fd, rb_fdset_t *fds) |
4766 | | { |
4767 | | if (fd < 0) { |
4768 | | return 0; |
4769 | | } |
4770 | | rb_fd_init(fds); |
4771 | | rb_fd_set(fd, fds); |
4772 | | |
4773 | | return fds; |
4774 | | } |
4775 | | |
4776 | | static int |
4777 | | thread_io_wait(rb_thread_t *th, struct rb_io *io, int fd, int events, struct timeval *timeout) |
4778 | | { |
4779 | | rb_fdset_t rfds, wfds, efds; |
4780 | | struct select_args args; |
4781 | | VALUE ptr = (VALUE)&args; |
4782 | | |
4783 | | struct rb_io_blocking_operation blocking_operation; |
4784 | | if (io) { |
4785 | | args.io = io; |
4786 | | blocking_operation.ec = th->ec; |
4787 | | rb_io_blocking_operation_enter(io, &blocking_operation); |
4788 | | args.blocking_operation = &blocking_operation; |
4789 | | } |
4790 | | else { |
4791 | | args.io = NULL; |
4792 | | blocking_operation.ec = NULL; |
4793 | | args.blocking_operation = NULL; |
4794 | | } |
4795 | | |
4796 | | args.as.fd = fd; |
4797 | | args.read = (events & RB_WAITFD_IN) ? init_set_fd(fd, &rfds) : NULL; |
4798 | | args.write = (events & RB_WAITFD_OUT) ? init_set_fd(fd, &wfds) : NULL; |
4799 | | args.except = (events & RB_WAITFD_PRI) ? init_set_fd(fd, &efds) : NULL; |
4800 | | args.tv = timeout; |
4801 | | |
4802 | | int result = (int)rb_ensure(select_single, ptr, select_single_cleanup, ptr); |
4803 | | if (result == -1) |
4804 | | errno = args.as.error; |
4805 | | |
4806 | | return result; |
4807 | | } |
4808 | | #endif /* ! USE_POLL */ |
4809 | | |
4810 | | int |
4811 | | rb_thread_wait_for_single_fd(rb_thread_t *th, int fd, int events, struct timeval *timeout) |
4812 | 0 | { |
4813 | 0 | return thread_io_wait(th, NULL, fd, events, timeout); |
4814 | 0 | } |
4815 | | |
4816 | | int |
4817 | | rb_thread_io_wait(rb_thread_t *th, struct rb_io *io, int events, struct timeval * timeout) |
4818 | 0 | { |
4819 | 0 | return thread_io_wait(th, io, io->fd, events, timeout); |
4820 | 0 | } |
4821 | | |
4822 | | /* |
4823 | | * for GC |
4824 | | */ |
4825 | | |
4826 | | #ifdef USE_CONSERVATIVE_STACK_END |
4827 | | void |
4828 | | rb_gc_set_stack_end(VALUE **stack_end_p) |
4829 | | { |
4830 | | VALUE stack_end; |
4831 | | COMPILER_WARNING_PUSH |
4832 | | #if RBIMPL_COMPILER_IS(GCC) |
4833 | | COMPILER_WARNING_IGNORED(-Wdangling-pointer); |
4834 | | #endif |
4835 | | *stack_end_p = &stack_end; |
4836 | | COMPILER_WARNING_POP |
4837 | | } |
4838 | | #endif |
4839 | | |
4840 | | /* |
4841 | | * |
4842 | | */ |
4843 | | |
4844 | | void |
4845 | | rb_threadptr_check_signal(rb_thread_t *mth) |
4846 | 0 | { |
4847 | | /* mth must be main_thread */ |
4848 | 0 | if (rb_signal_buff_size() > 0) { |
4849 | | /* wakeup main thread */ |
4850 | 0 | threadptr_trap_interrupt(mth); |
4851 | 0 | } |
4852 | 0 | } |
4853 | | |
4854 | | static void |
4855 | | async_bug_fd(const char *mesg, int errno_arg, int fd) |
4856 | 0 | { |
4857 | 0 | char buff[64]; |
4858 | 0 | size_t n = strlcpy(buff, mesg, sizeof(buff)); |
4859 | 0 | if (n < sizeof(buff)-3) { |
4860 | 0 | ruby_snprintf(buff+n, sizeof(buff)-n, "(%d)", fd); |
4861 | 0 | } |
4862 | 0 | rb_async_bug_errno(buff, errno_arg); |
4863 | 0 | } |
4864 | | |
4865 | | /* VM-dependent API is not available for this function */ |
4866 | | static int |
4867 | | consume_communication_pipe(int fd) |
4868 | 0 | { |
4869 | 0 | #if USE_EVENTFD |
4870 | 0 | uint64_t buff[1]; |
4871 | | #else |
4872 | | /* buffer can be shared because no one refers to them. */ |
4873 | | static char buff[1024]; |
4874 | | #endif |
4875 | 0 | ssize_t result; |
4876 | 0 | int ret = FALSE; /* for rb_sigwait_sleep */ |
4877 | |
|
4878 | 0 | while (1) { |
4879 | 0 | result = read(fd, buff, sizeof(buff)); |
4880 | 0 | #if USE_EVENTFD |
4881 | 0 | RUBY_DEBUG_LOG("resultf:%d buff:%lu", (int)result, (unsigned long)buff[0]); |
4882 | | #else |
4883 | | RUBY_DEBUG_LOG("result:%d", (int)result); |
4884 | | #endif |
4885 | 0 | if (result > 0) { |
4886 | 0 | ret = TRUE; |
4887 | 0 | if (USE_EVENTFD || result < (ssize_t)sizeof(buff)) { |
4888 | 0 | return ret; |
4889 | 0 | } |
4890 | 0 | } |
4891 | 0 | else if (result == 0) { |
4892 | 0 | return ret; |
4893 | 0 | } |
4894 | 0 | else if (result < 0) { |
4895 | 0 | int e = errno; |
4896 | 0 | switch (e) { |
4897 | 0 | case EINTR: |
4898 | 0 | continue; /* retry */ |
4899 | 0 | case EAGAIN: |
4900 | | #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN |
4901 | | case EWOULDBLOCK: |
4902 | | #endif |
4903 | 0 | return ret; |
4904 | 0 | default: |
4905 | 0 | async_bug_fd("consume_communication_pipe: read", e, fd); |
4906 | 0 | } |
4907 | 0 | } |
4908 | 0 | } |
4909 | 0 | } |
4910 | | |
4911 | | void |
4912 | | rb_thread_stop_timer_thread(void) |
4913 | 0 | { |
4914 | 0 | if (TIMER_THREAD_CREATED_P() && native_stop_timer_thread()) { |
4915 | 0 | native_reset_timer_thread(); |
4916 | 0 | } |
4917 | 0 | } |
4918 | | |
4919 | | void |
4920 | | rb_thread_reset_timer_thread(void) |
4921 | 0 | { |
4922 | 0 | native_reset_timer_thread(); |
4923 | 0 | } |
4924 | | |
4925 | | void |
4926 | | rb_thread_start_timer_thread(void) |
4927 | 0 | { |
4928 | 0 | system_working = 1; |
4929 | 0 | rb_thread_create_timer_thread(); |
4930 | 0 | } |
4931 | | |
4932 | | static int |
4933 | | clear_coverage_i(st_data_t key, st_data_t val, st_data_t dummy) |
4934 | 0 | { |
4935 | 0 | int i; |
4936 | 0 | VALUE coverage = (VALUE)val; |
4937 | 0 | VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES); |
4938 | 0 | VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES); |
4939 | |
|
4940 | 0 | if (lines) { |
4941 | 0 | if (GET_VM()->coverage_mode & COVERAGE_TARGET_ONESHOT_LINES) { |
4942 | 0 | rb_ary_clear(lines); |
4943 | 0 | } |
4944 | 0 | else { |
4945 | 0 | int i; |
4946 | 0 | for (i = 0; i < RARRAY_LEN(lines); i++) { |
4947 | 0 | if (RARRAY_AREF(lines, i) != Qnil) |
4948 | 0 | RARRAY_ASET(lines, i, INT2FIX(0)); |
4949 | 0 | } |
4950 | 0 | } |
4951 | 0 | } |
4952 | 0 | if (branches) { |
4953 | 0 | VALUE counters = RARRAY_AREF(branches, 1); |
4954 | 0 | for (i = 0; i < RARRAY_LEN(counters); i++) { |
4955 | 0 | RARRAY_ASET(counters, i, INT2FIX(0)); |
4956 | 0 | } |
4957 | 0 | } |
4958 | |
|
4959 | 0 | return ST_CONTINUE; |
4960 | 0 | } |
4961 | | |
4962 | | void |
4963 | | rb_clear_coverages(void) |
4964 | 0 | { |
4965 | 0 | VALUE coverages = rb_get_coverages(); |
4966 | 0 | if (RTEST(coverages)) { |
4967 | 0 | rb_hash_foreach(coverages, clear_coverage_i, 0); |
4968 | 0 | } |
4969 | 0 | } |
4970 | | |
4971 | | #if defined(HAVE_WORKING_FORK) |
4972 | | |
4973 | | static void |
4974 | | rb_thread_atfork_internal(rb_thread_t *th, void (*atfork)(rb_thread_t *, const rb_thread_t *)) |
4975 | 0 | { |
4976 | 0 | rb_thread_t *i = 0; |
4977 | 0 | rb_vm_t *vm = th->vm; |
4978 | 0 | rb_ractor_t *r = th->ractor; |
4979 | 0 | vm->ractor.main_ractor = r; |
4980 | 0 | vm->ractor.main_thread = th; |
4981 | 0 | r->threads.main = th; |
4982 | 0 | r->status_ = ractor_created; |
4983 | |
|
4984 | 0 | thread_sched_atfork(TH_SCHED(th)); |
4985 | 0 | ubf_list_atfork(); |
4986 | 0 | rb_signal_atfork(); |
4987 | | |
4988 | | // OK. Only this thread accesses: |
4989 | 0 | ccan_list_for_each(&vm->ractor.set, r, vmlr_node) { |
4990 | 0 | if (r != vm->ractor.main_ractor) { |
4991 | 0 | rb_ractor_terminate_atfork(vm, r); |
4992 | 0 | } |
4993 | 0 | ccan_list_for_each(&r->threads.set, i, lt_node) { |
4994 | 0 | atfork(i, th); |
4995 | 0 | } |
4996 | 0 | } |
4997 | 0 | rb_vm_living_threads_init(vm); |
4998 | |
|
4999 | 0 | rb_ractor_atfork(vm, th); |
5000 | 0 | rb_vm_postponed_job_atfork(); |
5001 | | |
5002 | | /* may be held by any thread in parent */ |
5003 | 0 | rb_native_mutex_initialize(&th->interrupt_lock); |
5004 | 0 | ccan_list_head_init(&th->interrupt_exec_tasks); |
5005 | |
|
5006 | 0 | vm->fork_gen++; |
5007 | 0 | rb_ractor_sleeper_threads_clear(th->ractor); |
5008 | 0 | rb_clear_coverages(); |
5009 | | |
5010 | | // restart timer thread (timer threads access to `vm->waitpid_lock` and so on. |
5011 | 0 | rb_thread_reset_timer_thread(); |
5012 | 0 | rb_thread_start_timer_thread(); |
5013 | |
|
5014 | 0 | VM_ASSERT(vm->ractor.blocking_cnt == 0); |
5015 | 0 | VM_ASSERT(vm->ractor.cnt == 1); |
5016 | 0 | } |
5017 | | |
5018 | | static void |
5019 | | terminate_atfork_i(rb_thread_t *th, const rb_thread_t *current_th) |
5020 | 0 | { |
5021 | 0 | if (th != current_th) { |
5022 | | // Clear the scheduler as it is no longer operational: |
5023 | 0 | th->scheduler = Qnil; |
5024 | |
|
5025 | 0 | rb_native_mutex_initialize(&th->interrupt_lock); |
5026 | 0 | rb_mutex_abandon_keeping_mutexes(th); |
5027 | 0 | rb_mutex_abandon_locking_mutex(th); |
5028 | 0 | thread_cleanup_func(th, TRUE); |
5029 | 0 | } |
5030 | 0 | } |
5031 | | |
5032 | | void rb_fiber_atfork(rb_thread_t *); |
5033 | | void |
5034 | | rb_thread_atfork(void) |
5035 | 0 | { |
5036 | 0 | rb_thread_t *th = GET_THREAD(); |
5037 | 0 | rb_threadptr_pending_interrupt_clear(th); |
5038 | 0 | rb_thread_atfork_internal(th, terminate_atfork_i); |
5039 | 0 | th->join_list = NULL; |
5040 | 0 | th->scheduler = Qnil; |
5041 | 0 | rb_fiber_atfork(th); |
5042 | | |
5043 | | /* We don't want reproduce CVE-2003-0900. */ |
5044 | 0 | rb_reset_random_seed(); |
5045 | 0 | } |
5046 | | |
5047 | | static void |
5048 | | terminate_atfork_before_exec_i(rb_thread_t *th, const rb_thread_t *current_th) |
5049 | 0 | { |
5050 | 0 | if (th != current_th) { |
5051 | 0 | thread_cleanup_func_before_exec(th); |
5052 | 0 | } |
5053 | 0 | } |
5054 | | |
5055 | | void |
5056 | | rb_thread_atfork_before_exec(void) |
5057 | 0 | { |
5058 | 0 | rb_thread_t *th = GET_THREAD(); |
5059 | 0 | rb_thread_atfork_internal(th, terminate_atfork_before_exec_i); |
5060 | 0 | } |
5061 | | #else |
5062 | | void |
5063 | | rb_thread_atfork(void) |
5064 | | { |
5065 | | } |
5066 | | |
5067 | | void |
5068 | | rb_thread_atfork_before_exec(void) |
5069 | | { |
5070 | | } |
5071 | | #endif |
5072 | | |
5073 | | struct thgroup { |
5074 | | int enclosed; |
5075 | | }; |
5076 | | |
5077 | | static const rb_data_type_t thgroup_data_type = { |
5078 | | "thgroup", |
5079 | | { |
5080 | | 0, |
5081 | | RUBY_TYPED_DEFAULT_FREE, |
5082 | | NULL, // No external memory to report |
5083 | | }, |
5084 | | 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE |
5085 | | }; |
5086 | | |
5087 | | /* |
5088 | | * Document-class: ThreadGroup |
5089 | | * |
5090 | | * ThreadGroup provides a means of keeping track of a number of threads as a |
5091 | | * group. |
5092 | | * |
5093 | | * A given Thread object can only belong to one ThreadGroup at a time; adding |
5094 | | * a thread to a new group will remove it from any previous group. |
5095 | | * |
5096 | | * Newly created threads belong to the same group as the thread from which they |
5097 | | * were created. |
5098 | | */ |
5099 | | |
5100 | | /* |
5101 | | * Document-const: Default |
5102 | | * |
5103 | | * The default ThreadGroup created when Ruby starts; all Threads belong to it |
5104 | | * by default. |
5105 | | */ |
5106 | | static VALUE |
5107 | | thgroup_s_alloc(VALUE klass) |
5108 | 9 | { |
5109 | 9 | VALUE group; |
5110 | 9 | struct thgroup *data; |
5111 | | |
5112 | 9 | group = TypedData_Make_Struct(klass, struct thgroup, &thgroup_data_type, data); |
5113 | 9 | data->enclosed = 0; |
5114 | | |
5115 | 9 | return group; |
5116 | 9 | } |
5117 | | |
5118 | | /* |
5119 | | * call-seq: |
5120 | | * thgrp.list -> array |
5121 | | * |
5122 | | * Returns an array of all existing Thread objects that belong to this group. |
5123 | | * |
5124 | | * ThreadGroup::Default.list #=> [#<Thread:0x401bdf4c run>] |
5125 | | */ |
5126 | | |
5127 | | static VALUE |
5128 | | thgroup_list(VALUE group) |
5129 | 0 | { |
5130 | 0 | VALUE ary = rb_ary_new(); |
5131 | 0 | rb_thread_t *th = 0; |
5132 | 0 | rb_ractor_t *r = GET_RACTOR(); |
5133 | |
|
5134 | 0 | ccan_list_for_each(&r->threads.set, th, lt_node) { |
5135 | 0 | if (th->thgroup == group) { |
5136 | 0 | rb_ary_push(ary, th->self); |
5137 | 0 | } |
5138 | 0 | } |
5139 | 0 | return ary; |
5140 | 0 | } |
5141 | | |
5142 | | |
5143 | | /* |
5144 | | * call-seq: |
5145 | | * thgrp.enclose -> thgrp |
5146 | | * |
5147 | | * Prevents threads from being added to or removed from the receiving |
5148 | | * ThreadGroup. |
5149 | | * |
5150 | | * New threads can still be started in an enclosed ThreadGroup. |
5151 | | * |
5152 | | * ThreadGroup::Default.enclose #=> #<ThreadGroup:0x4029d914> |
5153 | | * thr = Thread.new { Thread.stop } #=> #<Thread:0x402a7210 sleep> |
5154 | | * tg = ThreadGroup.new #=> #<ThreadGroup:0x402752d4> |
5155 | | * tg.add thr |
5156 | | * #=> ThreadError: can't move from the enclosed thread group |
5157 | | */ |
5158 | | |
5159 | | static VALUE |
5160 | | thgroup_enclose(VALUE group) |
5161 | 0 | { |
5162 | 0 | struct thgroup *data; |
5163 | |
|
5164 | 0 | TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data); |
5165 | 0 | data->enclosed = 1; |
5166 | |
|
5167 | 0 | return group; |
5168 | 0 | } |
5169 | | |
5170 | | |
5171 | | /* |
5172 | | * call-seq: |
5173 | | * thgrp.enclosed? -> true or false |
5174 | | * |
5175 | | * Returns +true+ if the +thgrp+ is enclosed. See also ThreadGroup#enclose. |
5176 | | */ |
5177 | | |
5178 | | static VALUE |
5179 | | thgroup_enclosed_p(VALUE group) |
5180 | 0 | { |
5181 | 0 | struct thgroup *data; |
5182 | |
|
5183 | 0 | TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data); |
5184 | 0 | return RBOOL(data->enclosed); |
5185 | 0 | } |
5186 | | |
5187 | | |
5188 | | /* |
5189 | | * call-seq: |
5190 | | * thgrp.add(thread) -> thgrp |
5191 | | * |
5192 | | * Adds the given +thread+ to this group, removing it from any other |
5193 | | * group to which it may have previously been a member. |
5194 | | * |
5195 | | * puts "Initial group is #{ThreadGroup::Default.list}" |
5196 | | * tg = ThreadGroup.new |
5197 | | * t1 = Thread.new { sleep } |
5198 | | * t2 = Thread.new { sleep } |
5199 | | * puts "t1 is #{t1}" |
5200 | | * puts "t2 is #{t2}" |
5201 | | * tg.add(t1) |
5202 | | * puts "Initial group now #{ThreadGroup::Default.list}" |
5203 | | * puts "tg group now #{tg.list}" |
5204 | | * |
5205 | | * This will produce: |
5206 | | * |
5207 | | * Initial group is #<Thread:0x401bdf4c> |
5208 | | * t1 is #<Thread:0x401b3c90> |
5209 | | * t2 is #<Thread:0x401b3c18> |
5210 | | * Initial group now #<Thread:0x401b3c18>#<Thread:0x401bdf4c> |
5211 | | * tg group now #<Thread:0x401b3c90> |
5212 | | */ |
5213 | | |
5214 | | static VALUE |
5215 | | thgroup_add(VALUE group, VALUE thread) |
5216 | 0 | { |
5217 | 0 | rb_thread_t *target_th = rb_thread_ptr(thread); |
5218 | 0 | struct thgroup *data; |
5219 | |
|
5220 | 0 | if (OBJ_FROZEN(group)) { |
5221 | 0 | rb_raise(rb_eThreadError, "can't move to the frozen thread group"); |
5222 | 0 | } |
5223 | 0 | TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data); |
5224 | 0 | if (data->enclosed) { |
5225 | 0 | rb_raise(rb_eThreadError, "can't move to the enclosed thread group"); |
5226 | 0 | } |
5227 | | |
5228 | 0 | if (OBJ_FROZEN(target_th->thgroup)) { |
5229 | 0 | rb_raise(rb_eThreadError, "can't move from the frozen thread group"); |
5230 | 0 | } |
5231 | 0 | TypedData_Get_Struct(target_th->thgroup, struct thgroup, &thgroup_data_type, data); |
5232 | 0 | if (data->enclosed) { |
5233 | 0 | rb_raise(rb_eThreadError, |
5234 | 0 | "can't move from the enclosed thread group"); |
5235 | 0 | } |
5236 | | |
5237 | 0 | target_th->thgroup = group; |
5238 | 0 | return group; |
5239 | 0 | } |
5240 | | |
5241 | | /* |
5242 | | * Document-class: ThreadShield |
5243 | | */ |
5244 | | static void |
5245 | | thread_shield_mark(void *ptr) |
5246 | 0 | { |
5247 | 0 | rb_gc_mark((VALUE)ptr); |
5248 | 0 | } |
5249 | | |
5250 | | static const rb_data_type_t thread_shield_data_type = { |
5251 | | "thread_shield", |
5252 | | {thread_shield_mark, 0, 0,}, |
5253 | | 0, 0, RUBY_TYPED_FREE_IMMEDIATELY |
5254 | | }; |
5255 | | |
5256 | | static VALUE |
5257 | | thread_shield_alloc(VALUE klass) |
5258 | 0 | { |
5259 | 0 | return TypedData_Wrap_Struct(klass, &thread_shield_data_type, (void *)mutex_alloc(0)); |
5260 | 0 | } |
5261 | | |
5262 | 0 | #define GetThreadShieldPtr(obj) ((VALUE)rb_check_typeddata((obj), &thread_shield_data_type)) |
5263 | 0 | #define THREAD_SHIELD_WAITING_MASK (((FL_USER19-1)&~(FL_USER0-1))|FL_USER19) |
5264 | 0 | #define THREAD_SHIELD_WAITING_SHIFT (FL_USHIFT) |
5265 | 0 | #define THREAD_SHIELD_WAITING_MAX (THREAD_SHIELD_WAITING_MASK>>THREAD_SHIELD_WAITING_SHIFT) |
5266 | | STATIC_ASSERT(THREAD_SHIELD_WAITING_MAX, THREAD_SHIELD_WAITING_MAX <= UINT_MAX); |
5267 | | static inline unsigned int |
5268 | | rb_thread_shield_waiting(VALUE b) |
5269 | 0 | { |
5270 | 0 | return ((RBASIC(b)->flags&THREAD_SHIELD_WAITING_MASK)>>THREAD_SHIELD_WAITING_SHIFT); |
5271 | 0 | } |
5272 | | |
5273 | | static inline void |
5274 | | rb_thread_shield_waiting_inc(VALUE b) |
5275 | 0 | { |
5276 | 0 | unsigned int w = rb_thread_shield_waiting(b); |
5277 | 0 | w++; |
5278 | 0 | if (w > THREAD_SHIELD_WAITING_MAX) |
5279 | 0 | rb_raise(rb_eRuntimeError, "waiting count overflow"); |
5280 | 0 | RBASIC(b)->flags &= ~THREAD_SHIELD_WAITING_MASK; |
5281 | 0 | RBASIC(b)->flags |= ((VALUE)w << THREAD_SHIELD_WAITING_SHIFT); |
5282 | 0 | } |
5283 | | |
5284 | | static inline void |
5285 | | rb_thread_shield_waiting_dec(VALUE b) |
5286 | 0 | { |
5287 | 0 | unsigned int w = rb_thread_shield_waiting(b); |
5288 | 0 | if (!w) rb_raise(rb_eRuntimeError, "waiting count underflow"); |
5289 | 0 | w--; |
5290 | 0 | RBASIC(b)->flags &= ~THREAD_SHIELD_WAITING_MASK; |
5291 | 0 | RBASIC(b)->flags |= ((VALUE)w << THREAD_SHIELD_WAITING_SHIFT); |
5292 | 0 | } |
5293 | | |
5294 | | VALUE |
5295 | | rb_thread_shield_new(void) |
5296 | 0 | { |
5297 | 0 | VALUE thread_shield = thread_shield_alloc(rb_cThreadShield); |
5298 | 0 | rb_mutex_lock((VALUE)DATA_PTR(thread_shield)); |
5299 | 0 | return thread_shield; |
5300 | 0 | } |
5301 | | |
5302 | | bool |
5303 | | rb_thread_shield_owned(VALUE self) |
5304 | 0 | { |
5305 | 0 | VALUE mutex = GetThreadShieldPtr(self); |
5306 | 0 | if (!mutex) return false; |
5307 | | |
5308 | 0 | rb_mutex_t *m = mutex_ptr(mutex); |
5309 | |
|
5310 | 0 | return m->ec_serial == rb_ec_serial(GET_EC()); |
5311 | 0 | } |
5312 | | |
5313 | | /* |
5314 | | * Wait a thread shield. |
5315 | | * |
5316 | | * Returns |
5317 | | * true: acquired the thread shield |
5318 | | * false: the thread shield was destroyed and no other threads waiting |
5319 | | * nil: the thread shield was destroyed but still in use |
5320 | | */ |
5321 | | VALUE |
5322 | | rb_thread_shield_wait(VALUE self) |
5323 | 0 | { |
5324 | 0 | VALUE mutex = GetThreadShieldPtr(self); |
5325 | 0 | rb_mutex_t *m; |
5326 | |
|
5327 | 0 | if (!mutex) return Qfalse; |
5328 | 0 | m = mutex_ptr(mutex); |
5329 | 0 | if (m->ec_serial == rb_ec_serial(GET_EC())) return Qnil; |
5330 | 0 | rb_thread_shield_waiting_inc(self); |
5331 | 0 | rb_mutex_lock(mutex); |
5332 | 0 | rb_thread_shield_waiting_dec(self); |
5333 | 0 | if (DATA_PTR(self)) return Qtrue; |
5334 | 0 | rb_mutex_unlock(mutex); |
5335 | 0 | return rb_thread_shield_waiting(self) > 0 ? Qnil : Qfalse; |
5336 | 0 | } |
5337 | | |
5338 | | static VALUE |
5339 | | thread_shield_get_mutex(VALUE self) |
5340 | 0 | { |
5341 | 0 | VALUE mutex = GetThreadShieldPtr(self); |
5342 | 0 | if (!mutex) |
5343 | 0 | rb_raise(rb_eThreadError, "destroyed thread shield - %p", (void *)self); |
5344 | 0 | return mutex; |
5345 | 0 | } |
5346 | | |
5347 | | /* |
5348 | | * Release a thread shield, and return true if it has waiting threads. |
5349 | | */ |
5350 | | VALUE |
5351 | | rb_thread_shield_release(VALUE self) |
5352 | 0 | { |
5353 | 0 | VALUE mutex = thread_shield_get_mutex(self); |
5354 | 0 | rb_mutex_unlock(mutex); |
5355 | 0 | return RBOOL(rb_thread_shield_waiting(self) > 0); |
5356 | 0 | } |
5357 | | |
5358 | | /* |
5359 | | * Release and destroy a thread shield, and return true if it has waiting threads. |
5360 | | */ |
5361 | | VALUE |
5362 | | rb_thread_shield_destroy(VALUE self) |
5363 | 0 | { |
5364 | 0 | VALUE mutex = thread_shield_get_mutex(self); |
5365 | 0 | DATA_PTR(self) = 0; |
5366 | 0 | rb_mutex_unlock(mutex); |
5367 | 0 | return RBOOL(rb_thread_shield_waiting(self) > 0); |
5368 | 0 | } |
5369 | | |
5370 | | static VALUE |
5371 | | threadptr_recursive_hash(rb_thread_t *th) |
5372 | 309 | { |
5373 | 309 | return th->ec->local_storage_recursive_hash; |
5374 | 309 | } |
5375 | | |
5376 | | static void |
5377 | | threadptr_recursive_hash_set(rb_thread_t *th, VALUE hash) |
5378 | 1 | { |
5379 | 1 | th->ec->local_storage_recursive_hash = hash; |
5380 | 1 | } |
5381 | | |
5382 | | ID rb_frame_last_func(void); |
5383 | | |
5384 | | /* |
5385 | | * Returns the current "recursive list" used to detect recursion. |
5386 | | * This list is a hash table, unique for the current thread and for |
5387 | | * the current __callee__. |
5388 | | */ |
5389 | | |
5390 | | static VALUE |
5391 | | recursive_list_access(VALUE sym) |
5392 | 309 | { |
5393 | 309 | rb_thread_t *th = GET_THREAD(); |
5394 | 309 | VALUE hash = threadptr_recursive_hash(th); |
5395 | 309 | VALUE list; |
5396 | 309 | if (NIL_P(hash) || !RB_TYPE_P(hash, T_HASH)) { |
5397 | 1 | hash = rb_ident_hash_new(); |
5398 | 1 | threadptr_recursive_hash_set(th, hash); |
5399 | 1 | list = Qnil; |
5400 | 1 | } |
5401 | 308 | else { |
5402 | 308 | list = rb_hash_aref(hash, sym); |
5403 | 308 | } |
5404 | 309 | if (NIL_P(list) || !RB_TYPE_P(list, T_HASH)) { |
5405 | 2 | list = rb_ident_hash_new(); |
5406 | 2 | rb_hash_aset(hash, sym, list); |
5407 | 2 | } |
5408 | 309 | return list; |
5409 | 309 | } |
5410 | | |
5411 | | /* |
5412 | | * Returns true if and only if obj (or the pair <obj, paired_obj>) is already |
5413 | | * in the recursion list. |
5414 | | * Assumes the recursion list is valid. |
5415 | | */ |
5416 | | |
5417 | | static bool |
5418 | | recursive_check(VALUE list, VALUE obj, VALUE paired_obj_id) |
5419 | 309 | { |
5420 | 309 | #if SIZEOF_LONG == SIZEOF_VOIDP |
5421 | 309 | #define OBJ_ID_EQL(obj_id, other) ((obj_id) == (other)) |
5422 | | #elif SIZEOF_LONG_LONG == SIZEOF_VOIDP |
5423 | | #define OBJ_ID_EQL(obj_id, other) (RB_BIGNUM_TYPE_P((obj_id)) ? \ |
5424 | | rb_big_eql((obj_id), (other)) : ((obj_id) == (other))) |
5425 | | #endif |
5426 | | |
5427 | 309 | VALUE pair_list = rb_hash_lookup2(list, obj, Qundef); |
5428 | 309 | if (UNDEF_P(pair_list)) |
5429 | 309 | return false; |
5430 | 0 | if (paired_obj_id) { |
5431 | 0 | if (!RB_TYPE_P(pair_list, T_HASH)) { |
5432 | 0 | if (!OBJ_ID_EQL(paired_obj_id, pair_list)) |
5433 | 0 | return false; |
5434 | 0 | } |
5435 | 0 | else { |
5436 | 0 | if (NIL_P(rb_hash_lookup(pair_list, paired_obj_id))) |
5437 | 0 | return false; |
5438 | 0 | } |
5439 | 0 | } |
5440 | 0 | return true; |
5441 | 0 | } |
5442 | | |
5443 | | /* |
5444 | | * Pushes obj (or the pair <obj, paired_obj>) in the recursion list. |
5445 | | * For a single obj, it sets list[obj] to Qtrue. |
5446 | | * For a pair, it sets list[obj] to paired_obj_id if possible, |
5447 | | * otherwise list[obj] becomes a hash like: |
5448 | | * {paired_obj_id_1 => true, paired_obj_id_2 => true, ... } |
5449 | | * Assumes the recursion list is valid. |
5450 | | */ |
5451 | | |
5452 | | static void |
5453 | | recursive_push(VALUE list, VALUE obj, VALUE paired_obj) |
5454 | 309 | { |
5455 | 309 | VALUE pair_list; |
5456 | | |
5457 | 309 | if (!paired_obj) { |
5458 | 108 | rb_hash_aset(list, obj, Qtrue); |
5459 | 108 | } |
5460 | 201 | else if (UNDEF_P(pair_list = rb_hash_lookup2(list, obj, Qundef))) { |
5461 | 201 | rb_hash_aset(list, obj, paired_obj); |
5462 | 201 | } |
5463 | 0 | else { |
5464 | 0 | if (!RB_TYPE_P(pair_list, T_HASH)){ |
5465 | 0 | VALUE other_paired_obj = pair_list; |
5466 | 0 | pair_list = rb_hash_new(); |
5467 | 0 | rb_hash_aset(pair_list, other_paired_obj, Qtrue); |
5468 | 0 | rb_hash_aset(list, obj, pair_list); |
5469 | 0 | } |
5470 | 0 | rb_hash_aset(pair_list, paired_obj, Qtrue); |
5471 | 0 | } |
5472 | 309 | } |
5473 | | |
5474 | | /* |
5475 | | * Pops obj (or the pair <obj, paired_obj>) from the recursion list. |
5476 | | * For a pair, if list[obj] is a hash, then paired_obj_id is |
5477 | | * removed from the hash and no attempt is made to simplify |
5478 | | * list[obj] from {only_one_paired_id => true} to only_one_paired_id |
5479 | | * Assumes the recursion list is valid. |
5480 | | */ |
5481 | | |
5482 | | static int |
5483 | | recursive_pop(VALUE list, VALUE obj, VALUE paired_obj) |
5484 | 309 | { |
5485 | 309 | if (paired_obj) { |
5486 | 201 | VALUE pair_list = rb_hash_lookup2(list, obj, Qundef); |
5487 | 201 | if (UNDEF_P(pair_list)) { |
5488 | 0 | return 0; |
5489 | 0 | } |
5490 | 201 | if (RB_TYPE_P(pair_list, T_HASH)) { |
5491 | 0 | rb_hash_delete_entry(pair_list, paired_obj); |
5492 | 0 | if (!RHASH_EMPTY_P(pair_list)) { |
5493 | 0 | return 1; /* keep hash until is empty */ |
5494 | 0 | } |
5495 | 0 | } |
5496 | 201 | } |
5497 | 309 | rb_hash_delete_entry(list, obj); |
5498 | 309 | return 1; |
5499 | 309 | } |
5500 | | |
5501 | | struct exec_recursive_params { |
5502 | | VALUE (*func) (VALUE, VALUE, int); |
5503 | | VALUE list; |
5504 | | VALUE obj; |
5505 | | VALUE pairid; |
5506 | | VALUE arg; |
5507 | | }; |
5508 | | |
5509 | | static VALUE |
5510 | | exec_recursive_i(RB_BLOCK_CALL_FUNC_ARGLIST(tag, data)) |
5511 | 0 | { |
5512 | 0 | struct exec_recursive_params *p = (void *)data; |
5513 | 0 | return (*p->func)(p->obj, p->arg, FALSE); |
5514 | 0 | } |
5515 | | |
5516 | | /* |
5517 | | * Calls func(obj, arg, recursive), where recursive is non-zero if the |
5518 | | * current method is called recursively on obj, or on the pair <obj, pairid> |
5519 | | * If outer is 0, then the innermost func will be called with recursive set |
5520 | | * to true, otherwise the outermost func will be called. In the latter case, |
5521 | | * all inner func are short-circuited by throw. |
5522 | | * Implementation details: the value thrown is the recursive list which is |
5523 | | * proper to the current method and unlikely to be caught anywhere else. |
5524 | | * list[recursive_key] is used as a flag for the outermost call. |
5525 | | */ |
5526 | | |
5527 | | static VALUE |
5528 | | exec_recursive(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE pairid, VALUE arg, int outer, ID mid) |
5529 | 309 | { |
5530 | 309 | VALUE result = Qundef; |
5531 | 309 | const VALUE sym = mid ? ID2SYM(mid) : ID2SYM(idNULL); |
5532 | 309 | struct exec_recursive_params p; |
5533 | 309 | int outermost; |
5534 | 309 | p.list = recursive_list_access(sym); |
5535 | 309 | p.obj = obj; |
5536 | 309 | p.pairid = pairid; |
5537 | 309 | p.arg = arg; |
5538 | 309 | outermost = outer && !recursive_check(p.list, ID2SYM(recursive_key), 0); |
5539 | | |
5540 | 309 | if (recursive_check(p.list, p.obj, pairid)) { |
5541 | 0 | if (outer && !outermost) { |
5542 | 0 | rb_throw_obj(p.list, p.list); |
5543 | 0 | } |
5544 | 0 | return (*func)(obj, arg, TRUE); |
5545 | 0 | } |
5546 | 309 | else { |
5547 | 309 | enum ruby_tag_type state; |
5548 | | |
5549 | 309 | p.func = func; |
5550 | | |
5551 | 309 | if (outermost) { |
5552 | 0 | recursive_push(p.list, ID2SYM(recursive_key), 0); |
5553 | 0 | recursive_push(p.list, p.obj, p.pairid); |
5554 | 0 | result = rb_catch_protect(p.list, exec_recursive_i, (VALUE)&p, &state); |
5555 | 0 | if (!recursive_pop(p.list, p.obj, p.pairid)) goto invalid; |
5556 | 0 | if (!recursive_pop(p.list, ID2SYM(recursive_key), 0)) goto invalid; |
5557 | 0 | if (state != TAG_NONE) EC_JUMP_TAG(GET_EC(), state); |
5558 | 0 | if (result == p.list) { |
5559 | 0 | result = (*func)(obj, arg, TRUE); |
5560 | 0 | } |
5561 | 0 | } |
5562 | 309 | else { |
5563 | 309 | volatile VALUE ret = Qundef; |
5564 | 309 | recursive_push(p.list, p.obj, p.pairid); |
5565 | 309 | EC_PUSH_TAG(GET_EC()); |
5566 | 309 | if ((state = EC_EXEC_TAG()) == TAG_NONE) { |
5567 | 309 | ret = (*func)(obj, arg, FALSE); |
5568 | 309 | } |
5569 | 309 | EC_POP_TAG(); |
5570 | 309 | if (!recursive_pop(p.list, p.obj, p.pairid)) { |
5571 | 0 | goto invalid; |
5572 | 0 | } |
5573 | 309 | if (state != TAG_NONE) EC_JUMP_TAG(GET_EC(), state); |
5574 | 309 | result = ret; |
5575 | 309 | } |
5576 | 309 | } |
5577 | 309 | *(volatile struct exec_recursive_params *)&p; |
5578 | 309 | return result; |
5579 | | |
5580 | 0 | invalid: |
5581 | 0 | rb_raise(rb_eTypeError, "invalid inspect_tbl pair_list " |
5582 | 0 | "for %+"PRIsVALUE" in %+"PRIsVALUE, |
5583 | 0 | sym, rb_thread_current()); |
5584 | 309 | UNREACHABLE_RETURN(Qundef); |
5585 | 309 | } |
5586 | | |
5587 | | /* |
5588 | | * Calls func(obj, arg, recursive), where recursive is non-zero if the |
5589 | | * current method is called recursively on obj |
5590 | | */ |
5591 | | |
5592 | | VALUE |
5593 | | rb_exec_recursive(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE arg) |
5594 | 108 | { |
5595 | 108 | return exec_recursive(func, obj, 0, arg, 0, rb_frame_last_func()); |
5596 | 108 | } |
5597 | | |
5598 | | /* |
5599 | | * Calls func(obj, arg, recursive), where recursive is non-zero if the |
5600 | | * current method is called recursively on the ordered pair <obj, paired_obj> |
5601 | | */ |
5602 | | |
5603 | | VALUE |
5604 | | rb_exec_recursive_paired(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE paired_obj, VALUE arg) |
5605 | 201 | { |
5606 | 201 | return exec_recursive(func, obj, rb_memory_id(paired_obj), arg, 0, rb_frame_last_func()); |
5607 | 201 | } |
5608 | | |
5609 | | /* |
5610 | | * If recursion is detected on the current method and obj, the outermost |
5611 | | * func will be called with (obj, arg, true). All inner func will be |
5612 | | * short-circuited using throw. |
5613 | | */ |
5614 | | |
5615 | | VALUE |
5616 | | rb_exec_recursive_outer(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE arg) |
5617 | 0 | { |
5618 | 0 | return exec_recursive(func, obj, 0, arg, 1, rb_frame_last_func()); |
5619 | 0 | } |
5620 | | |
5621 | | VALUE |
5622 | | rb_exec_recursive_outer_mid(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE arg, ID mid) |
5623 | 0 | { |
5624 | 0 | return exec_recursive(func, obj, 0, arg, 1, mid); |
5625 | 0 | } |
5626 | | |
5627 | | /* |
5628 | | * If recursion is detected on the current method, obj and paired_obj, |
5629 | | * the outermost func will be called with (obj, arg, true). All inner |
5630 | | * func will be short-circuited using throw. |
5631 | | */ |
5632 | | |
5633 | | VALUE |
5634 | | rb_exec_recursive_paired_outer(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE paired_obj, VALUE arg) |
5635 | 0 | { |
5636 | 0 | return exec_recursive(func, obj, rb_memory_id(paired_obj), arg, 1, rb_frame_last_func()); |
5637 | 0 | } |
5638 | | |
5639 | | /* |
5640 | | * call-seq: |
5641 | | * thread.backtrace -> array or nil |
5642 | | * |
5643 | | * Returns the current backtrace of the target thread. |
5644 | | * |
5645 | | */ |
5646 | | |
5647 | | static VALUE |
5648 | | rb_thread_backtrace_m(int argc, VALUE *argv, VALUE thval) |
5649 | 0 | { |
5650 | 0 | return rb_vm_thread_backtrace(argc, argv, thval); |
5651 | 0 | } |
5652 | | |
5653 | | /* call-seq: |
5654 | | * thread.backtrace_locations(*args) -> array or nil |
5655 | | * |
5656 | | * Returns the execution stack for the target thread---an array containing |
5657 | | * backtrace location objects. |
5658 | | * |
5659 | | * See Thread::Backtrace::Location for more information. |
5660 | | * |
5661 | | * This method behaves similarly to Kernel#caller_locations except it applies |
5662 | | * to a specific thread. |
5663 | | */ |
5664 | | static VALUE |
5665 | | rb_thread_backtrace_locations_m(int argc, VALUE *argv, VALUE thval) |
5666 | 0 | { |
5667 | 0 | return rb_vm_thread_backtrace_locations(argc, argv, thval); |
5668 | 0 | } |
5669 | | |
5670 | | void |
5671 | | Init_Thread_Mutex(void) |
5672 | 9 | { |
5673 | 9 | rb_thread_t *th = GET_THREAD(); |
5674 | | |
5675 | 9 | rb_native_mutex_initialize(&th->vm->workqueue_lock); |
5676 | 9 | rb_native_mutex_initialize(&th->interrupt_lock); |
5677 | 9 | } |
5678 | | |
5679 | | /* |
5680 | | * Document-class: ThreadError |
5681 | | * |
5682 | | * Raised when an invalid operation is attempted on a thread. |
5683 | | * |
5684 | | * For example, when no other thread has been started: |
5685 | | * |
5686 | | * Thread.stop |
5687 | | * |
5688 | | * This will raises the following exception: |
5689 | | * |
5690 | | * ThreadError: stopping only thread |
5691 | | * note: use sleep to stop forever |
5692 | | */ |
5693 | | |
5694 | | void |
5695 | | Init_Thread(void) |
5696 | 9 | { |
5697 | 9 | rb_thread_t *th = GET_THREAD(); |
5698 | | |
5699 | 9 | sym_never = ID2SYM(rb_intern_const("never")); |
5700 | 9 | sym_immediate = ID2SYM(rb_intern_const("immediate")); |
5701 | 9 | sym_on_blocking = ID2SYM(rb_intern_const("on_blocking")); |
5702 | | |
5703 | 9 | rb_define_singleton_method(rb_cThread, "new", thread_s_new, -1); |
5704 | 9 | rb_define_singleton_method(rb_cThread, "start", thread_start, -2); |
5705 | 9 | rb_define_singleton_method(rb_cThread, "fork", thread_start, -2); |
5706 | 9 | rb_define_singleton_method(rb_cThread, "main", rb_thread_s_main, 0); |
5707 | 9 | rb_define_singleton_method(rb_cThread, "current", thread_s_current, 0); |
5708 | 9 | rb_define_singleton_method(rb_cThread, "stop", thread_stop, 0); |
5709 | 9 | rb_define_singleton_method(rb_cThread, "kill", rb_thread_s_kill, 1); |
5710 | 9 | rb_define_singleton_method(rb_cThread, "exit", rb_thread_exit, 0); |
5711 | 9 | rb_define_singleton_method(rb_cThread, "pass", thread_s_pass, 0); |
5712 | 9 | rb_define_singleton_method(rb_cThread, "list", thread_list, 0); |
5713 | 9 | rb_define_singleton_method(rb_cThread, "abort_on_exception", rb_thread_s_abort_exc, 0); |
5714 | 9 | rb_define_singleton_method(rb_cThread, "abort_on_exception=", rb_thread_s_abort_exc_set, 1); |
5715 | 9 | rb_define_singleton_method(rb_cThread, "report_on_exception", rb_thread_s_report_exc, 0); |
5716 | 9 | rb_define_singleton_method(rb_cThread, "report_on_exception=", rb_thread_s_report_exc_set, 1); |
5717 | 9 | rb_define_singleton_method(rb_cThread, "ignore_deadlock", rb_thread_s_ignore_deadlock, 0); |
5718 | 9 | rb_define_singleton_method(rb_cThread, "ignore_deadlock=", rb_thread_s_ignore_deadlock_set, 1); |
5719 | 9 | rb_define_singleton_method(rb_cThread, "handle_interrupt", rb_thread_s_handle_interrupt, 1); |
5720 | 9 | rb_define_singleton_method(rb_cThread, "pending_interrupt?", rb_thread_s_pending_interrupt_p, -1); |
5721 | 9 | rb_define_method(rb_cThread, "pending_interrupt?", rb_thread_pending_interrupt_p, -1); |
5722 | | |
5723 | 9 | rb_define_method(rb_cThread, "initialize", thread_initialize, -2); |
5724 | 9 | rb_define_method(rb_cThread, "raise", thread_raise_m, -1); |
5725 | 9 | rb_define_method(rb_cThread, "join", thread_join_m, -1); |
5726 | 9 | rb_define_method(rb_cThread, "value", thread_value, 0); |
5727 | 9 | rb_define_method(rb_cThread, "kill", rb_thread_kill, 0); |
5728 | 9 | rb_define_method(rb_cThread, "terminate", rb_thread_kill, 0); |
5729 | 9 | rb_define_method(rb_cThread, "exit", rb_thread_kill, 0); |
5730 | 9 | rb_define_method(rb_cThread, "run", rb_thread_run, 0); |
5731 | 9 | rb_define_method(rb_cThread, "wakeup", rb_thread_wakeup, 0); |
5732 | 9 | rb_define_method(rb_cThread, "[]", rb_thread_aref, 1); |
5733 | 9 | rb_define_method(rb_cThread, "[]=", rb_thread_aset, 2); |
5734 | 9 | rb_define_method(rb_cThread, "fetch", rb_thread_fetch, -1); |
5735 | 9 | rb_define_method(rb_cThread, "key?", rb_thread_key_p, 1); |
5736 | 9 | rb_define_method(rb_cThread, "keys", rb_thread_keys, 0); |
5737 | 9 | rb_define_method(rb_cThread, "priority", rb_thread_priority, 0); |
5738 | 9 | rb_define_method(rb_cThread, "priority=", rb_thread_priority_set, 1); |
5739 | 9 | rb_define_method(rb_cThread, "status", rb_thread_status, 0); |
5740 | 9 | rb_define_method(rb_cThread, "thread_variable_get", rb_thread_variable_get, 1); |
5741 | 9 | rb_define_method(rb_cThread, "thread_variable_set", rb_thread_variable_set, 2); |
5742 | 9 | rb_define_method(rb_cThread, "thread_variables", rb_thread_variables, 0); |
5743 | 9 | rb_define_method(rb_cThread, "thread_variable?", rb_thread_variable_p, 1); |
5744 | 9 | rb_define_method(rb_cThread, "alive?", rb_thread_alive_p, 0); |
5745 | 9 | rb_define_method(rb_cThread, "stop?", rb_thread_stop_p, 0); |
5746 | 9 | rb_define_method(rb_cThread, "abort_on_exception", rb_thread_abort_exc, 0); |
5747 | 9 | rb_define_method(rb_cThread, "abort_on_exception=", rb_thread_abort_exc_set, 1); |
5748 | 9 | rb_define_method(rb_cThread, "report_on_exception", rb_thread_report_exc, 0); |
5749 | 9 | rb_define_method(rb_cThread, "report_on_exception=", rb_thread_report_exc_set, 1); |
5750 | 9 | rb_define_method(rb_cThread, "group", rb_thread_group, 0); |
5751 | 9 | rb_define_method(rb_cThread, "backtrace", rb_thread_backtrace_m, -1); |
5752 | 9 | rb_define_method(rb_cThread, "backtrace_locations", rb_thread_backtrace_locations_m, -1); |
5753 | | |
5754 | 9 | rb_define_method(rb_cThread, "name", rb_thread_getname, 0); |
5755 | 9 | rb_define_method(rb_cThread, "name=", rb_thread_setname, 1); |
5756 | 9 | rb_define_method(rb_cThread, "native_thread_id", rb_thread_native_thread_id, 0); |
5757 | 9 | rb_define_method(rb_cThread, "to_s", rb_thread_to_s, 0); |
5758 | 9 | rb_define_alias(rb_cThread, "inspect", "to_s"); |
5759 | | |
5760 | 9 | rb_vm_register_special_exception(ruby_error_stream_closed, rb_eIOError, |
5761 | 9 | "stream closed in another thread"); |
5762 | | |
5763 | 9 | cThGroup = rb_define_class("ThreadGroup", rb_cObject); |
5764 | 9 | rb_define_alloc_func(cThGroup, thgroup_s_alloc); |
5765 | 9 | rb_define_method(cThGroup, "list", thgroup_list, 0); |
5766 | 9 | rb_define_method(cThGroup, "enclose", thgroup_enclose, 0); |
5767 | 9 | rb_define_method(cThGroup, "enclosed?", thgroup_enclosed_p, 0); |
5768 | 9 | rb_define_method(cThGroup, "add", thgroup_add, 1); |
5769 | | |
5770 | 9 | const char * ptr = getenv("RUBY_THREAD_TIMESLICE"); |
5771 | | |
5772 | 9 | if (ptr) { |
5773 | 0 | long quantum = strtol(ptr, NULL, 0); |
5774 | 0 | if (quantum > 0 && !(SIZEOF_LONG > 4 && quantum > UINT32_MAX)) { |
5775 | 0 | thread_default_quantum_ms = (uint32_t)quantum; |
5776 | 0 | } |
5777 | 0 | else if (0) { |
5778 | 0 | fprintf(stderr, "Ignored RUBY_THREAD_TIMESLICE=%s\n", ptr); |
5779 | 0 | } |
5780 | 0 | } |
5781 | | |
5782 | 9 | { |
5783 | 9 | th->thgroup = th->ractor->thgroup_default = rb_obj_alloc(cThGroup); |
5784 | 9 | rb_define_const(cThGroup, "Default", th->thgroup); |
5785 | 9 | } |
5786 | | |
5787 | 9 | rb_eThreadError = rb_define_class("ThreadError", rb_eStandardError); |
5788 | | |
5789 | | /* init thread core */ |
5790 | 9 | { |
5791 | | /* main thread setting */ |
5792 | 9 | { |
5793 | | /* acquire global vm lock */ |
5794 | | #ifdef HAVE_PTHREAD_NP_H |
5795 | | VM_ASSERT(TH_SCHED(th)->running == th); |
5796 | | #endif |
5797 | | // thread_sched_to_running() should not be called because |
5798 | | // it assumes blocked by thread_sched_to_waiting(). |
5799 | | // thread_sched_to_running(sched, th); |
5800 | | |
5801 | 9 | th->pending_interrupt_queue = rb_ary_hidden_new(0); |
5802 | 9 | th->pending_interrupt_queue_checked = 0; |
5803 | 9 | th->pending_interrupt_mask_stack = rb_ary_hidden_new(0); |
5804 | 9 | } |
5805 | 9 | } |
5806 | | |
5807 | 9 | rb_thread_create_timer_thread(); |
5808 | | |
5809 | 9 | Init_thread_sync(); |
5810 | | |
5811 | | // TODO: Suppress unused function warning for now |
5812 | | // if (0) rb_thread_sched_destroy(NULL); |
5813 | 9 | } |
5814 | | |
5815 | | int |
5816 | | ruby_native_thread_p(void) |
5817 | 12 | { |
5818 | 12 | rb_thread_t *th = ruby_thread_from_native(); |
5819 | | |
5820 | 12 | return th != 0; |
5821 | 12 | } |
5822 | | |
5823 | | #ifdef NON_SCALAR_THREAD_ID |
5824 | | #define thread_id_str(th) (NULL) |
5825 | | #else |
5826 | 0 | #define thread_id_str(th) ((void *)(uintptr_t)(th)->nt->thread_id) |
5827 | | #endif |
5828 | | |
5829 | | static void |
5830 | | debug_deadlock_check(rb_ractor_t *r, VALUE msg) |
5831 | 0 | { |
5832 | 0 | rb_thread_t *th = 0; |
5833 | 0 | VALUE sep = rb_str_new_cstr("\n "); |
5834 | |
|
5835 | 0 | rb_str_catf(msg, "\n%d threads, %d sleeps current:%p main thread:%p\n", |
5836 | 0 | rb_ractor_living_thread_num(r), rb_ractor_sleeper_thread_num(r), |
5837 | 0 | (void *)GET_THREAD(), (void *)r->threads.main); |
5838 | |
|
5839 | 0 | ccan_list_for_each(&r->threads.set, th, lt_node) { |
5840 | 0 | rb_str_catf(msg, "* %+"PRIsVALUE"\n rb_thread_t:%p " |
5841 | 0 | "native:%p int:%u", |
5842 | 0 | th->self, (void *)th, th->nt ? thread_id_str(th) : "N/A", th->ec->interrupt_flag); |
5843 | |
|
5844 | 0 | if (th->locking_mutex) { |
5845 | 0 | rb_mutex_t *mutex = mutex_ptr(th->locking_mutex); |
5846 | 0 | rb_str_catf(msg, " mutex:%llu cond:%"PRIuSIZE, |
5847 | 0 | (unsigned long long)mutex->ec_serial, rb_mutex_num_waiting(mutex)); |
5848 | 0 | } |
5849 | |
|
5850 | 0 | { |
5851 | 0 | struct rb_waiting_list *list = th->join_list; |
5852 | 0 | while (list) { |
5853 | 0 | rb_str_catf(msg, "\n depended by: tb_thread_id:%p", (void *)list->thread); |
5854 | 0 | list = list->next; |
5855 | 0 | } |
5856 | 0 | } |
5857 | 0 | rb_str_catf(msg, "\n "); |
5858 | 0 | rb_str_concat(msg, rb_ary_join(rb_ec_backtrace_str_ary(th->ec, RUBY_BACKTRACE_START, RUBY_ALL_BACKTRACE_LINES), sep)); |
5859 | 0 | rb_str_catf(msg, "\n"); |
5860 | 0 | } |
5861 | 0 | } |
5862 | | |
5863 | | static void |
5864 | | rb_check_deadlock(rb_ractor_t *r) |
5865 | 0 | { |
5866 | 0 | if (GET_THREAD()->vm->thread_ignore_deadlock) return; |
5867 | | |
5868 | 0 | #ifdef RUBY_THREAD_PTHREAD_H |
5869 | 0 | if (r->threads.sched.readyq_cnt > 0) return; |
5870 | 0 | #endif |
5871 | | |
5872 | 0 | int sleeper_num = rb_ractor_sleeper_thread_num(r); |
5873 | 0 | int ltnum = rb_ractor_living_thread_num(r); |
5874 | |
|
5875 | 0 | if (ltnum > sleeper_num) return; |
5876 | 0 | if (ltnum < sleeper_num) rb_bug("sleeper must not be more than vm_living_thread_num(vm)"); |
5877 | | |
5878 | 0 | int found = 0; |
5879 | 0 | rb_thread_t *th = NULL; |
5880 | |
|
5881 | 0 | ccan_list_for_each(&r->threads.set, th, lt_node) { |
5882 | 0 | if (th->status != THREAD_STOPPED_FOREVER || RUBY_VM_INTERRUPTED(th->ec)) { |
5883 | 0 | found = 1; |
5884 | 0 | } |
5885 | 0 | else if (th->locking_mutex) { |
5886 | 0 | rb_mutex_t *mutex = mutex_ptr(th->locking_mutex); |
5887 | 0 | if (mutex->ec_serial == rb_ec_serial(th->ec) || (!mutex->ec_serial && !ccan_list_empty(&mutex->waitq))) { |
5888 | 0 | found = 1; |
5889 | 0 | } |
5890 | 0 | } |
5891 | 0 | if (found) |
5892 | 0 | break; |
5893 | 0 | } |
5894 | |
|
5895 | 0 | if (!found) { |
5896 | 0 | VALUE argv[2]; |
5897 | 0 | argv[0] = rb_eFatal; |
5898 | 0 | argv[1] = rb_str_new2("No live threads left. Deadlock?"); |
5899 | 0 | debug_deadlock_check(r, argv[1]); |
5900 | 0 | rb_ractor_sleeper_threads_dec(GET_RACTOR()); |
5901 | 0 | rb_threadptr_raise(r->threads.main, 2, argv); |
5902 | 0 | } |
5903 | 0 | } |
5904 | | |
5905 | | static void |
5906 | | update_line_coverage(VALUE data, const rb_trace_arg_t *trace_arg) |
5907 | 0 | { |
5908 | 0 | const rb_control_frame_t *cfp = GET_EC()->cfp; |
5909 | 0 | VALUE coverage = rb_iseq_coverage(CFP_ISEQ(cfp)); |
5910 | 0 | if (RB_TYPE_P(coverage, T_ARRAY) && !RBASIC_CLASS(coverage)) { |
5911 | 0 | VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES); |
5912 | 0 | if (lines) { |
5913 | 0 | long line = rb_sourceline() - 1; |
5914 | 0 | VM_ASSERT(line >= 0); |
5915 | 0 | long count; |
5916 | 0 | VALUE num; |
5917 | 0 | void rb_iseq_clear_event_flags(const rb_iseq_t *iseq, size_t pos, rb_event_flag_t reset); |
5918 | 0 | if (GET_VM()->coverage_mode & COVERAGE_TARGET_ONESHOT_LINES) { |
5919 | 0 | rb_iseq_clear_event_flags(CFP_ISEQ(cfp), CFP_PC(cfp) - ISEQ_BODY(CFP_ISEQ(cfp))->iseq_encoded - 1, RUBY_EVENT_COVERAGE_LINE); |
5920 | 0 | rb_ary_push(lines, LONG2FIX(line + 1)); |
5921 | 0 | return; |
5922 | 0 | } |
5923 | 0 | if (line >= RARRAY_LEN(lines)) { /* no longer tracked */ |
5924 | 0 | return; |
5925 | 0 | } |
5926 | 0 | num = RARRAY_AREF(lines, line); |
5927 | 0 | if (!FIXNUM_P(num)) return; |
5928 | 0 | count = FIX2LONG(num) + 1; |
5929 | 0 | if (POSFIXABLE(count)) { |
5930 | 0 | RARRAY_ASET(lines, line, LONG2FIX(count)); |
5931 | 0 | } |
5932 | 0 | } |
5933 | 0 | } |
5934 | 0 | } |
5935 | | |
5936 | | static void |
5937 | | update_branch_coverage(VALUE data, const rb_trace_arg_t *trace_arg) |
5938 | 0 | { |
5939 | 0 | const rb_control_frame_t *cfp = GET_EC()->cfp; |
5940 | 0 | VALUE coverage = rb_iseq_coverage(CFP_ISEQ(cfp)); |
5941 | 0 | if (RB_TYPE_P(coverage, T_ARRAY) && !RBASIC_CLASS(coverage)) { |
5942 | 0 | VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES); |
5943 | 0 | if (branches) { |
5944 | 0 | long pc = CFP_PC(cfp) - ISEQ_BODY(CFP_ISEQ(cfp))->iseq_encoded - 1; |
5945 | 0 | long idx = FIX2INT(RARRAY_AREF(ISEQ_PC2BRANCHINDEX(CFP_ISEQ(cfp)), pc)), count; |
5946 | 0 | VALUE counters = RARRAY_AREF(branches, 1); |
5947 | 0 | VALUE num = RARRAY_AREF(counters, idx); |
5948 | 0 | count = FIX2LONG(num) + 1; |
5949 | 0 | if (POSFIXABLE(count)) { |
5950 | 0 | RARRAY_ASET(counters, idx, LONG2FIX(count)); |
5951 | 0 | } |
5952 | 0 | } |
5953 | 0 | } |
5954 | 0 | } |
5955 | | |
5956 | | const rb_method_entry_t * |
5957 | | rb_resolve_me_location(const rb_method_entry_t *me, VALUE resolved_location[5]) |
5958 | 0 | { |
5959 | 0 | VALUE path, beg_pos_lineno, beg_pos_column, end_pos_lineno, end_pos_column; |
5960 | |
|
5961 | 0 | if (!me->def) return NULL; // negative cme |
5962 | | |
5963 | 0 | retry: |
5964 | 0 | switch (me->def->type) { |
5965 | 0 | case VM_METHOD_TYPE_ISEQ: { |
5966 | 0 | const rb_iseq_t *iseq = me->def->body.iseq.iseqptr; |
5967 | 0 | rb_iseq_location_t *loc = &ISEQ_BODY(iseq)->location; |
5968 | 0 | path = rb_iseq_path(iseq); |
5969 | 0 | beg_pos_lineno = INT2FIX(loc->code_location.beg_pos.lineno); |
5970 | 0 | beg_pos_column = INT2FIX(loc->code_location.beg_pos.column); |
5971 | 0 | end_pos_lineno = INT2FIX(loc->code_location.end_pos.lineno); |
5972 | 0 | end_pos_column = INT2FIX(loc->code_location.end_pos.column); |
5973 | 0 | break; |
5974 | 0 | } |
5975 | 0 | case VM_METHOD_TYPE_BMETHOD: { |
5976 | 0 | const rb_iseq_t *iseq = rb_proc_get_iseq(me->def->body.bmethod.proc, 0); |
5977 | 0 | if (iseq) { |
5978 | 0 | rb_iseq_location_t *loc; |
5979 | 0 | rb_iseq_check(iseq); |
5980 | 0 | path = rb_iseq_path(iseq); |
5981 | 0 | loc = &ISEQ_BODY(iseq)->location; |
5982 | 0 | beg_pos_lineno = INT2FIX(loc->code_location.beg_pos.lineno); |
5983 | 0 | beg_pos_column = INT2FIX(loc->code_location.beg_pos.column); |
5984 | 0 | end_pos_lineno = INT2FIX(loc->code_location.end_pos.lineno); |
5985 | 0 | end_pos_column = INT2FIX(loc->code_location.end_pos.column); |
5986 | 0 | break; |
5987 | 0 | } |
5988 | 0 | return NULL; |
5989 | 0 | } |
5990 | 0 | case VM_METHOD_TYPE_ALIAS: |
5991 | 0 | me = me->def->body.alias.original_me; |
5992 | 0 | goto retry; |
5993 | 0 | case VM_METHOD_TYPE_REFINED: |
5994 | 0 | me = me->def->body.refined.orig_me; |
5995 | 0 | if (!me) return NULL; |
5996 | 0 | goto retry; |
5997 | 0 | default: |
5998 | 0 | return NULL; |
5999 | 0 | } |
6000 | | |
6001 | | /* found */ |
6002 | 0 | if (RB_TYPE_P(path, T_ARRAY)) { |
6003 | 0 | path = rb_ary_entry(path, 1); |
6004 | 0 | if (!RB_TYPE_P(path, T_STRING)) return NULL; /* just for the case... */ |
6005 | 0 | } |
6006 | 0 | if (resolved_location) { |
6007 | 0 | resolved_location[0] = path; |
6008 | 0 | resolved_location[1] = beg_pos_lineno; |
6009 | 0 | resolved_location[2] = beg_pos_column; |
6010 | 0 | resolved_location[3] = end_pos_lineno; |
6011 | 0 | resolved_location[4] = end_pos_column; |
6012 | 0 | } |
6013 | 0 | return me; |
6014 | 0 | } |
6015 | | |
6016 | | static void |
6017 | | update_method_coverage(VALUE me2counter, rb_trace_arg_t *trace_arg) |
6018 | 0 | { |
6019 | 0 | const rb_control_frame_t *cfp = GET_EC()->cfp; |
6020 | 0 | const rb_callable_method_entry_t *cme = rb_vm_frame_method_entry(cfp); |
6021 | 0 | const rb_method_entry_t *me = (const rb_method_entry_t *)cme; |
6022 | 0 | VALUE rcount; |
6023 | 0 | long count; |
6024 | |
|
6025 | 0 | me = rb_resolve_me_location(me, 0); |
6026 | 0 | if (!me) return; |
6027 | | |
6028 | 0 | rcount = rb_hash_aref(me2counter, (VALUE) me); |
6029 | 0 | count = FIXNUM_P(rcount) ? FIX2LONG(rcount) + 1 : 1; |
6030 | 0 | if (POSFIXABLE(count)) { |
6031 | 0 | rb_hash_aset(me2counter, (VALUE) me, LONG2FIX(count)); |
6032 | 0 | } |
6033 | 0 | } |
6034 | | |
6035 | | VALUE |
6036 | | rb_get_coverages(void) |
6037 | 9 | { |
6038 | 9 | return GET_VM()->coverages; |
6039 | 9 | } |
6040 | | |
6041 | | int |
6042 | | rb_get_coverage_mode(void) |
6043 | 0 | { |
6044 | 0 | return GET_VM()->coverage_mode; |
6045 | 0 | } |
6046 | | |
6047 | | void |
6048 | | rb_set_coverages(VALUE coverages, int mode, VALUE me2counter) |
6049 | 0 | { |
6050 | 0 | GET_VM()->coverages = coverages; |
6051 | 0 | GET_VM()->me2counter = me2counter; |
6052 | 0 | GET_VM()->coverage_mode = mode; |
6053 | 0 | } |
6054 | | |
6055 | | void |
6056 | | rb_resume_coverages(void) |
6057 | 0 | { |
6058 | 0 | int mode = GET_VM()->coverage_mode; |
6059 | 0 | VALUE me2counter = GET_VM()->me2counter; |
6060 | 0 | rb_add_event_hook2((rb_event_hook_func_t) update_line_coverage, RUBY_EVENT_COVERAGE_LINE, Qnil, RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG); |
6061 | 0 | if (mode & COVERAGE_TARGET_BRANCHES) { |
6062 | 0 | rb_add_event_hook2((rb_event_hook_func_t) update_branch_coverage, RUBY_EVENT_COVERAGE_BRANCH, Qnil, RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG); |
6063 | 0 | } |
6064 | 0 | if (mode & COVERAGE_TARGET_METHODS) { |
6065 | 0 | rb_add_event_hook2((rb_event_hook_func_t) update_method_coverage, RUBY_EVENT_CALL, me2counter, RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG); |
6066 | 0 | } |
6067 | 0 | } |
6068 | | |
6069 | | void |
6070 | | rb_suspend_coverages(void) |
6071 | 0 | { |
6072 | 0 | rb_remove_event_hook((rb_event_hook_func_t) update_line_coverage); |
6073 | 0 | if (GET_VM()->coverage_mode & COVERAGE_TARGET_BRANCHES) { |
6074 | 0 | rb_remove_event_hook((rb_event_hook_func_t) update_branch_coverage); |
6075 | 0 | } |
6076 | 0 | if (GET_VM()->coverage_mode & COVERAGE_TARGET_METHODS) { |
6077 | 0 | rb_remove_event_hook((rb_event_hook_func_t) update_method_coverage); |
6078 | 0 | } |
6079 | 0 | } |
6080 | | |
6081 | | /* Make coverage arrays empty so old covered files are no longer tracked. */ |
6082 | | void |
6083 | | rb_reset_coverages(void) |
6084 | 0 | { |
6085 | 0 | rb_clear_coverages(); |
6086 | 0 | rb_iseq_remove_coverage_all(); |
6087 | 0 | GET_VM()->coverages = Qfalse; |
6088 | 0 | } |
6089 | | |
6090 | | VALUE |
6091 | | rb_default_coverage(int n) |
6092 | 0 | { |
6093 | 0 | VALUE coverage = rb_ary_hidden_new_fill(3); |
6094 | 0 | VALUE lines = Qfalse, branches = Qfalse; |
6095 | 0 | int mode = GET_VM()->coverage_mode; |
6096 | |
|
6097 | 0 | if (mode & COVERAGE_TARGET_LINES) { |
6098 | 0 | lines = n > 0 ? rb_ary_hidden_new_fill(n) : rb_ary_hidden_new(0); |
6099 | 0 | } |
6100 | 0 | RARRAY_ASET(coverage, COVERAGE_INDEX_LINES, lines); |
6101 | |
|
6102 | 0 | if (mode & COVERAGE_TARGET_BRANCHES) { |
6103 | 0 | branches = rb_ary_hidden_new_fill(2); |
6104 | | /* internal data structures for branch coverage: |
6105 | | * |
6106 | | * { branch base node => |
6107 | | * [base_type, base_first_lineno, base_first_column, base_last_lineno, base_last_column, { |
6108 | | * branch target id => |
6109 | | * [target_type, target_first_lineno, target_first_column, target_last_lineno, target_last_column, target_counter_index], |
6110 | | * ... |
6111 | | * }], |
6112 | | * ... |
6113 | | * } |
6114 | | * |
6115 | | * Example: |
6116 | | * { NODE_CASE => |
6117 | | * [1, 0, 4, 3, { |
6118 | | * NODE_WHEN => [2, 8, 2, 9, 0], |
6119 | | * NODE_WHEN => [3, 8, 3, 9, 1], |
6120 | | * ... |
6121 | | * }], |
6122 | | * ... |
6123 | | * } |
6124 | | */ |
6125 | 0 | VALUE structure = rb_hash_new(); |
6126 | 0 | rb_obj_hide(structure); |
6127 | 0 | RARRAY_ASET(branches, 0, structure); |
6128 | | /* branch execution counters */ |
6129 | 0 | RARRAY_ASET(branches, 1, rb_ary_hidden_new(0)); |
6130 | 0 | } |
6131 | 0 | RARRAY_ASET(coverage, COVERAGE_INDEX_BRANCHES, branches); |
6132 | |
|
6133 | 0 | return coverage; |
6134 | 0 | } |
6135 | | |
6136 | | static VALUE |
6137 | | uninterruptible_exit(VALUE v) |
6138 | 0 | { |
6139 | 0 | rb_thread_t *cur_th = GET_THREAD(); |
6140 | 0 | rb_ary_pop(cur_th->pending_interrupt_mask_stack); |
6141 | |
|
6142 | 0 | cur_th->pending_interrupt_queue_checked = 0; |
6143 | 0 | if (!rb_threadptr_pending_interrupt_empty_p(cur_th)) { |
6144 | 0 | RUBY_VM_SET_INTERRUPT(cur_th->ec); |
6145 | 0 | } |
6146 | 0 | return Qnil; |
6147 | 0 | } |
6148 | | |
6149 | | VALUE |
6150 | | rb_uninterruptible(VALUE (*b_proc)(VALUE), VALUE data) |
6151 | 0 | { |
6152 | 0 | VALUE interrupt_mask = rb_ident_hash_new(); |
6153 | 0 | rb_thread_t *cur_th = GET_THREAD(); |
6154 | |
|
6155 | 0 | rb_hash_aset(interrupt_mask, rb_cObject, sym_never); |
6156 | 0 | OBJ_FREEZE(interrupt_mask); |
6157 | 0 | rb_ary_push(cur_th->pending_interrupt_mask_stack, interrupt_mask); |
6158 | |
|
6159 | 0 | VALUE ret = rb_ensure(b_proc, data, uninterruptible_exit, Qnil); |
6160 | |
|
6161 | 0 | RUBY_VM_CHECK_INTS(cur_th->ec); |
6162 | 0 | return ret; |
6163 | 0 | } |
6164 | | |
6165 | | static void |
6166 | | thread_specific_storage_alloc(rb_thread_t *th) |
6167 | 0 | { |
6168 | 0 | VM_ASSERT(th->specific_storage == NULL); |
6169 | |
|
6170 | 0 | if (UNLIKELY(specific_key_count > 0)) { |
6171 | 0 | th->specific_storage = ZALLOC_N(void *, RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX); |
6172 | 0 | } |
6173 | 0 | } |
6174 | | |
6175 | | rb_internal_thread_specific_key_t |
6176 | | rb_internal_thread_specific_key_create(void) |
6177 | 0 | { |
6178 | 0 | rb_vm_t *vm = GET_VM(); |
6179 | |
|
6180 | 0 | if (specific_key_count == 0 && vm->ractor.cnt > 1) { |
6181 | 0 | rb_raise(rb_eThreadError, "The first rb_internal_thread_specific_key_create() is called with multiple ractors"); |
6182 | 0 | } |
6183 | 0 | else if (specific_key_count > RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX) { |
6184 | 0 | rb_raise(rb_eThreadError, "rb_internal_thread_specific_key_create() is called more than %d times", RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX); |
6185 | 0 | } |
6186 | 0 | else { |
6187 | 0 | rb_internal_thread_specific_key_t key = specific_key_count++; |
6188 | |
|
6189 | 0 | if (key == 0) { |
6190 | | // allocate |
6191 | 0 | rb_ractor_t *cr = GET_RACTOR(); |
6192 | 0 | rb_thread_t *th; |
6193 | |
|
6194 | 0 | ccan_list_for_each(&cr->threads.set, th, lt_node) { |
6195 | 0 | thread_specific_storage_alloc(th); |
6196 | 0 | } |
6197 | 0 | } |
6198 | 0 | return key; |
6199 | 0 | } |
6200 | 0 | } |
6201 | | |
6202 | | // async and native thread safe. |
6203 | | void * |
6204 | | rb_internal_thread_specific_get(VALUE thread_val, rb_internal_thread_specific_key_t key) |
6205 | 0 | { |
6206 | 0 | rb_thread_t *th = DATA_PTR(thread_val); |
6207 | |
|
6208 | 0 | VM_ASSERT(rb_thread_ptr(thread_val) == th); |
6209 | 0 | VM_ASSERT(key < RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX); |
6210 | 0 | VM_ASSERT(th->specific_storage); |
6211 | |
|
6212 | 0 | return th->specific_storage[key]; |
6213 | 0 | } |
6214 | | |
6215 | | // async and native thread safe. |
6216 | | void |
6217 | | rb_internal_thread_specific_set(VALUE thread_val, rb_internal_thread_specific_key_t key, void *data) |
6218 | 0 | { |
6219 | 0 | rb_thread_t *th = DATA_PTR(thread_val); |
6220 | |
|
6221 | 0 | VM_ASSERT(rb_thread_ptr(thread_val) == th); |
6222 | 0 | VM_ASSERT(key < RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX); |
6223 | 0 | VM_ASSERT(th->specific_storage); |
6224 | |
|
6225 | 0 | th->specific_storage[key] = data; |
6226 | 0 | } |
6227 | | |
6228 | | // interrupt_exec |
6229 | | |
6230 | | struct rb_interrupt_exec_task { |
6231 | | struct ccan_list_node node; |
6232 | | |
6233 | | rb_interrupt_exec_func_t *func; |
6234 | | void *data; |
6235 | | enum rb_interrupt_exec_flag flags; |
6236 | | }; |
6237 | | |
6238 | | void |
6239 | | rb_threadptr_interrupt_exec_task_mark(rb_thread_t *th) |
6240 | 45.5k | { |
6241 | 45.5k | struct rb_interrupt_exec_task *task; |
6242 | | |
6243 | 45.5k | ccan_list_for_each(&th->interrupt_exec_tasks, task, node) { |
6244 | 0 | if (task->flags & rb_interrupt_exec_flag_value_data) { |
6245 | 0 | rb_gc_mark((VALUE)task->data); |
6246 | 0 | } |
6247 | 0 | } |
6248 | 45.5k | } |
6249 | | |
6250 | | // native thread safe |
6251 | | // th should be available |
6252 | | void |
6253 | | rb_threadptr_interrupt_exec(rb_thread_t *th, rb_interrupt_exec_func_t *func, void *data, enum rb_interrupt_exec_flag flags) |
6254 | 0 | { |
6255 | | // should not use ALLOC |
6256 | 0 | struct rb_interrupt_exec_task *task = ALLOC(struct rb_interrupt_exec_task); |
6257 | 0 | *task = (struct rb_interrupt_exec_task) { |
6258 | 0 | .flags = flags, |
6259 | 0 | .func = func, |
6260 | 0 | .data = data, |
6261 | 0 | }; |
6262 | |
|
6263 | 0 | rb_native_mutex_lock(&th->interrupt_lock); |
6264 | 0 | { |
6265 | 0 | ccan_list_add_tail(&th->interrupt_exec_tasks, &task->node); |
6266 | 0 | threadptr_set_interrupt_locked(th, true); |
6267 | 0 | } |
6268 | 0 | rb_native_mutex_unlock(&th->interrupt_lock); |
6269 | 0 | } |
6270 | | |
6271 | | static void |
6272 | | threadptr_interrupt_exec_exec(rb_thread_t *th) |
6273 | 0 | { |
6274 | 0 | while (1) { |
6275 | 0 | struct rb_interrupt_exec_task *task; |
6276 | |
|
6277 | 0 | rb_native_mutex_lock(&th->interrupt_lock); |
6278 | 0 | { |
6279 | 0 | task = ccan_list_pop(&th->interrupt_exec_tasks, struct rb_interrupt_exec_task, node); |
6280 | 0 | } |
6281 | 0 | rb_native_mutex_unlock(&th->interrupt_lock); |
6282 | |
|
6283 | 0 | RUBY_DEBUG_LOG("task:%p", task); |
6284 | |
|
6285 | 0 | if (task) { |
6286 | 0 | if (task->flags & rb_interrupt_exec_flag_new_thread) { |
6287 | 0 | rb_thread_create(task->func, task->data); |
6288 | 0 | } |
6289 | 0 | else { |
6290 | 0 | (*task->func)(task->data); |
6291 | 0 | } |
6292 | 0 | SIZED_FREE(task); |
6293 | 0 | } |
6294 | 0 | else { |
6295 | 0 | break; |
6296 | 0 | } |
6297 | 0 | } |
6298 | 0 | } |
6299 | | |
6300 | | static void |
6301 | | threadptr_interrupt_exec_cleanup(rb_thread_t *th) |
6302 | 0 | { |
6303 | 0 | rb_native_mutex_lock(&th->interrupt_lock); |
6304 | 0 | { |
6305 | 0 | struct rb_interrupt_exec_task *task; |
6306 | |
|
6307 | 0 | while ((task = ccan_list_pop(&th->interrupt_exec_tasks, struct rb_interrupt_exec_task, node)) != NULL) { |
6308 | 0 | SIZED_FREE(task); |
6309 | 0 | } |
6310 | 0 | } |
6311 | 0 | rb_native_mutex_unlock(&th->interrupt_lock); |
6312 | 0 | } |
6313 | | |
6314 | | // native thread safe |
6315 | | // func/data should be native thread safe |
6316 | | void |
6317 | | rb_ractor_interrupt_exec(struct rb_ractor_struct *target_r, |
6318 | | rb_interrupt_exec_func_t *func, void *data, enum rb_interrupt_exec_flag flags) |
6319 | 0 | { |
6320 | 0 | RUBY_DEBUG_LOG("flags:%d", (int)flags); |
6321 | |
|
6322 | 0 | rb_thread_t *main_th = target_r->threads.main; |
6323 | 0 | rb_threadptr_interrupt_exec(main_th, func, data, flags | rb_interrupt_exec_flag_new_thread); |
6324 | 0 | } |
6325 | | |