Ruby 4.1.0dev (2026-09-13 revision 74d4ed7691e0e69c07810cb39ad5efcb025ce256)
scheduler.c (74d4ed7691e0e69c07810cb39ad5efcb025ce256)
1/**********************************************************************
2
3 scheduler.c
4
5 $Author$
6
7 Copyright (C) 2020 Samuel Grant Dawson Williams
8
9**********************************************************************/
10
11#include "vm_core.h"
12#include "eval_intern.h"
14#include "ruby/io.h"
15#include "ruby/io/buffer.h"
16
17#include "ruby/thread.h"
18
19// For `ruby_thread_has_gvl_p`:
20#include "internal/thread.h"
21
22// For atomic operations:
23#include "ruby_atomic.h"
24
25static ID id_close;
26static ID id_scheduler_close;
27
28static ID id_block;
29static ID id_unblock;
30
31static ID id_yield;
32
33#ifdef FIBER_SCHEDULER_CALL_TIMEOUT_AFTER
34static ID id_timeout_after;
35#endif
36static ID id_kernel_sleep;
37static ID id_process_wait;
38
39static ID id_io_read, id_io_pread;
40static ID id_io_write, id_io_pwrite;
41static ID id_io_wait;
42static ID id_io_select;
43static ID id_io_close;
44
45static ID id_address_resolve;
46
47static ID id_blocking_operation_wait;
48static ID id_fiber_interrupt;
49
50static ID id_fiber_schedule;
51
52// Our custom blocking operation class
53static VALUE rb_cFiberSchedulerBlockingOperation;
54
55/*
56 * Custom blocking operation structure for blocking operations
57 * This replaces the use of Ruby procs to avoid use-after-free issues
58 * and provides a cleaner C API for native work pools.
59 */
60
61typedef enum {
62 RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED, // Submitted but not started
63 RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING, // Currently running
64 RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED, // Finished (success/error)
65 RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED // Cancelled
66} rb_fiber_blocking_operation_status_t;
67
69 void *(*function)(void *);
70 void *data;
71
72 rb_unblock_function_t *unblock_function;
73 void *data2;
74
75 int flags;
77
78 // Execution status
79 volatile rb_atomic_t status;
80};
81
82static size_t
83blocking_operation_memsize(const void *ptr)
84{
86}
87
88static const rb_data_type_t blocking_operation_data_type = {
89 "Fiber::Scheduler::BlockingOperation",
90 {
91 NULL, // nothing to mark
93 blocking_operation_memsize,
94 },
95 0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
96};
97
98/*
99 * Allocate a new blocking operation
100 */
101static VALUE
102blocking_operation_alloc(VALUE klass)
103{
104 rb_fiber_scheduler_blocking_operation_t *blocking_operation;
105 VALUE obj = TypedData_Make_Struct(klass, rb_fiber_scheduler_blocking_operation_t, &blocking_operation_data_type, blocking_operation);
106
107 blocking_operation->function = NULL;
108 blocking_operation->data = NULL;
109 blocking_operation->unblock_function = NULL;
110 blocking_operation->data2 = NULL;
111 blocking_operation->flags = 0;
112 blocking_operation->state = NULL;
113 blocking_operation->status = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED;
114
115 return obj;
116}
117
118/*
119 * Get the blocking operation struct from a Ruby object
120 */
122get_blocking_operation(VALUE obj)
123{
124 rb_fiber_scheduler_blocking_operation_t *blocking_operation;
125 TypedData_Get_Struct(obj, rb_fiber_scheduler_blocking_operation_t, &blocking_operation_data_type, blocking_operation);
126 return blocking_operation;
127}
128
129/*
130 * Document-method: Fiber::Scheduler::BlockingOperation#call
131 *
132 * Execute the blocking operation. This method releases the GVL and calls
133 * the blocking function, then restores the errno value.
134 *
135 * Returns nil. The actual result is stored in the associated state object.
136 */
137static VALUE
138blocking_operation_call(VALUE self)
139{
140 rb_fiber_scheduler_blocking_operation_t *blocking_operation = get_blocking_operation(self);
141
142 if (blocking_operation->status != RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED) {
143 rb_raise(rb_eRuntimeError, "Blocking operation has already been executed!");
144 }
145
146 if (blocking_operation->function == NULL) {
147 rb_raise(rb_eRuntimeError, "Blocking operation has no function to execute!");
148 }
149
150 if (blocking_operation->state == NULL) {
151 rb_raise(rb_eRuntimeError, "Blocking operation has no result object!");
152 }
153
154 // Mark as executing
155 blocking_operation->status = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING;
156
157 // Execute the blocking operation without GVL
158 blocking_operation->state->result = rb_nogvl(blocking_operation->function, blocking_operation->data,
159 blocking_operation->unblock_function, blocking_operation->data2,
160 blocking_operation->flags);
161 blocking_operation->state->saved_errno = rb_errno();
162
163 // Mark as completed
164 blocking_operation->status = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED;
165
166 return Qnil;
167}
168
169/*
170 * C API: Extract blocking operation struct from Ruby object (GVL required)
171 *
172 * This function safely extracts the opaque struct from a BlockingOperation VALUE
173 * while holding the GVL. The returned pointer can be passed to worker threads
174 * and used with rb_fiber_scheduler_blocking_operation_execute_opaque_nogvl.
175 *
176 * Returns the opaque struct pointer on success, NULL on error.
177 * Must be called while holding the GVL.
178 */
181{
182 return get_blocking_operation(self);
183}
184
185/*
186 * C API: Execute blocking operation from opaque struct (GVL not required)
187 *
188 * This function executes a blocking operation using the opaque struct pointer
189 * obtained from rb_fiber_scheduler_blocking_operation_extract.
190 * It can be called from native threads without holding the GVL.
191 *
192 * Returns 0 on success, -1 on error.
193 */
194int
196{
197 if (blocking_operation == NULL) {
198 return -1;
199 }
200
201 if (blocking_operation->function == NULL || blocking_operation->state == NULL) {
202 return -1; // Invalid blocking operation
203 }
204
205 // Resolve sentinel values for unblock_function and data2:
206 rb_thread_resolve_unblock_function(&blocking_operation->unblock_function, &blocking_operation->data2, GET_THREAD());
207
208 // Atomically check if we can transition from QUEUED to EXECUTING
209 rb_atomic_t expected = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED;
210 if (RUBY_ATOMIC_CAS(blocking_operation->status, expected, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING) != expected) {
211 // Already cancelled or in wrong state
212 return -1;
213 }
214
215 // Now we're executing - call the function
216 blocking_operation->state->result = blocking_operation->function(blocking_operation->data);
217 blocking_operation->state->saved_errno = errno;
218
219 // Atomically transition to completed (unless cancelled during execution)
220 expected = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING;
221 if (RUBY_ATOMIC_CAS(blocking_operation->status, expected, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED) == expected) {
222 // Successfully completed
223 return 0;
224 } else {
225 // Was cancelled during execution
226 blocking_operation->state->saved_errno = EINTR;
227 return -1;
228 }
229}
230
231/*
232 * C API: Create a new blocking operation
233 *
234 * This creates a blocking operation that can be executed by native work pools.
235 * The blocking operation holds references to the function and data safely.
236 */
237VALUE
238rb_fiber_scheduler_blocking_operation_new(void *(*function)(void *), void *data,
239 rb_unblock_function_t *unblock_function, void *data2,
240 int flags, struct rb_fiber_scheduler_blocking_operation_state *state)
241{
242 VALUE self = blocking_operation_alloc(rb_cFiberSchedulerBlockingOperation);
243 rb_fiber_scheduler_blocking_operation_t *blocking_operation = get_blocking_operation(self);
244
245 blocking_operation->function = function;
246 blocking_operation->data = data;
247 blocking_operation->unblock_function = unblock_function;
248 blocking_operation->data2 = data2;
249 blocking_operation->flags = flags;
250 blocking_operation->state = state;
251
252 return self;
253}
254
255/*
256 *
257 * Document-class: Fiber::Scheduler
258 *
259 * This is not an existing class, but documentation of the interface that Scheduler
260 * object should comply to in order to be used as argument to Fiber.scheduler and handle non-blocking
261 * fibers. See also the "Non-blocking fibers" section in Fiber class docs for explanations
262 * of some concepts.
263 *
264 * Scheduler's behavior and usage are expected to be as follows:
265 *
266 * * When the execution in the non-blocking Fiber reaches some blocking operation (like
267 * sleep, wait for a process, or a non-ready I/O), it calls some of the scheduler's
268 * hook methods, listed below.
269 * * Scheduler somehow registers what the current fiber is waiting on, and yields control
270 * to other fibers with Fiber.yield (so the fiber would be suspended while expecting its
271 * wait to end, and other fibers in the same thread can perform)
272 * * At the end of the current thread execution, the scheduler's method #scheduler_close is called
273 * * The scheduler runs into a wait loop, checking all the blocked fibers (which it has
274 * registered on hook calls) and resuming them when the awaited resource is ready
275 * (e.g. I/O ready or sleep time elapsed).
276 *
277 * This way concurrent execution will be achieved transparently for every
278 * individual Fiber's code.
279 *
280 * Scheduler implementations are provided by gems, like
281 * Async[https://github.com/socketry/async].
282 *
283 * Hook methods are:
284 *
285 * * #io_wait, #io_read, #io_write, #io_pread, #io_pwrite #io_select, and #io_close
286 * * #process_wait
287 * * #kernel_sleep
288 * * #timeout_after
289 * * #address_resolve
290 * * #block and #unblock
291 * * #blocking_operation_wait
292 * * #fiber_interrupt
293 * * #yield
294 * * (the list is expanded as Ruby developers make more methods having non-blocking calls)
295 *
296 * The #block, #unblock, #kernel_sleep, #io_wait, and #fiber_interrupt hooks are mandatory.
297 * Other hooks are optional unless specified otherwise.
298 *
299 * It is also strongly recommended that the scheduler implements the #fiber method, which is
300 * delegated to by Fiber.schedule.
301 *
302 * Sample _toy_ implementation of the scheduler can be found in Ruby's code, in
303 * <tt>test/fiber/scheduler.rb</tt>
304 *
305 */
306void
307Init_Fiber_Scheduler(void)
308{
309 id_close = rb_intern_const("close");
310 id_scheduler_close = rb_intern_const("scheduler_close");
311
312 id_block = rb_intern_const("block");
313 id_unblock = rb_intern_const("unblock");
314 id_yield = rb_intern_const("yield");
315
316#ifdef FIBER_SCHEDULER_CALL_TIMEOUT_AFTER
317 id_timeout_after = rb_intern_const("timeout_after");
318#endif
319 id_kernel_sleep = rb_intern_const("kernel_sleep");
320 id_process_wait = rb_intern_const("process_wait");
321
322 id_io_read = rb_intern_const("io_read");
323 id_io_pread = rb_intern_const("io_pread");
324 id_io_write = rb_intern_const("io_write");
325 id_io_pwrite = rb_intern_const("io_pwrite");
326
327 id_io_wait = rb_intern_const("io_wait");
328 id_io_select = rb_intern_const("io_select");
329 id_io_close = rb_intern_const("io_close");
330
331 id_address_resolve = rb_intern_const("address_resolve");
332
333 id_blocking_operation_wait = rb_intern_const("blocking_operation_wait");
334 id_fiber_interrupt = rb_intern_const("fiber_interrupt");
335
336 id_fiber_schedule = rb_intern_const("fiber");
337
338 // Define an anonymous BlockingOperation class for internal use only
339 // This is completely hidden from Ruby code and cannot be instantiated directly
340 rb_cFiberSchedulerBlockingOperation = rb_class_new(rb_cObject);
341 rb_define_alloc_func(rb_cFiberSchedulerBlockingOperation, blocking_operation_alloc);
342 rb_define_method(rb_cFiberSchedulerBlockingOperation, "call", blocking_operation_call, 0);
343
344 // Register the anonymous class as a GC root so it doesn't get collected
345 rb_gc_register_mark_object(rb_cFiberSchedulerBlockingOperation);
346
347#if 0 /* for RDoc */
348 rb_cFiberScheduler = rb_define_class_under(rb_cFiber, "Scheduler", rb_cObject);
349 rb_define_method(rb_cFiberScheduler, "close", rb_fiber_scheduler_close, 0);
350 rb_define_method(rb_cFiberScheduler, "process_wait", rb_fiber_scheduler_process_wait, 2);
351 rb_define_method(rb_cFiberScheduler, "io_wait", rb_fiber_scheduler_io_wait, 3);
352 rb_define_method(rb_cFiberScheduler, "io_read", rb_fiber_scheduler_io_read, 4);
353 rb_define_method(rb_cFiberScheduler, "io_write", rb_fiber_scheduler_io_write, 4);
354 rb_define_method(rb_cFiberScheduler, "io_pread", rb_fiber_scheduler_io_pread, 5);
355 rb_define_method(rb_cFiberScheduler, "io_pwrite", rb_fiber_scheduler_io_pwrite, 5);
356 rb_define_method(rb_cFiberScheduler, "io_select", rb_fiber_scheduler_io_select, 4);
357 rb_define_method(rb_cFiberScheduler, "kernel_sleep", rb_fiber_scheduler_kernel_sleep, 1);
358 rb_define_method(rb_cFiberScheduler, "address_resolve", rb_fiber_scheduler_address_resolve, 1);
359 rb_define_method(rb_cFiberScheduler, "timeout_after", rb_fiber_scheduler_timeout_after, 3);
360 rb_define_method(rb_cFiberScheduler, "block", rb_fiber_scheduler_block, 2);
361 rb_define_method(rb_cFiberScheduler, "unblock", rb_fiber_scheduler_unblock, 2);
362 rb_define_method(rb_cFiberScheduler, "fiber", rb_fiber_scheduler_fiber, -2);
363 rb_define_method(rb_cFiberScheduler, "blocking_operation_wait", rb_fiber_scheduler_blocking_operation_wait, -2);
364 rb_define_method(rb_cFiberScheduler, "yield", rb_fiber_scheduler_yield, 0);
365 rb_define_method(rb_cFiberScheduler, "fiber_interrupt", rb_fiber_scheduler_fiber_interrupt, 2);
366 rb_define_method(rb_cFiberScheduler, "io_close", rb_fiber_scheduler_io_close, 1);
367#endif
368}
369
370VALUE
372{
373 RUBY_ASSERT(ruby_thread_has_gvl_p());
374
375 rb_thread_t *thread = GET_THREAD();
376 RUBY_ASSERT(thread);
377
378 return thread->scheduler;
379}
380
381static void
382verify_interface(VALUE scheduler)
383{
384 if (!rb_respond_to(scheduler, id_block)) {
385 rb_raise(rb_eArgError, "Scheduler must implement #block");
386 }
387
388 if (!rb_respond_to(scheduler, id_unblock)) {
389 rb_raise(rb_eArgError, "Scheduler must implement #unblock");
390 }
391
392 if (!rb_respond_to(scheduler, id_kernel_sleep)) {
393 rb_raise(rb_eArgError, "Scheduler must implement #kernel_sleep");
394 }
395
396 if (!rb_respond_to(scheduler, id_io_wait)) {
397 rb_raise(rb_eArgError, "Scheduler must implement #io_wait");
398 }
399
400 if (!rb_respond_to(scheduler, id_fiber_interrupt)) {
401 rb_raise(rb_eArgError, "Scheduler must implement #fiber_interrupt");
402 }
403}
404
405static VALUE
406fiber_scheduler_close(VALUE scheduler)
407{
408 return rb_fiber_scheduler_close(scheduler);
409}
410
411static VALUE
412fiber_scheduler_close_ensure(VALUE _thread)
413{
414 rb_thread_t *thread = (rb_thread_t*)_thread;
415 thread->scheduler = Qnil;
416
417 return Qnil;
418}
419
420VALUE
422{
423 RUBY_ASSERT(ruby_thread_has_gvl_p());
424
425 rb_thread_t *thread = GET_THREAD();
426 RUBY_ASSERT(thread);
427
428 if (scheduler != Qnil) {
429 verify_interface(scheduler);
430 }
431
432 // We invoke Scheduler#close when setting it to something else, to ensure
433 // the previous scheduler runs to completion before changing the scheduler.
434 // That way, we do not need to consider interactions, e.g., of a Fiber from
435 // the previous scheduler with the new scheduler.
436 if (thread->scheduler != Qnil) {
437 // rb_fiber_scheduler_close(thread->scheduler);
438 rb_ensure(fiber_scheduler_close, thread->scheduler, fiber_scheduler_close_ensure, (VALUE)thread);
439 }
440
441 thread->scheduler = scheduler;
442
443 return thread->scheduler;
444}
445
446static VALUE
447fiber_scheduler_current_for_threadptr(rb_thread_t *thread)
448{
449 RUBY_ASSERT(thread);
450
451 if (thread->blocking == 0) {
452 return thread->scheduler;
453 }
454 else {
455 return Qnil;
456 }
457}
458
460{
461 RUBY_ASSERT(ruby_thread_has_gvl_p());
462
463 return fiber_scheduler_current_for_threadptr(GET_THREAD());
464}
465
466// This function is allowed to be called without holding the GVL.
468{
469 return fiber_scheduler_current_for_threadptr(rb_thread_ptr(thread));
470}
471
473{
474 return fiber_scheduler_current_for_threadptr(thread);
475}
476
477/*
478 *
479 * Document-method: Fiber::Scheduler#close
480 *
481 * Called when the current thread exits. The scheduler is expected to implement this
482 * method in order to allow all waiting fibers to finalize their execution.
483 *
484 * The suggested pattern is to implement the main event loop in the #close method.
485 *
486 */
487VALUE
489{
490 RUBY_ASSERT(ruby_thread_has_gvl_p());
491
492 VALUE result;
493
494 // The reason for calling `scheduler_close` before calling `close` is for
495 // legacy schedulers which implement `close` and expect the user to call
496 // it. Subsequently, that method would call `Fiber.set_scheduler(nil)`
497 // which should call `scheduler_close`. If it were to call `close`, it
498 // would create an infinite loop.
499
500 result = rb_check_funcall(scheduler, id_scheduler_close, 0, NULL);
501 if (!UNDEF_P(result)) return result;
502
503 result = rb_check_funcall(scheduler, id_close, 0, NULL);
504 if (!UNDEF_P(result)) return result;
505
506 return Qnil;
507}
508
509VALUE
511{
512 if (timeout) {
513 return rb_float_new((double)timeout->tv_sec + (0.000001 * timeout->tv_usec));
514 }
515
516 return Qnil;
517}
518
519/*
520 * Document-method: Fiber::Scheduler#kernel_sleep
521 * call-seq: kernel_sleep(duration = nil)
522 *
523 * Invoked by Kernel#sleep and Thread::Mutex#sleep and is expected to provide
524 * an implementation of sleeping in a non-blocking way. Implementation might
525 * register the current fiber in some list of "which fiber wait until what
526 * moment", call Fiber.yield to pass control, and then in #close resume
527 * the fibers whose wait period has elapsed.
528 *
529 */
530VALUE
532{
533 return rb_funcall(scheduler, id_kernel_sleep, 1, timeout);
534}
535
536VALUE
537rb_fiber_scheduler_kernel_sleepv(VALUE scheduler, int argc, VALUE * argv)
538{
539 return rb_funcallv(scheduler, id_kernel_sleep, argc, argv);
540}
541
548VALUE
550{
551 // First try to call the scheduler's yield method, if it exists:
552 VALUE result = rb_check_funcall(scheduler, id_yield, 0, NULL);
553 if (!UNDEF_P(result)) return result;
554
555 // Otherwise, we can emulate yield by sleeping for 0 seconds:
556 return rb_fiber_scheduler_kernel_sleep(scheduler, RB_INT2NUM(0));
557}
558
559#ifdef FIBER_SCHEDULER_CALL_TIMEOUT_AFTER
560/*
561 * Document-method: Fiber::Scheduler#timeout_after
562 * call-seq: timeout_after(duration, exception_class, *exception_arguments, &block) -> result of block
563 *
564 * Invoked by Timeout.timeout to execute the given +block+ within the given
565 * +duration+. It can also be invoked directly by the scheduler or user code.
566 *
567 * Attempt to limit the execution time of a given +block+ to the given
568 * +duration+ if possible. When a non-blocking operation causes the +block+'s
569 * execution time to exceed the specified +duration+, that non-blocking
570 * operation should be interrupted by raising the specified +exception_class+
571 * constructed with the given +exception_arguments+.
572 *
573 * General execution timeouts are often considered risky. This implementation
574 * will only interrupt non-blocking operations. This is by design because it's
575 * expected that non-blocking operations can fail for a variety of
576 * unpredictable reasons, so applications should already be robust in handling
577 * these conditions and by implication timeouts.
578 *
579 * However, as a result of this design, if the +block+ does not invoke any
580 * non-blocking operations, it will be impossible to interrupt it. If you
581 * desire to provide predictable points for timeouts, consider adding
582 * <tt>sleep(0)</tt>.
583 *
584 * If the block is executed successfully, its result will be returned.
585 *
586 * The exception will typically be raised using Fiber#raise.
587 */
588VALUE
589rb_fiber_scheduler_timeout_after(VALUE scheduler, VALUE timeout, VALUE exception, VALUE message)
590{
591 VALUE arguments[] = {
592 timeout, exception, message
593 };
594
595 return rb_check_funcall(scheduler, id_timeout_after, 3, arguments);
596}
597
598VALUE
599rb_fiber_scheduler_timeout_afterv(VALUE scheduler, int argc, VALUE * argv)
600{
601 return rb_check_funcall(scheduler, id_timeout_after, argc, argv);
602}
603#endif
604
605/*
606 * Document-method: Fiber::Scheduler#process_wait
607 * call-seq: process_wait(pid, flags)
608 *
609 * Invoked by Process::Status.wait in order to wait for a specified process.
610 * See that method description for arguments description.
611 *
612 * Suggested minimal implementation:
613 *
614 * Thread.new do
615 * Process::Status.wait(pid, flags)
616 * end.value
617 *
618 * This hook is optional: if it is not present in the current scheduler,
619 * Process::Status.wait will behave as a blocking method.
620 *
621 * Expected to return a Process::Status instance.
622 */
623VALUE
624rb_fiber_scheduler_process_wait(VALUE scheduler, rb_pid_t pid, int flags)
625{
626 VALUE arguments[] = {
627 PIDT2NUM(pid), RB_INT2NUM(flags)
628 };
629
630 return rb_check_funcall(scheduler, id_process_wait, 2, arguments);
631}
632
633/*
634 * Document-method: Fiber::Scheduler#block
635 * call-seq: block(blocker, timeout = nil)
636 *
637 * Invoked by methods like Thread.join, and by Thread::Mutex, to signify that current
638 * Fiber is blocked until further notice (e.g. #unblock) or until +timeout+ has
639 * elapsed.
640 *
641 * +blocker+ is what we are waiting on, informational only (for debugging and
642 * logging). There are no guarantee about its value.
643 *
644 * Expected to return boolean, specifying whether the blocking operation was
645 * successful or not.
646 */
647VALUE
648rb_fiber_scheduler_block(VALUE scheduler, VALUE blocker, VALUE timeout)
649{
650 return rb_funcall(scheduler, id_block, 2, blocker, timeout);
651}
652
653/*
654 * Document-method: Fiber::Scheduler#unblock
655 * call-seq: unblock(blocker, fiber)
656 *
657 * Invoked to wake up Fiber previously blocked with #block (for example, Thread::Mutex#lock
658 * calls #block and Thread::Mutex#unlock calls #unblock). The scheduler should use
659 * the +fiber+ parameter to understand which fiber is unblocked.
660 *
661 * +blocker+ is what was awaited for, but it is informational only (for debugging
662 * and logging), and it is not guaranteed to be the same value as the +blocker+ for
663 * #block.
664 *
665 */
666VALUE
667rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber)
668{
669 RUBY_ASSERT(rb_obj_is_fiber(fiber));
670
671 VALUE result;
672 enum ruby_tag_type state;
673
674 // `rb_fiber_scheduler_unblock` can be called from points where `errno` is expected to be preserved. Therefore, we should save and restore it. For example `io_binwrite` calls `rb_fiber_scheduler_unblock` and if `errno` is reset to 0 by user code, it will break the error handling in `io_write`.
675 //
676 // If we explicitly preserve `errno` in `io_binwrite` and other similar functions (e.g. by returning it), this code is no longer needed. I hope in the future we will be able to remove it.
677 int saved_errno = errno;
678
679 // We must prevent interrupts while invoking the unblock method, because otherwise fibers can be left permanently blocked if an interrupt occurs during the execution of user code. See also `rb_fiber_scheduler_fiber_interrupt`.
680 rb_execution_context_t * volatile ec = GET_EC();
681 volatile int saved_interrupt_mask = ec->interrupt_mask;
682 ec->interrupt_mask |= PENDING_INTERRUPT_MASK;
683
684 rb_control_frame_t *volatile cfp = ec->cfp;
685 EC_PUSH_TAG(ec);
686 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
687 result = rb_funcall(scheduler, id_unblock, 2, blocker, fiber);
688 }
689 else {
690 rb_vm_rewind_cfp(ec, cfp);
691 }
692 EC_POP_TAG();
693
694 ec->interrupt_mask = saved_interrupt_mask;
695
696 if (state) {
697 EC_JUMP_TAG(ec, state);
698 }
699
700 RUBY_VM_CHECK_INTS(ec);
701
702 errno = saved_errno;
703
704 return result;
705}
706
707/*
708 * Document-method: Fiber::Scheduler#io_wait
709 * call-seq: io_wait(io, events, timeout)
710 *
711 * Invoked by IO#wait, IO#wait_readable, IO#wait_writable to ask whether the
712 * specified descriptor is ready for specified events within
713 * the specified +timeout+.
714 *
715 * +events+ is a bit mask of <tt>IO::READABLE</tt>, <tt>IO::WRITABLE</tt>, and
716 * <tt>IO::PRIORITY</tt>.
717 *
718 * Suggested implementation should register which Fiber is waiting for which
719 * resources and immediately calling Fiber.yield to pass control to other
720 * fibers. Then, in the #close method, the scheduler might dispatch all the
721 * I/O resources to fibers waiting for it.
722 *
723 * Expected to return the subset of events that are ready immediately.
724 *
725 */
726static VALUE
727fiber_scheduler_io_wait(VALUE _argument) {
728 VALUE *arguments = (VALUE*)_argument;
729
730 return rb_funcallv(arguments[0], id_io_wait, 3, arguments + 1);
731}
732
733VALUE
734rb_fiber_scheduler_io_wait(VALUE scheduler, VALUE io, VALUE events, VALUE timeout)
735{
736 VALUE arguments[] = {
737 scheduler, io, events, timeout
738 };
739
740 return rb_thread_io_blocking_operation(io, fiber_scheduler_io_wait, (VALUE)&arguments);
741}
742
743VALUE
748
749VALUE
754
755/*
756 * Document-method: Fiber::Scheduler#io_select
757 * call-seq: io_select(readables, writables, exceptables, timeout)
758 *
759 * Invoked by IO.select to ask whether the specified descriptors are ready for
760 * specified events within the specified +timeout+.
761 *
762 * Expected to return the 3-tuple of Array of IOs that are ready.
763 *
764 */
765VALUE rb_fiber_scheduler_io_select(VALUE scheduler, VALUE readables, VALUE writables, VALUE exceptables, VALUE timeout)
766{
767 VALUE arguments[] = {
768 readables, writables, exceptables, timeout
769 };
770
771 return rb_fiber_scheduler_io_selectv(scheduler, 4, arguments);
772}
773
775{
776 // I wondered about extracting argv, and checking if there is only a single
777 // IO instance, and instead calling `io_wait`. However, it would require a
778 // decent amount of work and it would be hard to preserve the exact
779 // semantics of IO.select.
780
781 return rb_check_funcall(scheduler, id_io_select, argc, argv);
782}
783
784/*
785 * Document-method: Fiber::Scheduler#io_read
786 * call-seq: io_read(io, buffer, offset, length) -> read length or -errno
787 *
788 * Invoked by IO#read or IO::Buffer#read to perform one read of at most
789 * +length+ bytes from +io+ into a specified +buffer+ (see IO::Buffer),
790 * starting at the given +offset+.
791 *
792 * A short read is returned directly. Specifying a +length+ of 0 returns 0
793 * without performing a read.
794 *
795 * The implementation should perform one non-blocking read and return
796 * <tt>-EAGAIN</tt> if +io+ is not ready. The caller can then wait and retry as
797 * appropriate.
798 *
799 * See IO::Buffer for an interface available to return data.
800 *
801 * Expected to return number of bytes read, or, in case of an error,
802 * <tt>-errno</tt> (negated number corresponding to system's error code).
803 *
804 */
805static VALUE
806fiber_scheduler_io_read(VALUE _argument) {
807 VALUE *arguments = (VALUE*)_argument;
808
809 return rb_funcallv(arguments[0], id_io_read, 4, arguments + 1);
810}
811
812VALUE
813rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length)
814{
815 if (!rb_respond_to(scheduler, id_io_read)) {
816 return RUBY_Qundef;
817 }
818
819 VALUE arguments[] = {
820 scheduler, io, buffer, SIZET2NUM(offset), SIZET2NUM(length)
821 };
822
823 return rb_thread_io_blocking_operation(io, fiber_scheduler_io_read, (VALUE)&arguments);
824}
825
826/*
827 * Document-method: Fiber::Scheduler#io_pread
828 * call-seq: io_pread(io, buffer, from, offset, length) -> read length or -errno
829 *
830 * Invoked by IO#pread or IO::Buffer#pread to perform one read of at most
831 * +length+ bytes from +io+ at offset +from+ into a specified +buffer+ (see
832 * IO::Buffer), starting at the given +offset+.
833 *
834 * This method is semantically the same as #io_read, but it allows to specify
835 * the offset to read from and is often better for asynchronous IO on the same
836 * file.
837 *
838 */
839static VALUE
840fiber_scheduler_io_pread(VALUE _argument) {
841 VALUE *arguments = (VALUE*)_argument;
842
843 return rb_funcallv(arguments[0], id_io_pread, 5, arguments + 1);
844}
845
846VALUE
847rb_fiber_scheduler_io_pread(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t offset, size_t length)
848{
849 if (!rb_respond_to(scheduler, id_io_pread)) {
850 return RUBY_Qundef;
851 }
852
853 VALUE arguments[] = {
854 scheduler, io, buffer, OFFT2NUM(from), SIZET2NUM(offset), SIZET2NUM(length)
855 };
856
857 return rb_thread_io_blocking_operation(io, fiber_scheduler_io_pread, (VALUE)&arguments);
858}
859
860/*
861 * Document-method: Fiber::Scheduler#io_write
862 * call-seq: io_write(io, buffer, offset, length) -> written length or -errno
863 *
864 * Invoked by IO#write or IO::Buffer#write to perform one write of at most
865 * +length+ bytes to +io+ from a specified +buffer+ (see IO::Buffer), starting
866 * at the given +offset+.
867 *
868 * A short write is returned directly. Specifying a +length+ of 0 returns 0
869 * without performing a write.
870 *
871 * The implementation should perform one non-blocking write and return
872 * <tt>-EAGAIN</tt> if +io+ is not ready. The caller can then wait and retry as
873 * appropriate.
874 *
875 * See IO::Buffer for an interface available to get data from buffer
876 * efficiently.
877 *
878 * Expected to return number of bytes written, or, in case of an error,
879 * <tt>-errno</tt> (negated number corresponding to system's error code).
880 *
881 */
882static VALUE
883fiber_scheduler_io_write(VALUE _argument) {
884 VALUE *arguments = (VALUE*)_argument;
885
886 return rb_funcallv(arguments[0], id_io_write, 4, arguments + 1);
887}
888
889VALUE
890rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length)
891{
892 if (!rb_respond_to(scheduler, id_io_write)) {
893 return RUBY_Qundef;
894 }
895
896 VALUE arguments[] = {
897 scheduler, io, buffer, SIZET2NUM(offset), SIZET2NUM(length)
898 };
899
900 return rb_thread_io_blocking_operation(io, fiber_scheduler_io_write, (VALUE)&arguments);
901}
902
903/*
904 * Document-method: Fiber::Scheduler#io_pwrite
905 * call-seq: io_pwrite(io, buffer, from, offset, length) -> written length or -errno
906 *
907 * Invoked by IO#pwrite or IO::Buffer#pwrite to perform one write of at most
908 * +length+ bytes to +io+ at offset +from+ from a specified +buffer+ (see
909 * IO::Buffer), starting at the given +offset+.
910 *
911 * This method is semantically the same as #io_write, but it allows to specify
912 * the offset to write to and is often better for asynchronous IO on the same
913 * file.
914 *
915 */
916static VALUE
917fiber_scheduler_io_pwrite(VALUE _argument) {
918 VALUE *arguments = (VALUE*)_argument;
919
920 return rb_funcallv(arguments[0], id_io_pwrite, 5, arguments + 1);
921}
922
923VALUE
924rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t offset, size_t length)
925{
926
927
928 if (!rb_respond_to(scheduler, id_io_pwrite)) {
929 return RUBY_Qundef;
930 }
931
932 VALUE arguments[] = {
933 scheduler, io, buffer, OFFT2NUM(from), SIZET2NUM(offset), SIZET2NUM(length)
934 };
935
936 return rb_thread_io_blocking_operation(io, fiber_scheduler_io_pwrite, (VALUE)&arguments);
937}
938
940 VALUE scheduler;
941 VALUE io;
942 VALUE buffer;
943 rb_off_t from;
944 size_t offset;
945 size_t length;
946};
947
948static VALUE
949fiber_scheduler_io_read_memory(VALUE _arguments)
950{
951 struct fiber_scheduler_io_memory_arguments *arguments = (void *)_arguments;
952
953 return rb_fiber_scheduler_io_read(arguments->scheduler, arguments->io, arguments->buffer, arguments->offset, arguments->length);
954}
955
956VALUE
957rb_fiber_scheduler_io_read_memory(VALUE scheduler, VALUE io, void *base, size_t size)
958{
959 VALUE buffer = rb_io_buffer_new_locked(base, size, 0);
960
961 struct fiber_scheduler_io_memory_arguments arguments = {
962 .scheduler = scheduler,
963 .io = io,
964 .buffer = buffer,
965 .offset = 0,
966 .length = size,
967 };
968
969 return rb_ensure(fiber_scheduler_io_read_memory, (VALUE)&arguments, rb_io_buffer_free_locked, buffer);
970}
971
972static VALUE
973fiber_scheduler_io_write_memory(VALUE _arguments)
974{
975 struct fiber_scheduler_io_memory_arguments *arguments = (void *)_arguments;
976
977 return rb_fiber_scheduler_io_write(arguments->scheduler, arguments->io, arguments->buffer, arguments->offset, arguments->length);
978}
979
980VALUE
981rb_fiber_scheduler_io_write_memory(VALUE scheduler, VALUE io, const void *base, size_t size)
982{
983 VALUE buffer = rb_io_buffer_new_locked((void*)base, size, RB_IO_BUFFER_READONLY);
984
985 struct fiber_scheduler_io_memory_arguments arguments = {
986 .scheduler = scheduler,
987 .io = io,
988 .buffer = buffer,
989 .offset = 0,
990 .length = size,
991 };
992
993 return rb_ensure(fiber_scheduler_io_write_memory, (VALUE)&arguments, rb_io_buffer_free_locked, buffer);
994}
995
996static VALUE
997fiber_scheduler_io_pread_memory(VALUE _arguments)
998{
999 struct fiber_scheduler_io_memory_arguments *arguments = (void *)_arguments;
1000
1001 return rb_fiber_scheduler_io_pread(arguments->scheduler, arguments->io, arguments->from, arguments->buffer, arguments->offset, arguments->length);
1002}
1003
1004VALUE
1005rb_fiber_scheduler_io_pread_memory(VALUE scheduler, VALUE io, rb_off_t from, void *base, size_t size)
1006{
1007 VALUE buffer = rb_io_buffer_new_locked(base, size, 0);
1008
1009 struct fiber_scheduler_io_memory_arguments arguments = {
1010 .scheduler = scheduler,
1011 .io = io,
1012 .buffer = buffer,
1013 .from = from,
1014 .offset = 0,
1015 .length = size,
1016 };
1017
1018 return rb_ensure(fiber_scheduler_io_pread_memory, (VALUE)&arguments, rb_io_buffer_free_locked, buffer);
1019}
1020
1021static VALUE
1022fiber_scheduler_io_pwrite_memory(VALUE _arguments)
1023{
1024 struct fiber_scheduler_io_memory_arguments *arguments = (void *)_arguments;
1025
1026 return rb_fiber_scheduler_io_pwrite(arguments->scheduler, arguments->io, arguments->from, arguments->buffer, arguments->offset, arguments->length);
1027}
1028
1029VALUE
1030rb_fiber_scheduler_io_pwrite_memory(VALUE scheduler, VALUE io, rb_off_t from, const void *base, size_t size)
1031{
1032 VALUE buffer = rb_io_buffer_new_locked((void*)base, size, RB_IO_BUFFER_READONLY);
1033
1034 struct fiber_scheduler_io_memory_arguments arguments = {
1035 .scheduler = scheduler,
1036 .io = io,
1037 .buffer = buffer,
1038 .from = from,
1039 .offset = 0,
1040 .length = size,
1041 };
1042
1043 return rb_ensure(fiber_scheduler_io_pwrite_memory, (VALUE)&arguments, rb_io_buffer_free_locked, buffer);
1044}
1045
1046/*
1047 * Document-method: Fiber::Scheduler#io_close
1048 * call-seq: io_close(fd)
1049 *
1050 * Invoked by Ruby's core methods to notify scheduler that the IO object is closed. Note that
1051 * the method will receive an integer file descriptor of the closed object, not an object
1052 * itself.
1053 */
1054VALUE
1056{
1057 VALUE arguments[] = {io};
1058
1059 return rb_check_funcall(scheduler, id_io_close, 1, arguments);
1060}
1061
1062/*
1063 * Document-method: Fiber::Scheduler#address_resolve
1064 * call-seq: address_resolve(hostname) -> array_of_strings or nil
1065 *
1066 * Invoked by any method that performs a non-reverse DNS lookup. The most
1067 * notable method is Addrinfo.getaddrinfo, but there are many other.
1068 *
1069 * The method is expected to return an array of strings corresponding to ip
1070 * addresses the +hostname+ is resolved to, or +nil+ if it can not be resolved.
1071 *
1072 * Fairly exhaustive list of all possible call-sites:
1073 *
1074 * - Addrinfo.getaddrinfo
1075 * - Addrinfo.tcp
1076 * - Addrinfo.udp
1077 * - Addrinfo.ip
1078 * - Addrinfo.new
1079 * - Addrinfo.marshal_load
1080 * - SOCKSSocket.new
1081 * - TCPServer.new
1082 * - TCPSocket.new
1083 * - IPSocket.getaddress
1084 * - TCPSocket.gethostbyname
1085 * - UDPSocket#connect
1086 * - UDPSocket#bind
1087 * - UDPSocket#send
1088 * - Socket.getaddrinfo
1089 * - Socket.gethostbyname
1090 * - Socket.pack_sockaddr_in
1091 * - Socket.sockaddr_in
1092 * - Socket.unpack_sockaddr_in
1093 */
1094VALUE
1096{
1097 VALUE arguments[] = {
1098 hostname
1099 };
1100
1101 return rb_check_funcall(scheduler, id_address_resolve, 1, arguments);
1102}
1103
1104/*
1105 * Document-method: Fiber::Scheduler#blocking_operation_wait
1106 * call-seq: blocking_operation_wait(blocking_operation)
1107 *
1108 * Invoked by Ruby's core methods to run a blocking operation in a non-blocking way.
1109 * The blocking_operation is an opaque object that encapsulates the blocking operation
1110 * and responds to a <tt>#call</tt> method without any arguments.
1111 *
1112 * If the scheduler doesn't implement this method, or if the scheduler doesn't execute
1113 * the blocking operation, Ruby will fall back to the non-scheduler implementation.
1114 *
1115 * Minimal suggested implementation is:
1116 *
1117 * def blocking_operation_wait(blocking_operation)
1118 * Thread.new { blocking_operation.call }.join
1119 * end
1120 */
1121VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void* (*function)(void *), void *data, rb_unblock_function_t *unblock_function, void *data2, int flags, struct rb_fiber_scheduler_blocking_operation_state *state)
1122{
1123 // Check if scheduler supports blocking_operation_wait before creating the object
1124 if (!rb_respond_to(scheduler, id_blocking_operation_wait)) {
1125 return Qundef;
1126 }
1127
1128 // Create a new BlockingOperation with the blocking operation
1129 VALUE blocking_operation = rb_fiber_scheduler_blocking_operation_new(function, data, unblock_function, data2, flags, state);
1130
1131 VALUE result = rb_funcall(scheduler, id_blocking_operation_wait, 1, blocking_operation);
1132
1133 // Get the operation data to check if it was executed
1134 rb_fiber_scheduler_blocking_operation_t *operation = get_blocking_operation(blocking_operation);
1135 rb_atomic_t current_status = RUBY_ATOMIC_LOAD(operation->status);
1136
1137 // Invalidate the operation now that we're done with it
1138 operation->function = NULL;
1139 operation->state = NULL;
1140 operation->data = NULL;
1141 operation->data2 = NULL;
1142 operation->unblock_function = NULL;
1143
1144 // Ensure that the blocking operation remains visible until this point:
1145 RB_GC_GUARD(blocking_operation);
1146
1147 // If the blocking operation was never executed, return Qundef to signal the caller to use rb_nogvl instead
1148 if (current_status == RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED) {
1149 return Qundef;
1150 }
1151
1152 return result;
1153}
1154
1155/*
1156 * Document-method: Fiber::Scheduler#fiber_interrupt
1157 * call-seq: fiber_interrupt(fiber, exception)
1158 *
1159 * Invoked by Ruby's core methods to notify the scheduler that the blocked fiber should be interrupted
1160 * with an exception. For example, IO#close uses this method to interrupt fibers that are performing
1161 * blocking IO operations.
1162 *
1163 */
1165{
1166 VALUE arguments[] = {
1167 fiber, exception
1168 };
1169
1170 VALUE result;
1171 enum ruby_tag_type state;
1172
1173 // We must prevent interrupts while invoking the fiber_interrupt method, because otherwise fibers can be left permanently blocked if an interrupt occurs during the execution of user code. See also `rb_fiber_scheduler_unblock`.
1174 rb_execution_context_t * volatile ec = GET_EC();
1175 volatile int saved_interrupt_mask = ec->interrupt_mask;
1176 ec->interrupt_mask |= PENDING_INTERRUPT_MASK;
1177
1178 rb_control_frame_t *volatile cfp = ec->cfp;
1179 EC_PUSH_TAG(ec);
1180 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
1181 result = rb_funcallv(scheduler, id_fiber_interrupt, 2, arguments);
1182 }
1183 else {
1184 rb_vm_rewind_cfp(ec, cfp);
1185 }
1186 EC_POP_TAG();
1187
1188 ec->interrupt_mask = saved_interrupt_mask;
1189
1190 if (state) {
1191 EC_JUMP_TAG(ec, state);
1192 }
1193
1194 RUBY_VM_CHECK_INTS(ec);
1195
1196 return result;
1197}
1198
1199/*
1200 * Document-method: Fiber::Scheduler#fiber
1201 * call-seq: fiber(&block)
1202 *
1203 * Implementation of the Fiber.schedule. The method is <em>expected</em> to immediately
1204 * run the given block of code in a separate non-blocking fiber, and to return that Fiber.
1205 *
1206 * Minimal suggested implementation is:
1207 *
1208 * def fiber(&block)
1209 * fiber = Fiber.new(blocking: false, &block)
1210 * fiber.resume
1211 * fiber
1212 * end
1213 */
1214VALUE
1215rb_fiber_scheduler_fiber(VALUE scheduler, int argc, VALUE *argv, int kw_splat)
1216{
1217 return rb_funcall_passing_block_kw(scheduler, id_fiber_schedule, argc, argv, kw_splat);
1218}
1219
1220/*
1221 * C API: Cancel a blocking operation
1222 *
1223 * This function cancels a blocking operation. If the operation is queued,
1224 * it just marks it as cancelled. If it's executing, it marks it as cancelled
1225 * and calls the unblock function to interrupt the operation.
1226 *
1227 * Returns 1 if unblock function was called, 0 if just marked cancelled, -1 on error.
1228 */
1229int
1231{
1232 if (blocking_operation == NULL) {
1233 return -1;
1234 }
1235
1236 rb_atomic_t current_state = RUBY_ATOMIC_LOAD(blocking_operation->status);
1237
1238 switch (current_state) {
1239 case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED:
1240 // Work hasn't started - just mark as cancelled:
1241 if (RUBY_ATOMIC_CAS(blocking_operation->status, current_state, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED) == current_state) {
1242 // Successfully cancelled before execution:
1243 return 0;
1244 }
1245 // Fall through if state changed between load and CAS
1246
1247 case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING:
1248 // Work is running - mark cancelled AND call unblock function
1249 if (RUBY_ATOMIC_CAS(blocking_operation->status, current_state, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED) != current_state) {
1250 // State changed between load and CAS - operation may have completed:
1251 return 0;
1252 }
1253 // Otherwise, we successfully marked it as cancelled, so we can call the unblock function:
1254 rb_unblock_function_t *unblock_function = blocking_operation->unblock_function;
1255 if (unblock_function) {
1256 RUBY_ASSERT(unblock_function != (rb_unblock_function_t *)-1 && "unblock_function is still sentinel value -1, should have been resolved earlier");
1257 blocking_operation->unblock_function(blocking_operation->data2);
1258 }
1259 // Cancelled during execution (unblock function called):
1260 return 1;
1261
1262 case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED:
1263 case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED:
1264 // Already finished or cancelled:
1265 return 0;
1266 }
1267
1268 return 0;
1269}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define RUBY_ATOMIC_CAS(var, oldval, newval)
Atomic compare-and-swap.
Definition atomic.h:165
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
Definition atomic.h:69
#define RUBY_ATOMIC_LOAD(var)
Atomic load.
Definition atomic.h:175
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
VALUE rb_class_new(VALUE super)
Creates a new, anonymous class.
Definition class.c:848
#define Qundef
Old name of RUBY_Qundef.
#define SIZET2NUM
Old name of RB_SIZE2NUM.
Definition size_t.h:62
#define Qnil
Old name of RUBY_Qnil.
VALUE rb_eRuntimeError
RuntimeError exception.
Definition error.c:1461
VALUE rb_cObject
Object class.
Definition object.c:60
VALUE rb_funcall(VALUE recv, ID mid, int n,...)
Calls a method.
Definition vm_eval.c:1123
VALUE rb_funcallv(VALUE recv, ID mid, int argc, const VALUE *argv)
Identical to rb_funcall(), except it takes the method arguments as a C array.
Definition vm_eval.c:1081
VALUE rb_funcall_passing_block_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat)
Identical to rb_funcallv_passing_block(), except you can specify how to handle the last element of th...
Definition vm_eval.c:1193
void rb_unblock_function_t(void *)
This is the type of UBFs.
Definition thread.h:336
int rb_respond_to(VALUE obj, ID mid)
Queries if the object responds to the method.
Definition vm_method.c:3609
VALUE rb_check_funcall(VALUE recv, ID mid, int argc, const VALUE *argv)
Identical to rb_funcallv(), except it returns RUBY_Qundef instead of raising rb_eNoMethodError.
Definition vm_eval.c:691
void rb_define_alloc_func(VALUE klass, rb_alloc_func_t func)
Sets the allocator function of a class.
static ID rb_intern_const(const char *str)
This is a "tiny optimisation" over rb_intern().
Definition symbol.h:285
VALUE rb_io_timeout(VALUE io)
Get the timeout associated with the specified io object.
Definition io.c:861
@ RUBY_IO_READABLE
IO::READABLE
Definition io.h:97
@ RUBY_IO_WRITABLE
IO::WRITABLE
Definition io.h:98
void * rb_nogvl(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void *data2, int flags)
Identical to rb_thread_call_without_gvl(), except it additionally takes "flags" that change the behav...
Definition thread.c:1772
#define RB_UINT2NUM
Just another name of rb_uint2num_inline.
Definition int.h:39
#define RB_INT2NUM
Just another name of rb_int2num_inline.
Definition int.h:37
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
VALUE rb_ensure(type *q, VALUE w, type *e, VALUE r)
An equivalent of ensure clause.
#define OFFT2NUM
Converts a C's off_t into an instance of rb_cInteger.
Definition off_t.h:33
#define PIDT2NUM
Converts a C's pid_t into an instance of rb_cInteger.
Definition pid_t.h:28
#define RUBY_DEFAULT_FREE
This is a value you can set to RData::dfree.
Definition rdata.h:56
#define TypedData_Get_Struct(obj, type, data_type, sval)
Obtains a C struct from inside of a wrapper Ruby object.
Definition rtypeddata.h:773
#define TypedData_Make_Struct(klass, type, data_type, sval)
Identical to TypedData_Wrap_Struct, except it allocates a new data region internally instead of takin...
Definition rtypeddata.h:604
#define errno
Ractor-aware version of errno.
Definition ruby.h:388
Scheduler APIs.
VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void *(*function)(void *), void *data, rb_unblock_function_t *unblock_function, void *data2, int flags, struct rb_fiber_scheduler_blocking_operation_state *state)
Defer the execution of the passed function to the scheduler.
Definition scheduler.c:1121
VALUE rb_fiber_scheduler_current(void)
Identical to rb_fiber_scheduler_get(), except it also returns RUBY_Qnil in case of a blocking fiber.
Definition scheduler.c:459
VALUE rb_fiber_scheduler_make_timeout(struct timeval *timeout)
Converts the passed timeout to an expression that rb_fiber_scheduler_block() etc.
Definition scheduler.c:510
VALUE rb_fiber_scheduler_io_wait_readable(VALUE scheduler, VALUE io)
Non-blocking wait until the passed IO is ready for reading.
Definition scheduler.c:744
VALUE rb_fiber_scheduler_kernel_sleepv(VALUE scheduler, int argc, VALUE *argv)
Identical to rb_fiber_scheduler_kernel_sleep(), except it can pass multiple arguments.
Definition scheduler.c:537
VALUE rb_fiber_scheduler_fiber_interrupt(VALUE scheduler, VALUE fiber, VALUE exception)
Interrupt a fiber by raising an exception.
Definition scheduler.c:1164
VALUE rb_fiber_scheduler_io_wait(VALUE scheduler, VALUE io, VALUE events, VALUE timeout)
Non-blocking version of rb_io_wait().
Definition scheduler.c:734
VALUE rb_fiber_scheduler_io_select(VALUE scheduler, VALUE readables, VALUE writables, VALUE exceptables, VALUE timeout)
Non-blocking version of IO.select.
Definition scheduler.c:765
VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length)
Non-blocking read from the passed IO.
Definition scheduler.c:813
int rb_fiber_scheduler_blocking_operation_cancel(rb_fiber_scheduler_blocking_operation_t *blocking_operation)
Cancel a blocking operation.
Definition scheduler.c:1230
VALUE rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t offset, size_t length)
Non-blocking write to the passed IO at the specified offset.
Definition scheduler.c:924
VALUE rb_fiber_scheduler_io_pread_memory(VALUE scheduler, VALUE io, rb_off_t from, void *base, size_t size)
Non-blocking pread from the passed IO using a native buffer.
Definition scheduler.c:1005
VALUE rb_fiber_scheduler_io_selectv(VALUE scheduler, int argc, VALUE *argv)
Non-blocking version of IO.select, argv variant.
Definition scheduler.c:774
VALUE rb_fiber_scheduler_process_wait(VALUE scheduler, rb_pid_t pid, int flags)
Non-blocking waitpid.
Definition scheduler.c:624
VALUE rb_fiber_scheduler_io_pread(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t offset, size_t length)
Non-blocking read from the passed IO at the specified offset.
Definition scheduler.c:847
VALUE rb_fiber_scheduler_block(VALUE scheduler, VALUE blocker, VALUE timeout)
Non-blocking wait for the passed "blocker", which is for instance Thread.join or Mutex....
Definition scheduler.c:648
VALUE rb_fiber_scheduler_io_read_memory(VALUE scheduler, VALUE io, void *base, size_t size)
Non-blocking read from the passed IO using a native buffer.
Definition scheduler.c:957
int rb_fiber_scheduler_blocking_operation_execute(rb_fiber_scheduler_blocking_operation_t *blocking_operation)
Execute blocking operation from handle (GVL not required).
Definition scheduler.c:195
VALUE rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length)
Non-blocking write to the passed IO.
Definition scheduler.c:890
VALUE rb_fiber_scheduler_close(VALUE scheduler)
Closes the passed scheduler object.
Definition scheduler.c:488
rb_fiber_scheduler_blocking_operation_t * rb_fiber_scheduler_blocking_operation_extract(VALUE self)
Extract the blocking operation handle from a BlockingOperationRuby object.
Definition scheduler.c:180
VALUE rb_fiber_scheduler_current_for_thread(VALUE thread)
Identical to rb_fiber_scheduler_current(), except it queries for that of the passed thread value inst...
Definition scheduler.c:467
VALUE rb_fiber_scheduler_kernel_sleep(VALUE scheduler, VALUE duration)
Non-blocking sleep.
Definition scheduler.c:531
VALUE rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname)
Non-blocking DNS lookup.
Definition scheduler.c:1095
VALUE rb_fiber_scheduler_yield(VALUE scheduler)
Yield to the scheduler, to be resumed on the next scheduling cycle.
Definition scheduler.c:549
VALUE rb_fiber_scheduler_io_pwrite_memory(VALUE scheduler, VALUE io, rb_off_t from, const void *base, size_t size)
Non-blocking pwrite to the passed IO using a native buffer.
Definition scheduler.c:1030
VALUE rb_fiber_scheduler_set(VALUE scheduler)
Destructively assigns the passed scheduler to that of the current thread that is calling this functio...
Definition scheduler.c:421
VALUE rb_fiber_scheduler_current_for_threadptr(struct rb_thread_struct *thread)
Identical to rb_fiber_scheduler_current_for_thread(), except it expects a threadptr instead of a thre...
Definition scheduler.c:472
VALUE rb_fiber_scheduler_io_wait_writable(VALUE scheduler, VALUE io)
Non-blocking wait until the passed IO is ready for writing.
Definition scheduler.c:750
VALUE rb_fiber_scheduler_io_close(VALUE scheduler, VALUE io)
Non-blocking close the given IO.
Definition scheduler.c:1055
VALUE rb_fiber_scheduler_get(void)
Queries the current scheduler of the current thread that is calling this function.
Definition scheduler.c:371
VALUE rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber)
Wakes up a fiber previously blocked using rb_fiber_scheduler_block().
Definition scheduler.c:667
VALUE rb_fiber_scheduler_io_write_memory(VALUE scheduler, VALUE io, const void *base, size_t size)
Non-blocking write to the passed IO using a native buffer.
Definition scheduler.c:981
VALUE rb_fiber_scheduler_fiber(VALUE scheduler, int argc, VALUE *argv, int kw_splat)
Create and schedule a non-blocking fiber.
Definition scheduler.c:1215
@ RUBY_Qundef
Represents so-called undef.
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:238
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40