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