2007-05-24 02:52:19 +04:00
|
|
|
/**********************************************************************
|
|
|
|
|
2009-02-22 17:23:33 +03:00
|
|
|
cont.c -
|
2007-05-24 02:52:19 +04:00
|
|
|
|
|
|
|
$Author$
|
|
|
|
created at: Thu May 23 09:03:43 2007
|
|
|
|
|
|
|
|
Copyright (C) 2007 Koichi Sasada
|
|
|
|
|
|
|
|
**********************************************************************/
|
|
|
|
|
2020-05-08 12:31:09 +03:00
|
|
|
#include "ruby/internal/config.h"
|
2018-11-20 12:59:10 +03:00
|
|
|
|
2010-05-05 22:37:37 +04:00
|
|
|
#ifndef _WIN32
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/mman.h>
|
2010-07-28 04:38:21 +04:00
|
|
|
#endif
|
2019-06-01 08:48:25 +03:00
|
|
|
|
2021-11-29 17:46:48 +03:00
|
|
|
// On Solaris, madvise() is NOT declared for SUS (XPG4v2) or later,
|
|
|
|
// but MADV_* macros are defined when __EXTENSIONS__ is defined.
|
Fix conflicting declaration on Solaris
SunC
```
"cont.c", line 24: identifier redeclared: madvise
current : function(pointer to char, unsigned int, int) returning int
previous: function(pointer to void, unsigned int, int) returning int : "/usr/include/sys/mman.h", line 232
```
GCC
```
cont.c:24:12: error: conflicting types for 'madvise'
24 | extern int madvise(caddr_t, size_t, int);
| ^~~~~~~
In file included from cont.c:16:
/usr/include/sys/mman.h:232:12: note: previous declaration of 'madvise' was here
232 | extern int madvise(void *, size_t, int);
| ^~~~~~~
```
2021-11-30 03:01:36 +03:00
|
|
|
#ifdef NEED_MADVICE_PROTOTYPE_USING_CADDR_T
|
2021-11-29 17:46:48 +03:00
|
|
|
#include <sys/types.h>
|
|
|
|
extern int madvise(caddr_t, size_t, int);
|
|
|
|
#endif
|
|
|
|
|
2019-12-04 11:16:30 +03:00
|
|
|
#include COROUTINE_H
|
|
|
|
|
|
|
|
#include "eval_intern.h"
|
|
|
|
#include "internal.h"
|
|
|
|
#include "internal/cont.h"
|
2023-05-18 17:33:42 +03:00
|
|
|
#include "internal/thread.h"
|
2022-12-20 20:10:57 +03:00
|
|
|
#include "internal/error.h"
|
2023-02-08 14:56:53 +03:00
|
|
|
#include "internal/gc.h"
|
2019-12-04 11:16:30 +03:00
|
|
|
#include "internal/proc.h"
|
2022-09-20 11:07:42 +03:00
|
|
|
#include "internal/sanitizers.h"
|
2019-12-04 11:16:30 +03:00
|
|
|
#include "internal/warnings.h"
|
2021-02-09 09:39:56 +03:00
|
|
|
#include "ruby/fiber/scheduler.h"
|
2023-03-07 10:17:25 +03:00
|
|
|
#include "rjit.h"
|
2022-10-17 19:27:59 +03:00
|
|
|
#include "yjit.h"
|
2019-12-04 11:16:30 +03:00
|
|
|
#include "vm_core.h"
|
2022-10-17 19:27:59 +03:00
|
|
|
#include "vm_sync.h"
|
2020-01-04 03:45:58 +03:00
|
|
|
#include "id_table.h"
|
2020-11-17 10:40:47 +03:00
|
|
|
#include "ractor_core.h"
|
2019-12-04 11:16:30 +03:00
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
static const int DEBUG = 0;
|
|
|
|
|
2010-07-28 04:25:53 +04:00
|
|
|
#define RB_PAGE_SIZE (pagesize)
|
|
|
|
#define RB_PAGE_MASK (~(RB_PAGE_SIZE - 1))
|
2010-05-05 22:37:37 +04:00
|
|
|
static long pagesize;
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
static const rb_data_type_t cont_data_type, fiber_data_type;
|
|
|
|
static VALUE rb_cContinuation;
|
|
|
|
static VALUE rb_cFiber;
|
|
|
|
static VALUE rb_eFiberError;
|
|
|
|
#ifdef RB_EXPERIMENTAL_FIBER_POOL
|
|
|
|
static VALUE rb_cFiberPool;
|
|
|
|
#endif
|
|
|
|
|
2008-11-18 19:19:37 +03:00
|
|
|
#define CAPTURE_JUST_VALID_VM_STACK 1
|
2019-07-16 07:11:55 +03:00
|
|
|
|
|
|
|
// Defined in `coroutine/$arch/Context.h`:
|
|
|
|
#ifdef COROUTINE_LIMITED_ADDRESS_SPACE
|
|
|
|
#define FIBER_POOL_ALLOCATION_FREE
|
|
|
|
#define FIBER_POOL_INITIAL_SIZE 8
|
|
|
|
#define FIBER_POOL_ALLOCATION_MAXIMUM_SIZE 32
|
|
|
|
#else
|
|
|
|
#define FIBER_POOL_INITIAL_SIZE 32
|
2019-07-18 10:02:16 +03:00
|
|
|
#define FIBER_POOL_ALLOCATION_MAXIMUM_SIZE 1024
|
2019-07-16 07:11:55 +03:00
|
|
|
#endif
|
2022-03-16 12:41:00 +03:00
|
|
|
#ifdef RB_EXPERIMENTAL_FIBER_POOL
|
|
|
|
#define FIBER_POOL_ALLOCATION_FREE
|
|
|
|
#endif
|
2008-11-18 19:19:37 +03:00
|
|
|
|
2023-03-09 10:14:33 +03:00
|
|
|
#define jit_cont_enabled (rb_rjit_enabled || rb_yjit_enabled_p())
|
2022-10-17 19:27:59 +03:00
|
|
|
|
2007-11-09 04:11:49 +03:00
|
|
|
enum context_type {
|
|
|
|
CONTINUATION_CONTEXT = 0,
|
2018-09-12 23:49:24 +03:00
|
|
|
FIBER_CONTEXT = 1
|
2007-11-09 04:11:49 +03:00
|
|
|
};
|
|
|
|
|
2017-08-22 03:41:24 +03:00
|
|
|
struct cont_saved_vm_stack {
|
|
|
|
VALUE *ptr;
|
|
|
|
#ifdef CAPTURE_JUST_VALID_VM_STACK
|
2017-10-26 11:42:44 +03:00
|
|
|
size_t slen; /* length of stack (head of ec->vm_stack) */
|
|
|
|
size_t clen; /* length of control frames (tail of ec->vm_stack) */
|
2017-08-22 03:41:24 +03:00
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
struct fiber_pool;
|
|
|
|
|
|
|
|
// Represents a single stack.
|
|
|
|
struct fiber_pool_stack {
|
|
|
|
// A pointer to the memory allocation (lowest address) for the stack.
|
|
|
|
void * base;
|
|
|
|
|
|
|
|
// The current stack pointer, taking into account the direction of the stack.
|
|
|
|
void * current;
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
// The size of the stack excluding any guard pages.
|
2019-06-02 03:49:58 +03:00
|
|
|
size_t size;
|
|
|
|
|
|
|
|
// The available stack capacity w.r.t. the current stack offset.
|
|
|
|
size_t available;
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
// The pool this stack should be allocated from.
|
2019-06-02 03:49:58 +03:00
|
|
|
struct fiber_pool * pool;
|
2019-07-12 04:42:34 +03:00
|
|
|
|
|
|
|
// If the stack is allocated, the allocation it came from.
|
|
|
|
struct fiber_pool_allocation * allocation;
|
2019-06-02 03:49:58 +03:00
|
|
|
};
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
// A linked list of vacant (unused) stacks.
|
2019-06-02 03:49:58 +03:00
|
|
|
// This structure is stored in the first page of a stack if it is not in use.
|
|
|
|
// @sa fiber_pool_vacancy_pointer
|
|
|
|
struct fiber_pool_vacancy {
|
|
|
|
// Details about the vacant stack:
|
|
|
|
struct fiber_pool_stack stack;
|
|
|
|
|
2019-07-16 03:49:14 +03:00
|
|
|
// The vacancy linked list.
|
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-07-12 04:42:34 +03:00
|
|
|
struct fiber_pool_vacancy * previous;
|
2019-07-16 03:49:14 +03:00
|
|
|
#endif
|
2019-06-02 03:49:58 +03:00
|
|
|
struct fiber_pool_vacancy * next;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Manages singly linked list of mapped regions of memory which contains 1 more more stack:
|
|
|
|
//
|
|
|
|
// base = +-------------------------------+-----------------------+ +
|
|
|
|
// |VM Stack |VM Stack | | |
|
|
|
|
// | | | | |
|
|
|
|
// | | | | |
|
|
|
|
// +-------------------------------+ | |
|
|
|
|
// |Machine Stack |Machine Stack | | |
|
|
|
|
// | | | | |
|
|
|
|
// | | | | |
|
|
|
|
// | | | . . . . | | size
|
|
|
|
// | | | | |
|
|
|
|
// | | | | |
|
|
|
|
// | | | | |
|
|
|
|
// | | | | |
|
|
|
|
// | | | | |
|
|
|
|
// +-------------------------------+ | |
|
|
|
|
// |Guard Page |Guard Page | | |
|
|
|
|
// +-------------------------------+-----------------------+ v
|
|
|
|
//
|
|
|
|
// +------------------------------------------------------->
|
|
|
|
//
|
|
|
|
// count
|
|
|
|
//
|
|
|
|
struct fiber_pool_allocation {
|
|
|
|
// A pointer to the memory mapped region.
|
|
|
|
void * base;
|
|
|
|
|
|
|
|
// The size of the individual stacks.
|
|
|
|
size_t size;
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
// The stride of individual stacks (including any guard pages or other accounting details).
|
|
|
|
size_t stride;
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
// The number of stacks that were allocated.
|
|
|
|
size_t count;
|
|
|
|
|
2019-07-16 03:49:14 +03:00
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-06-02 03:49:58 +03:00
|
|
|
// The number of stacks used in this allocation.
|
2019-07-12 04:42:34 +03:00
|
|
|
size_t used;
|
2019-07-16 03:49:14 +03:00
|
|
|
#endif
|
2019-07-12 04:42:34 +03:00
|
|
|
|
|
|
|
struct fiber_pool * pool;
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2019-07-16 03:49:14 +03:00
|
|
|
// The allocation linked list.
|
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-07-12 04:42:34 +03:00
|
|
|
struct fiber_pool_allocation * previous;
|
2019-07-16 03:49:14 +03:00
|
|
|
#endif
|
2019-06-02 03:49:58 +03:00
|
|
|
struct fiber_pool_allocation * next;
|
|
|
|
};
|
|
|
|
|
|
|
|
// A fiber pool manages vacant stacks to reduce the overhead of creating fibers.
|
|
|
|
struct fiber_pool {
|
|
|
|
// A singly-linked list of allocations which contain 1 or more stacks each.
|
|
|
|
struct fiber_pool_allocation * allocations;
|
|
|
|
|
2023-05-25 05:17:49 +03:00
|
|
|
// Free list that provides O(1) stack "allocation".
|
2019-06-02 03:49:58 +03:00
|
|
|
struct fiber_pool_vacancy * vacancies;
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
// The size of the stack allocations (excluding any guard page).
|
2019-06-02 03:49:58 +03:00
|
|
|
size_t size;
|
|
|
|
|
|
|
|
// The total number of stacks that have been allocated in this pool.
|
|
|
|
size_t count;
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
// The initial number of stacks to allocate.
|
|
|
|
size_t initial_count;
|
|
|
|
|
2023-05-25 05:17:49 +03:00
|
|
|
// Whether to madvise(free) the stack or not.
|
|
|
|
// If this value is set to 1, the stack will be madvise(free)ed
|
|
|
|
// (or equivalent), where possible, when it is returned to the pool.
|
2019-07-16 07:00:35 +03:00
|
|
|
int free_stacks;
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
// The number of stacks that have been used in this pool.
|
|
|
|
size_t used;
|
|
|
|
|
2023-05-25 05:17:49 +03:00
|
|
|
// The amount to allocate for the vm_stack.
|
2019-06-02 03:49:58 +03:00
|
|
|
size_t vm_stack_size;
|
|
|
|
};
|
|
|
|
|
2022-10-17 19:27:59 +03:00
|
|
|
// Continuation contexts used by JITs
|
|
|
|
struct rb_jit_cont {
|
|
|
|
rb_execution_context_t *ec; // continuation ec
|
|
|
|
struct rb_jit_cont *prev, *next; // used to form lists
|
|
|
|
};
|
|
|
|
|
|
|
|
// Doubly linked list for enumerating all on-stack ISEQs.
|
|
|
|
static struct rb_jit_cont *first_jit_cont;
|
|
|
|
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
typedef struct rb_context_struct {
|
2008-10-22 19:12:07 +04:00
|
|
|
enum context_type type;
|
2008-12-01 06:00:48 +03:00
|
|
|
int argc;
|
2019-09-26 03:57:00 +03:00
|
|
|
int kw_splat;
|
2014-08-15 04:25:34 +04:00
|
|
|
VALUE self;
|
2007-06-02 11:48:29 +04:00
|
|
|
VALUE value;
|
2017-08-22 03:41:24 +03:00
|
|
|
|
|
|
|
struct cont_saved_vm_stack saved_vm_stack;
|
|
|
|
|
2014-01-28 10:09:58 +04:00
|
|
|
struct {
|
2019-06-01 08:48:25 +03:00
|
|
|
VALUE *stack;
|
|
|
|
VALUE *stack_src;
|
|
|
|
size_t stack_size;
|
2014-01-28 10:09:58 +04:00
|
|
|
} machine;
|
2017-09-10 22:00:08 +03:00
|
|
|
rb_execution_context_t saved_ec;
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
rb_jmpbuf_t jmpbuf;
|
2013-11-15 21:15:31 +04:00
|
|
|
rb_ensure_entry_t *ensure_array;
|
2022-10-17 19:27:59 +03:00
|
|
|
struct rb_jit_cont *jit_cont; // Continuation contexts for JITs
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
} rb_context_t;
|
|
|
|
|
2017-08-10 04:47:13 +03:00
|
|
|
/*
|
|
|
|
* Fiber status:
|
2023-05-18 17:33:42 +03:00
|
|
|
* [Fiber.new] ------> FIBER_CREATED ----> [Fiber#kill] --> |
|
|
|
|
* | [Fiber#resume] |
|
|
|
|
* v |
|
|
|
|
* +--> FIBER_RESUMED ----> [return] ------> |
|
|
|
|
* [Fiber#resume] | | [Fiber.yield/transfer] |
|
|
|
|
* [Fiber#transfer] | v |
|
|
|
|
* +--- FIBER_SUSPENDED --> [Fiber#kill] --> |
|
|
|
|
* |
|
|
|
|
* |
|
|
|
|
* FIBER_TERMINATED <-------------------+
|
2017-08-10 04:47:13 +03:00
|
|
|
*/
|
2008-10-22 19:12:07 +04:00
|
|
|
enum fiber_status {
|
2017-06-26 08:36:10 +03:00
|
|
|
FIBER_CREATED,
|
2017-08-10 04:47:13 +03:00
|
|
|
FIBER_RESUMED,
|
|
|
|
FIBER_SUSPENDED,
|
2017-06-26 08:36:10 +03:00
|
|
|
FIBER_TERMINATED
|
2008-10-22 19:12:07 +04:00
|
|
|
};
|
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
#define FIBER_CREATED_P(fiber) ((fiber)->status == FIBER_CREATED)
|
|
|
|
#define FIBER_RESUMED_P(fiber) ((fiber)->status == FIBER_RESUMED)
|
|
|
|
#define FIBER_SUSPENDED_P(fiber) ((fiber)->status == FIBER_SUSPENDED)
|
|
|
|
#define FIBER_TERMINATED_P(fiber) ((fiber)->status == FIBER_TERMINATED)
|
|
|
|
#define FIBER_RUNNABLE_P(fiber) (FIBER_CREATED_P(fiber) || FIBER_SUSPENDED_P(fiber))
|
2017-08-10 04:47:13 +03:00
|
|
|
|
2014-10-16 04:19:22 +04:00
|
|
|
struct rb_fiber_struct {
|
2008-10-22 19:12:07 +04:00
|
|
|
rb_context_t cont;
|
2017-06-28 18:25:30 +03:00
|
|
|
VALUE first_proc;
|
2014-10-16 02:35:08 +04:00
|
|
|
struct rb_fiber_struct *prev;
|
2021-07-14 04:42:18 +03:00
|
|
|
struct rb_fiber_struct *resuming_fiber;
|
2011-11-09 08:26:39 +04:00
|
|
|
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
BITFIELD(enum fiber_status, status, 2);
|
2020-05-14 13:10:55 +03:00
|
|
|
/* Whether the fiber is allowed to implicitly yield. */
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
unsigned int yielding : 1;
|
2020-05-14 13:10:55 +03:00
|
|
|
unsigned int blocking : 1;
|
|
|
|
|
2023-05-18 17:33:42 +03:00
|
|
|
unsigned int killed : 1;
|
|
|
|
|
2019-06-24 14:54:19 +03:00
|
|
|
struct coroutine_context context;
|
2019-06-02 03:49:58 +03:00
|
|
|
struct fiber_pool_stack stack;
|
2014-10-16 04:17:44 +04:00
|
|
|
};
|
2008-10-22 19:12:07 +04:00
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
static struct fiber_pool shared_fiber_pool = {NULL, NULL, 0, 0, 0, 0};
|
|
|
|
|
2022-12-01 13:00:33 +03:00
|
|
|
static ID fiber_initialize_keywords[3] = {0};
|
2020-05-14 13:10:55 +03:00
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
/*
|
|
|
|
* FreeBSD require a first (i.e. addr) argument of mmap(2) is not NULL
|
|
|
|
* if MAP_STACK is passed.
|
2022-03-14 17:20:30 +03:00
|
|
|
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=158755
|
2019-06-02 03:49:58 +03:00
|
|
|
*/
|
|
|
|
#if defined(MAP_STACK) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__)
|
|
|
|
#define FIBER_STACK_FLAGS (MAP_PRIVATE | MAP_ANON | MAP_STACK)
|
|
|
|
#else
|
|
|
|
#define FIBER_STACK_FLAGS (MAP_PRIVATE | MAP_ANON)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define ERRNOMSG strerror(errno)
|
|
|
|
|
|
|
|
// Locates the stack vacancy details for the given stack.
|
|
|
|
inline static struct fiber_pool_vacancy *
|
|
|
|
fiber_pool_vacancy_pointer(void * base, size_t size)
|
|
|
|
{
|
|
|
|
STACK_GROW_DIR_DETECTION;
|
|
|
|
|
|
|
|
return (struct fiber_pool_vacancy *)(
|
|
|
|
(char*)base + STACK_DIR_UPPER(0, size - RB_PAGE_SIZE)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-05-19 14:03:49 +03:00
|
|
|
#if defined(COROUTINE_SANITIZE_ADDRESS)
|
|
|
|
// Compute the base pointer for a vacant stack, for the area which can be poisoned.
|
|
|
|
inline static void *
|
|
|
|
fiber_pool_stack_poison_base(struct fiber_pool_stack * stack)
|
|
|
|
{
|
|
|
|
STACK_GROW_DIR_DETECTION;
|
|
|
|
|
|
|
|
return (char*)stack->base + STACK_DIR_UPPER(RB_PAGE_SIZE, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute the size of the vacant stack, for the area that can be poisoned.
|
|
|
|
inline static size_t
|
|
|
|
fiber_pool_stack_poison_size(struct fiber_pool_stack * stack)
|
|
|
|
{
|
|
|
|
return stack->size - RB_PAGE_SIZE;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-07-11 10:17:34 +03:00
|
|
|
// Reset the current stack pointer and available size of the given stack.
|
|
|
|
inline static void
|
|
|
|
fiber_pool_stack_reset(struct fiber_pool_stack * stack)
|
|
|
|
{
|
|
|
|
STACK_GROW_DIR_DETECTION;
|
|
|
|
|
|
|
|
stack->current = (char*)stack->base + STACK_DIR_UPPER(0, stack->size);
|
|
|
|
stack->available = stack->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
// A pointer to the base of the current unused portion of the stack.
|
|
|
|
inline static void *
|
|
|
|
fiber_pool_stack_base(struct fiber_pool_stack * stack)
|
|
|
|
{
|
|
|
|
STACK_GROW_DIR_DETECTION;
|
|
|
|
|
2019-07-19 10:42:00 +03:00
|
|
|
VM_ASSERT(stack->current);
|
|
|
|
|
2019-07-11 10:17:34 +03:00
|
|
|
return STACK_DIR_UPPER(stack->current, (char*)stack->current - stack->available);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocate some memory from the stack. Used to allocate vm_stack inline with machine stack.
|
|
|
|
// @sa fiber_initialize_coroutine
|
|
|
|
inline static void *
|
|
|
|
fiber_pool_stack_alloca(struct fiber_pool_stack * stack, size_t offset)
|
|
|
|
{
|
|
|
|
STACK_GROW_DIR_DETECTION;
|
|
|
|
|
Fix clang errors when pendantic errors enabled
I've been compiling with:
```
set -lx cflags '-std=c99 -Werror=pedantic -pedantic-errors'
```
But compilation would fail with the following:
```
cont.c:296:90: error: format specifies type 'void *' but the argument has type 'struct fiber_pool_stack *' [-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_pool_stack_alloca(%p): %"PRIuSIZE"/%"PRIuSIZE"\n", stack, offset, stack->available);
~~ ^~~~~
cont.c:467:24: error: format specifies type 'void *' but the argument has type 'struct fiber_pool *' [-Werror,-Wformat-pedantic]
count, fiber_pool, fiber_pool->used, fiber_pool->count, size, fiber_pool->vm_stack_size);
^~~~~~~~~~
cont.c:588:83: error: format specifies type 'void *' but the argument has type 'struct fiber_pool_vacancy *' [-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_pool_stack_acquire: %p used=%"PRIuSIZE"\n", fiber_pool->vacancies, fiber_pool->used);
~~ ^~~~~~~~~~~~~~~~~~~~~
cont.c:736:76: error: format specifies type 'void *' but the argument has type 'rb_fiber_t *' (aka 'struct rb_fiber_struct *')
[-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_stack_release: %p, stack.base=%p\n", fiber, fiber->stack.base);
```
This commit just fixes the pedantic errors
2019-09-27 00:57:45 +03:00
|
|
|
if (DEBUG) fprintf(stderr, "fiber_pool_stack_alloca(%p): %"PRIuSIZE"/%"PRIuSIZE"\n", (void*)stack, offset, stack->available);
|
2019-07-11 10:17:34 +03:00
|
|
|
VM_ASSERT(stack->available >= offset);
|
|
|
|
|
|
|
|
// The pointer to the memory being allocated:
|
|
|
|
void * pointer = STACK_DIR_UPPER(stack->current, (char*)stack->current - offset);
|
|
|
|
|
|
|
|
// Move the stack pointer:
|
|
|
|
stack->current = STACK_DIR_UPPER((char*)stack->current + offset, (char*)stack->current - offset);
|
|
|
|
stack->available -= offset;
|
|
|
|
|
|
|
|
return pointer;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset the current stack pointer and available size of the given stack.
|
|
|
|
inline static void
|
|
|
|
fiber_pool_vacancy_reset(struct fiber_pool_vacancy * vacancy)
|
|
|
|
{
|
|
|
|
fiber_pool_stack_reset(&vacancy->stack);
|
|
|
|
|
|
|
|
// Consume one page of the stack because it's used for the vacancy list:
|
|
|
|
fiber_pool_stack_alloca(&vacancy->stack, RB_PAGE_SIZE);
|
|
|
|
}
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
inline static struct fiber_pool_vacancy *
|
2019-07-19 00:15:47 +03:00
|
|
|
fiber_pool_vacancy_push(struct fiber_pool_vacancy * vacancy, struct fiber_pool_vacancy * head)
|
|
|
|
{
|
2019-07-12 04:42:34 +03:00
|
|
|
vacancy->next = head;
|
|
|
|
|
2019-07-16 03:49:14 +03:00
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-07-12 04:42:34 +03:00
|
|
|
if (head) {
|
|
|
|
head->previous = vacancy;
|
2020-06-04 13:37:09 +03:00
|
|
|
vacancy->previous = NULL;
|
2019-07-12 04:42:34 +03:00
|
|
|
}
|
2019-07-16 03:49:14 +03:00
|
|
|
#endif
|
2019-07-12 04:42:34 +03:00
|
|
|
|
|
|
|
return vacancy;
|
|
|
|
}
|
|
|
|
|
2019-07-16 03:49:14 +03:00
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-07-12 04:42:34 +03:00
|
|
|
static void
|
2019-07-19 00:15:47 +03:00
|
|
|
fiber_pool_vacancy_remove(struct fiber_pool_vacancy * vacancy)
|
|
|
|
{
|
2019-07-12 04:42:34 +03:00
|
|
|
if (vacancy->next) {
|
|
|
|
vacancy->next->previous = vacancy->previous;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vacancy->previous) {
|
|
|
|
vacancy->previous->next = vacancy->next;
|
2019-07-19 00:15:47 +03:00
|
|
|
}
|
|
|
|
else {
|
2019-07-12 04:42:34 +03:00
|
|
|
// It's the head of the list:
|
|
|
|
vacancy->stack.pool->vacancies = vacancy->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline static struct fiber_pool_vacancy *
|
2019-07-19 00:15:47 +03:00
|
|
|
fiber_pool_vacancy_pop(struct fiber_pool * pool)
|
|
|
|
{
|
2019-07-12 04:42:34 +03:00
|
|
|
struct fiber_pool_vacancy * vacancy = pool->vacancies;
|
|
|
|
|
|
|
|
if (vacancy) {
|
2019-07-19 00:15:47 +03:00
|
|
|
fiber_pool_vacancy_remove(vacancy);
|
2019-07-12 04:42:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return vacancy;
|
|
|
|
}
|
2019-07-16 03:49:14 +03:00
|
|
|
#else
|
|
|
|
inline static struct fiber_pool_vacancy *
|
2019-07-19 00:15:47 +03:00
|
|
|
fiber_pool_vacancy_pop(struct fiber_pool * pool)
|
|
|
|
{
|
2019-07-16 03:49:14 +03:00
|
|
|
struct fiber_pool_vacancy * vacancy = pool->vacancies;
|
|
|
|
|
|
|
|
if (vacancy) {
|
2019-07-19 00:15:47 +03:00
|
|
|
pool->vacancies = vacancy->next;
|
2019-07-16 03:49:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return vacancy;
|
|
|
|
}
|
|
|
|
#endif
|
2019-07-12 04:42:34 +03:00
|
|
|
|
2019-07-11 10:17:34 +03:00
|
|
|
// Initialize the vacant stack. The [base, size] allocation should not include the guard page.
|
|
|
|
// @param base The pointer to the lowest address of the allocated memory.
|
|
|
|
// @param size The size of the allocated memory.
|
|
|
|
inline static struct fiber_pool_vacancy *
|
|
|
|
fiber_pool_vacancy_initialize(struct fiber_pool * fiber_pool, struct fiber_pool_vacancy * vacancies, void * base, size_t size)
|
|
|
|
{
|
|
|
|
struct fiber_pool_vacancy * vacancy = fiber_pool_vacancy_pointer(base, size);
|
|
|
|
|
|
|
|
vacancy->stack.base = base;
|
|
|
|
vacancy->stack.size = size;
|
|
|
|
|
|
|
|
fiber_pool_vacancy_reset(vacancy);
|
|
|
|
|
|
|
|
vacancy->stack.pool = fiber_pool;
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
return fiber_pool_vacancy_push(vacancy, vacancies);
|
2019-07-11 10:17:34 +03:00
|
|
|
}
|
|
|
|
|
2019-07-18 10:02:16 +03:00
|
|
|
// Allocate a maximum of count stacks, size given by stride.
|
|
|
|
// @param count the number of stacks to allocate / were allocated.
|
|
|
|
// @param stride the size of the individual stacks.
|
|
|
|
// @return [void *] the allocated memory or NULL if allocation failed.
|
|
|
|
inline static void *
|
|
|
|
fiber_pool_allocate_memory(size_t * count, size_t stride)
|
|
|
|
{
|
2019-07-19 11:18:42 +03:00
|
|
|
// We use a divide-by-2 strategy to try and allocate memory. We are trying
|
|
|
|
// to allocate `count` stacks. In normal situation, this won't fail. But
|
|
|
|
// if we ran out of address space, or we are allocating more memory than
|
|
|
|
// the system would allow (e.g. overcommit * physical memory + swap), we
|
|
|
|
// divide count by two and try again. This condition should only be
|
|
|
|
// encountered in edge cases, but we handle it here gracefully.
|
2019-07-18 10:02:16 +03:00
|
|
|
while (*count > 1) {
|
|
|
|
#if defined(_WIN32)
|
|
|
|
void * base = VirtualAlloc(0, (*count)*stride, MEM_COMMIT, PAGE_READWRITE);
|
|
|
|
|
|
|
|
if (!base) {
|
|
|
|
*count = (*count) >> 1;
|
2019-07-19 00:15:47 +03:00
|
|
|
}
|
|
|
|
else {
|
2019-07-18 10:02:16 +03:00
|
|
|
return base;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
errno = 0;
|
|
|
|
void * base = mmap(NULL, (*count)*stride, PROT_READ | PROT_WRITE, FIBER_STACK_FLAGS, -1, 0);
|
|
|
|
|
|
|
|
if (base == MAP_FAILED) {
|
2019-07-19 11:18:42 +03:00
|
|
|
// If the allocation fails, count = count / 2, and try again.
|
2019-07-18 10:02:16 +03:00
|
|
|
*count = (*count) >> 1;
|
2019-07-19 00:15:47 +03:00
|
|
|
}
|
|
|
|
else {
|
2021-03-23 21:39:36 +03:00
|
|
|
#if defined(MADV_FREE_REUSE)
|
|
|
|
// On Mac MADV_FREE_REUSE is necessary for the task_info api
|
|
|
|
// to keep the accounting accurate as possible when a page is marked as reusable
|
2021-04-25 18:10:39 +03:00
|
|
|
// it can possibly not occurring at first call thus re-iterating if necessary.
|
2021-03-23 21:39:36 +03:00
|
|
|
while (madvise(base, (*count)*stride, MADV_FREE_REUSE) == -1 && errno == EAGAIN);
|
|
|
|
#endif
|
2019-07-18 10:02:16 +03:00
|
|
|
return base;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
// Given an existing fiber pool, expand it by the specified number of stacks.
|
2019-07-18 10:02:16 +03:00
|
|
|
// @param count the maximum number of stacks to allocate.
|
|
|
|
// @return the allocated fiber pool.
|
|
|
|
// @sa fiber_pool_allocation_free
|
2019-06-02 03:49:58 +03:00
|
|
|
static struct fiber_pool_allocation *
|
|
|
|
fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count)
|
|
|
|
{
|
2019-07-11 10:17:34 +03:00
|
|
|
STACK_GROW_DIR_DETECTION;
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
size_t size = fiber_pool->size;
|
2019-07-11 10:17:34 +03:00
|
|
|
size_t stride = size + RB_PAGE_SIZE;
|
|
|
|
|
2019-07-18 10:02:16 +03:00
|
|
|
// Allocate the memory required for the stacks:
|
|
|
|
void * base = fiber_pool_allocate_memory(&count, stride);
|
|
|
|
|
|
|
|
if (base == NULL) {
|
2019-07-24 23:54:38 +03:00
|
|
|
rb_raise(rb_eFiberError, "can't alloc machine stack to fiber (%"PRIuSIZE" x %"PRIuSIZE" bytes): %s", count, size, ERRNOMSG);
|
2019-07-18 10:02:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
struct fiber_pool_vacancy * vacancies = fiber_pool->vacancies;
|
|
|
|
struct fiber_pool_allocation * allocation = RB_ALLOC(struct fiber_pool_allocation);
|
|
|
|
|
2019-07-11 10:17:34 +03:00
|
|
|
// Initialize fiber pool allocation:
|
2019-07-18 10:02:16 +03:00
|
|
|
allocation->base = base;
|
2019-06-02 03:49:58 +03:00
|
|
|
allocation->size = size;
|
2019-07-12 04:42:34 +03:00
|
|
|
allocation->stride = stride;
|
2019-06-02 03:49:58 +03:00
|
|
|
allocation->count = count;
|
2019-07-16 03:49:14 +03:00
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-07-12 04:42:34 +03:00
|
|
|
allocation->used = 0;
|
2019-07-16 03:49:14 +03:00
|
|
|
#endif
|
2019-07-12 04:42:34 +03:00
|
|
|
allocation->pool = fiber_pool;
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2019-07-24 23:54:38 +03:00
|
|
|
if (DEBUG) {
|
|
|
|
fprintf(stderr, "fiber_pool_expand(%"PRIuSIZE"): %p, %"PRIuSIZE"/%"PRIuSIZE" x [%"PRIuSIZE":%"PRIuSIZE"]\n",
|
Fix clang errors when pendantic errors enabled
I've been compiling with:
```
set -lx cflags '-std=c99 -Werror=pedantic -pedantic-errors'
```
But compilation would fail with the following:
```
cont.c:296:90: error: format specifies type 'void *' but the argument has type 'struct fiber_pool_stack *' [-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_pool_stack_alloca(%p): %"PRIuSIZE"/%"PRIuSIZE"\n", stack, offset, stack->available);
~~ ^~~~~
cont.c:467:24: error: format specifies type 'void *' but the argument has type 'struct fiber_pool *' [-Werror,-Wformat-pedantic]
count, fiber_pool, fiber_pool->used, fiber_pool->count, size, fiber_pool->vm_stack_size);
^~~~~~~~~~
cont.c:588:83: error: format specifies type 'void *' but the argument has type 'struct fiber_pool_vacancy *' [-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_pool_stack_acquire: %p used=%"PRIuSIZE"\n", fiber_pool->vacancies, fiber_pool->used);
~~ ^~~~~~~~~~~~~~~~~~~~~
cont.c:736:76: error: format specifies type 'void *' but the argument has type 'rb_fiber_t *' (aka 'struct rb_fiber_struct *')
[-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_stack_release: %p, stack.base=%p\n", fiber, fiber->stack.base);
```
This commit just fixes the pedantic errors
2019-09-27 00:57:45 +03:00
|
|
|
count, (void*)fiber_pool, fiber_pool->used, fiber_pool->count, size, fiber_pool->vm_stack_size);
|
2019-07-24 23:54:38 +03:00
|
|
|
}
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2019-07-11 10:17:34 +03:00
|
|
|
// Iterate over all stacks, initializing the vacancy list:
|
2019-07-18 10:02:16 +03:00
|
|
|
for (size_t i = 0; i < count; i += 1) {
|
2019-07-11 10:17:34 +03:00
|
|
|
void * base = (char*)allocation->base + (stride * i);
|
|
|
|
void * page = (char*)base + STACK_DIR_UPPER(size, 0);
|
|
|
|
|
|
|
|
#if defined(_WIN32)
|
|
|
|
DWORD old_protect;
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2019-07-11 10:17:34 +03:00
|
|
|
if (!VirtualProtect(page, RB_PAGE_SIZE, PAGE_READWRITE | PAGE_GUARD, &old_protect)) {
|
|
|
|
VirtualFree(allocation->base, 0, MEM_RELEASE);
|
|
|
|
rb_raise(rb_eFiberError, "can't set a guard page: %s", ERRNOMSG);
|
|
|
|
}
|
|
|
|
#else
|
2019-06-02 03:49:58 +03:00
|
|
|
if (mprotect(page, RB_PAGE_SIZE, PROT_NONE) < 0) {
|
2019-07-11 10:17:34 +03:00
|
|
|
munmap(allocation->base, count*stride);
|
2019-06-02 03:49:58 +03:00
|
|
|
rb_raise(rb_eFiberError, "can't set a guard page: %s", ERRNOMSG);
|
|
|
|
}
|
2019-07-11 10:17:34 +03:00
|
|
|
#endif
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2019-07-11 10:17:34 +03:00
|
|
|
vacancies = fiber_pool_vacancy_initialize(
|
2019-07-19 00:15:47 +03:00
|
|
|
fiber_pool, vacancies,
|
|
|
|
(char*)base + STACK_DIR_UPPER(0, RB_PAGE_SIZE),
|
|
|
|
size
|
2019-07-11 10:17:34 +03:00
|
|
|
);
|
2019-07-12 04:42:34 +03:00
|
|
|
|
2019-07-16 03:49:14 +03:00
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-07-12 04:42:34 +03:00
|
|
|
vacancies->stack.allocation = allocation;
|
2019-07-16 03:49:14 +03:00
|
|
|
#endif
|
2019-06-02 03:49:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Insert the allocation into the head of the pool:
|
|
|
|
allocation->next = fiber_pool->allocations;
|
2019-07-12 04:42:34 +03:00
|
|
|
|
2019-07-16 03:49:14 +03:00
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-07-12 04:42:34 +03:00
|
|
|
if (allocation->next) {
|
|
|
|
allocation->next->previous = allocation;
|
|
|
|
}
|
|
|
|
|
|
|
|
allocation->previous = NULL;
|
2019-07-16 03:49:14 +03:00
|
|
|
#endif
|
2019-07-12 04:42:34 +03:00
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
fiber_pool->allocations = allocation;
|
|
|
|
fiber_pool->vacancies = vacancies;
|
|
|
|
fiber_pool->count += count;
|
|
|
|
|
|
|
|
return allocation;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the specified fiber pool with the given number of stacks.
|
|
|
|
// @param vm_stack_size The size of the vm stack to allocate.
|
|
|
|
static void
|
|
|
|
fiber_pool_initialize(struct fiber_pool * fiber_pool, size_t size, size_t count, size_t vm_stack_size)
|
|
|
|
{
|
|
|
|
VM_ASSERT(vm_stack_size < size);
|
|
|
|
|
|
|
|
fiber_pool->allocations = NULL;
|
|
|
|
fiber_pool->vacancies = NULL;
|
|
|
|
fiber_pool->size = ((size / RB_PAGE_SIZE) + 1) * RB_PAGE_SIZE;
|
2019-07-12 04:42:34 +03:00
|
|
|
fiber_pool->count = 0;
|
|
|
|
fiber_pool->initial_count = count;
|
2019-07-16 07:00:35 +03:00
|
|
|
fiber_pool->free_stacks = 1;
|
2019-06-02 03:49:58 +03:00
|
|
|
fiber_pool->used = 0;
|
|
|
|
|
|
|
|
fiber_pool->vm_stack_size = vm_stack_size;
|
|
|
|
|
|
|
|
fiber_pool_expand(fiber_pool, count);
|
|
|
|
}
|
|
|
|
|
2019-07-16 03:49:14 +03:00
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-06-02 03:49:58 +03:00
|
|
|
// Free the list of fiber pool allocations.
|
|
|
|
static void
|
2019-07-12 04:42:34 +03:00
|
|
|
fiber_pool_allocation_free(struct fiber_pool_allocation * allocation)
|
2019-06-02 03:49:58 +03:00
|
|
|
{
|
2019-07-12 04:42:34 +03:00
|
|
|
STACK_GROW_DIR_DETECTION;
|
|
|
|
|
|
|
|
VM_ASSERT(allocation->used == 0);
|
|
|
|
|
2021-09-02 02:26:46 +03:00
|
|
|
if (DEBUG) fprintf(stderr, "fiber_pool_allocation_free: %p base=%p count=%"PRIuSIZE"\n", (void*)allocation, allocation->base, allocation->count);
|
2019-07-12 04:42:34 +03:00
|
|
|
|
|
|
|
size_t i;
|
|
|
|
for (i = 0; i < allocation->count; i += 1) {
|
|
|
|
void * base = (char*)allocation->base + (allocation->stride * i) + STACK_DIR_UPPER(0, RB_PAGE_SIZE);
|
|
|
|
|
|
|
|
struct fiber_pool_vacancy * vacancy = fiber_pool_vacancy_pointer(base, allocation->size);
|
|
|
|
|
|
|
|
// Pop the vacant stack off the free list:
|
|
|
|
fiber_pool_vacancy_remove(vacancy);
|
|
|
|
}
|
2019-06-02 03:49:58 +03:00
|
|
|
|
|
|
|
#ifdef _WIN32
|
2019-07-19 00:15:47 +03:00
|
|
|
VirtualFree(allocation->base, 0, MEM_RELEASE);
|
2019-06-02 03:49:58 +03:00
|
|
|
#else
|
2019-07-19 00:15:47 +03:00
|
|
|
munmap(allocation->base, allocation->stride * allocation->count);
|
2019-06-02 03:49:58 +03:00
|
|
|
#endif
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
if (allocation->previous) {
|
|
|
|
allocation->previous->next = allocation->next;
|
2019-07-19 00:15:47 +03:00
|
|
|
}
|
|
|
|
else {
|
2019-07-12 04:42:34 +03:00
|
|
|
// We are the head of the list, so update the pool:
|
|
|
|
allocation->pool->allocations = allocation->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (allocation->next) {
|
|
|
|
allocation->next->previous = allocation->previous;
|
2019-06-02 03:49:58 +03:00
|
|
|
}
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
allocation->pool->count -= allocation->count;
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
ruby_xfree(allocation);
|
|
|
|
}
|
2019-07-16 03:49:14 +03:00
|
|
|
#endif
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2019-08-12 19:03:14 +03:00
|
|
|
// Acquire a stack from the given fiber pool. If none are available, allocate more.
|
2019-06-02 03:49:58 +03:00
|
|
|
static struct fiber_pool_stack
|
2020-12-28 13:52:14 +03:00
|
|
|
fiber_pool_stack_acquire(struct fiber_pool * fiber_pool)
|
|
|
|
{
|
2019-07-12 04:42:34 +03:00
|
|
|
struct fiber_pool_vacancy * vacancy = fiber_pool_vacancy_pop(fiber_pool);
|
2019-06-02 03:49:58 +03:00
|
|
|
|
Fix clang errors when pendantic errors enabled
I've been compiling with:
```
set -lx cflags '-std=c99 -Werror=pedantic -pedantic-errors'
```
But compilation would fail with the following:
```
cont.c:296:90: error: format specifies type 'void *' but the argument has type 'struct fiber_pool_stack *' [-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_pool_stack_alloca(%p): %"PRIuSIZE"/%"PRIuSIZE"\n", stack, offset, stack->available);
~~ ^~~~~
cont.c:467:24: error: format specifies type 'void *' but the argument has type 'struct fiber_pool *' [-Werror,-Wformat-pedantic]
count, fiber_pool, fiber_pool->used, fiber_pool->count, size, fiber_pool->vm_stack_size);
^~~~~~~~~~
cont.c:588:83: error: format specifies type 'void *' but the argument has type 'struct fiber_pool_vacancy *' [-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_pool_stack_acquire: %p used=%"PRIuSIZE"\n", fiber_pool->vacancies, fiber_pool->used);
~~ ^~~~~~~~~~~~~~~~~~~~~
cont.c:736:76: error: format specifies type 'void *' but the argument has type 'rb_fiber_t *' (aka 'struct rb_fiber_struct *')
[-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_stack_release: %p, stack.base=%p\n", fiber, fiber->stack.base);
```
This commit just fixes the pedantic errors
2019-09-27 00:57:45 +03:00
|
|
|
if (DEBUG) fprintf(stderr, "fiber_pool_stack_acquire: %p used=%"PRIuSIZE"\n", (void*)fiber_pool->vacancies, fiber_pool->used);
|
2019-06-02 03:49:58 +03:00
|
|
|
|
|
|
|
if (!vacancy) {
|
2019-07-16 07:11:55 +03:00
|
|
|
const size_t maximum = FIBER_POOL_ALLOCATION_MAXIMUM_SIZE;
|
2019-07-12 04:42:34 +03:00
|
|
|
const size_t minimum = fiber_pool->initial_count;
|
2019-07-18 11:55:22 +03:00
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
size_t count = fiber_pool->count;
|
2019-07-12 04:42:34 +03:00
|
|
|
if (count > maximum) count = maximum;
|
|
|
|
if (count < minimum) count = minimum;
|
2019-06-02 03:49:58 +03:00
|
|
|
|
|
|
|
fiber_pool_expand(fiber_pool, count);
|
|
|
|
|
|
|
|
// The free list should now contain some stacks:
|
|
|
|
VM_ASSERT(fiber_pool->vacancies);
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
vacancy = fiber_pool_vacancy_pop(fiber_pool);
|
2019-06-02 03:49:58 +03:00
|
|
|
}
|
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
VM_ASSERT(vacancy);
|
2019-07-19 10:42:00 +03:00
|
|
|
VM_ASSERT(vacancy->stack.base);
|
2019-07-12 04:42:34 +03:00
|
|
|
|
2022-05-19 14:03:49 +03:00
|
|
|
#if defined(COROUTINE_SANITIZE_ADDRESS)
|
|
|
|
__asan_unpoison_memory_region(fiber_pool_stack_poison_base(&vacancy->stack), fiber_pool_stack_poison_size(&vacancy->stack));
|
|
|
|
#endif
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
// Take the top item from the free list:
|
|
|
|
fiber_pool->used += 1;
|
|
|
|
|
2019-07-16 03:49:14 +03:00
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-07-12 04:42:34 +03:00
|
|
|
vacancy->stack.allocation->used += 1;
|
2019-07-16 03:49:14 +03:00
|
|
|
#endif
|
2019-07-12 04:42:34 +03:00
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
fiber_pool_stack_reset(&vacancy->stack);
|
|
|
|
|
|
|
|
return vacancy->stack;
|
|
|
|
}
|
|
|
|
|
2019-07-11 10:17:34 +03:00
|
|
|
// We advise the operating system that the stack memory pages are no longer being used.
|
|
|
|
// This introduce some performance overhead but allows system to relaim memory when there is pressure.
|
|
|
|
static inline void
|
2019-07-19 00:15:47 +03:00
|
|
|
fiber_pool_stack_free(struct fiber_pool_stack * stack)
|
|
|
|
{
|
2019-07-11 10:17:34 +03:00
|
|
|
void * base = fiber_pool_stack_base(stack);
|
2019-07-12 04:42:34 +03:00
|
|
|
size_t size = stack->available;
|
2019-07-11 10:17:34 +03:00
|
|
|
|
2019-07-19 11:09:03 +03:00
|
|
|
// If this is not true, the vacancy information will almost certainly be destroyed:
|
2019-07-19 10:42:00 +03:00
|
|
|
VM_ASSERT(size <= (stack->size - RB_PAGE_SIZE));
|
|
|
|
|
2023-05-25 05:17:49 +03:00
|
|
|
int advice = stack->pool->free_stacks >> 1;
|
|
|
|
|
|
|
|
if (DEBUG) fprintf(stderr, "fiber_pool_stack_free: %p+%"PRIuSIZE" [base=%p, size=%"PRIuSIZE"] advice=%d\n", base, size, stack->base, stack->size, advice);
|
2019-07-11 10:17:34 +03:00
|
|
|
|
2022-11-04 10:13:48 +03:00
|
|
|
// The pages being used by the stack can be returned back to the system.
|
|
|
|
// That doesn't change the page mapping, but it does allow the system to
|
|
|
|
// reclaim the physical memory.
|
|
|
|
// Since we no longer care about the data itself, we don't need to page
|
|
|
|
// out to disk, since that is costly. Not all systems support that, so
|
|
|
|
// we try our best to select the most efficient implementation.
|
|
|
|
// In addition, it's actually slightly desirable to not do anything here,
|
|
|
|
// but that results in higher memory usage.
|
|
|
|
|
|
|
|
#ifdef __wasi__
|
|
|
|
// WebAssembly doesn't support madvise, so we just don't do anything.
|
|
|
|
#elif VM_CHECK_MODE > 0 && defined(MADV_DONTNEED)
|
2023-05-25 05:17:49 +03:00
|
|
|
if (!advice) advice = MADV_DONTNEED;
|
2019-07-19 10:42:00 +03:00
|
|
|
// This immediately discards the pages and the memory is reset to zero.
|
2023-05-25 05:17:49 +03:00
|
|
|
madvise(base, size, advice);
|
2019-07-19 10:42:00 +03:00
|
|
|
#elif defined(MADV_FREE_REUSABLE)
|
2023-05-25 05:17:49 +03:00
|
|
|
if (!advice) advice = MADV_FREE_REUSABLE;
|
2022-11-04 10:13:48 +03:00
|
|
|
// Darwin / macOS / iOS.
|
2021-03-23 21:39:36 +03:00
|
|
|
// Acknowledge the kernel down to the task info api we make this
|
|
|
|
// page reusable for future use.
|
2023-05-25 05:17:49 +03:00
|
|
|
// As for MADV_FREE_REUSABLE below we ensure in the rare occasions the task was not
|
2021-03-23 21:39:36 +03:00
|
|
|
// completed at the time of the call to re-iterate.
|
2023-05-25 05:17:49 +03:00
|
|
|
while (madvise(base, size, advice) == -1 && errno == EAGAIN);
|
2019-07-11 10:17:34 +03:00
|
|
|
#elif defined(MADV_FREE)
|
2023-05-25 05:17:49 +03:00
|
|
|
if (!advice) advice = MADV_FREE;
|
2022-11-04 10:13:48 +03:00
|
|
|
// Recent Linux.
|
2023-05-25 05:17:49 +03:00
|
|
|
madvise(base, size, advice);
|
2019-07-11 10:17:34 +03:00
|
|
|
#elif defined(MADV_DONTNEED)
|
2023-05-25 05:17:49 +03:00
|
|
|
if (!advice) advice = MADV_DONTNEED;
|
2022-11-04 10:13:48 +03:00
|
|
|
// Old Linux.
|
2023-05-25 05:17:49 +03:00
|
|
|
madvise(base, size, advice);
|
2022-11-04 10:13:48 +03:00
|
|
|
#elif defined(POSIX_MADV_DONTNEED)
|
2023-05-25 05:17:49 +03:00
|
|
|
if (!advice) advice = POSIX_MADV_DONTNEED;
|
2022-11-04 10:13:48 +03:00
|
|
|
// Solaris?
|
2023-05-25 05:17:49 +03:00
|
|
|
posix_madvise(base, size, advice);
|
2019-07-11 10:17:34 +03:00
|
|
|
#elif defined(_WIN32)
|
|
|
|
VirtualAlloc(base, size, MEM_RESET, PAGE_READWRITE);
|
|
|
|
// Not available in all versions of Windows.
|
|
|
|
//DiscardVirtualMemory(base, size);
|
|
|
|
#endif
|
2022-05-19 14:03:49 +03:00
|
|
|
|
|
|
|
#if defined(COROUTINE_SANITIZE_ADDRESS)
|
|
|
|
__asan_poison_memory_region(fiber_pool_stack_poison_base(stack), fiber_pool_stack_poison_size(stack));
|
|
|
|
#endif
|
2019-07-11 10:17:34 +03:00
|
|
|
}
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
// Release and return a stack to the vacancy list.
|
|
|
|
static void
|
2019-07-19 00:15:47 +03:00
|
|
|
fiber_pool_stack_release(struct fiber_pool_stack * stack)
|
|
|
|
{
|
2019-07-19 10:42:00 +03:00
|
|
|
struct fiber_pool * pool = stack->pool;
|
2019-07-12 04:42:34 +03:00
|
|
|
struct fiber_pool_vacancy * vacancy = fiber_pool_vacancy_pointer(stack->base, stack->size);
|
|
|
|
|
2019-07-24 23:54:38 +03:00
|
|
|
if (DEBUG) fprintf(stderr, "fiber_pool_stack_release: %p used=%"PRIuSIZE"\n", stack->base, stack->pool->used);
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2019-07-11 10:17:34 +03:00
|
|
|
// Copy the stack details into the vacancy area:
|
2019-07-12 04:42:34 +03:00
|
|
|
vacancy->stack = *stack;
|
2019-07-19 10:42:00 +03:00
|
|
|
// After this point, be careful about updating/using state in stack, since it's copied to the vacancy area.
|
2019-07-11 10:17:34 +03:00
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
// Reset the stack pointers and reserve space for the vacancy data:
|
|
|
|
fiber_pool_vacancy_reset(vacancy);
|
2019-07-11 10:17:34 +03:00
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
// Push the vacancy into the vancancies list:
|
2022-05-19 14:03:49 +03:00
|
|
|
pool->vacancies = fiber_pool_vacancy_push(vacancy, pool->vacancies);
|
2019-07-19 10:42:00 +03:00
|
|
|
pool->used -= 1;
|
2019-07-16 03:49:14 +03:00
|
|
|
|
|
|
|
#ifdef FIBER_POOL_ALLOCATION_FREE
|
2019-07-19 11:09:03 +03:00
|
|
|
struct fiber_pool_allocation * allocation = stack->allocation;
|
2019-07-19 10:42:00 +03:00
|
|
|
|
2019-07-29 17:03:59 +03:00
|
|
|
allocation->used -= 1;
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
// Release address space and/or dirty memory:
|
2019-07-29 17:03:59 +03:00
|
|
|
if (allocation->used == 0) {
|
|
|
|
fiber_pool_allocation_free(allocation);
|
2019-07-19 00:15:47 +03:00
|
|
|
}
|
|
|
|
else if (stack->pool->free_stacks) {
|
2019-07-19 10:42:00 +03:00
|
|
|
fiber_pool_stack_free(&vacancy->stack);
|
2019-07-16 07:00:35 +03:00
|
|
|
}
|
|
|
|
#else
|
2022-05-25 09:13:04 +03:00
|
|
|
// This is entirely optional, but clears the dirty flag from the stack
|
|
|
|
// memory, so it won't get swapped to disk when there is memory pressure:
|
2019-07-16 07:00:35 +03:00
|
|
|
if (stack->pool->free_stacks) {
|
2019-07-19 10:42:00 +03:00
|
|
|
fiber_pool_stack_free(&vacancy->stack);
|
2019-07-12 04:42:34 +03:00
|
|
|
}
|
2019-07-16 03:49:14 +03:00
|
|
|
#endif
|
2019-06-02 03:49:58 +03:00
|
|
|
}
|
|
|
|
|
2021-06-26 01:17:26 +03:00
|
|
|
static inline void
|
|
|
|
ec_switch(rb_thread_t *th, rb_fiber_t *fiber)
|
|
|
|
{
|
|
|
|
rb_execution_context_t *ec = &fiber->cont.saved_ec;
|
|
|
|
rb_ractor_set_current_ec(th->ractor, th->ec = ec);
|
|
|
|
// ruby_current_execution_context_ptr = th->ec = ec;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* timer-thread may set trap interrupt on previous th->ec at any time;
|
|
|
|
* ensure we do not delay (or lose) the trap interrupt handling.
|
|
|
|
*/
|
|
|
|
if (th->vm->ractor.main_thread == th &&
|
|
|
|
rb_signal_buff_size() > 0) {
|
|
|
|
RUBY_VM_SET_TRAP_INTERRUPT(ec);
|
|
|
|
}
|
|
|
|
|
|
|
|
VM_ASSERT(ec->fiber_ptr->cont.self == 0 || ec->vm_stack != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
fiber_restore_thread(rb_thread_t *th, rb_fiber_t *fiber)
|
|
|
|
{
|
|
|
|
ec_switch(th, fiber);
|
|
|
|
VM_ASSERT(th->ec->fiber_ptr == fiber);
|
|
|
|
}
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
static COROUTINE
|
|
|
|
fiber_entry(struct coroutine_context * from, struct coroutine_context * to)
|
|
|
|
{
|
2021-06-26 01:17:26 +03:00
|
|
|
rb_fiber_t *fiber = to->argument;
|
2022-05-19 14:03:49 +03:00
|
|
|
|
|
|
|
#if defined(COROUTINE_SANITIZE_ADDRESS)
|
2022-05-25 09:13:04 +03:00
|
|
|
// Address sanitizer will copy the previous stack base and stack size into
|
|
|
|
// the "from" fiber. `coroutine_initialize_main` doesn't generally know the
|
|
|
|
// stack bounds (base + size). Therefore, the main fiber `stack_base` and
|
|
|
|
// `stack_size` will be NULL/0. It's specifically important in that case to
|
|
|
|
// get the (base+size) of the previous fiber and save it, so that later when
|
|
|
|
// we return to the main coroutine, we don't supply (NULL, 0) to
|
|
|
|
// __sanitizer_start_switch_fiber which royally messes up the internal state
|
|
|
|
// of ASAN and causes (sometimes) the following message:
|
|
|
|
// "WARNING: ASan is ignoring requested __asan_handle_no_return"
|
|
|
|
__sanitizer_finish_switch_fiber(to->fake_stack, (const void**)&from->stack_base, &from->stack_size);
|
2022-05-19 14:03:49 +03:00
|
|
|
#endif
|
|
|
|
|
2021-06-26 01:17:26 +03:00
|
|
|
rb_thread_t *thread = fiber->cont.saved_ec.thread_ptr;
|
|
|
|
|
|
|
|
#ifdef COROUTINE_PTHREAD_CONTEXT
|
|
|
|
ruby_thread_set_native(thread);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
fiber_restore_thread(thread, fiber);
|
|
|
|
|
|
|
|
rb_fiber_start(fiber);
|
|
|
|
|
|
|
|
#ifndef COROUTINE_PTHREAD_CONTEXT
|
|
|
|
VM_UNREACHABLE(fiber_entry);
|
|
|
|
#endif
|
2019-06-02 03:49:58 +03:00
|
|
|
}
|
|
|
|
|
2019-06-27 09:59:25 +03:00
|
|
|
// Initialize a fiber's coroutine's machine stack and vm stack.
|
2019-06-02 03:49:58 +03:00
|
|
|
static VALUE *
|
2019-06-27 09:59:25 +03:00
|
|
|
fiber_initialize_coroutine(rb_fiber_t *fiber, size_t * vm_stack_size)
|
2019-06-02 03:49:58 +03:00
|
|
|
{
|
|
|
|
struct fiber_pool * fiber_pool = fiber->stack.pool;
|
|
|
|
rb_execution_context_t *sec = &fiber->cont.saved_ec;
|
|
|
|
void * vm_stack = NULL;
|
|
|
|
|
|
|
|
VM_ASSERT(fiber_pool != NULL);
|
|
|
|
|
|
|
|
fiber->stack = fiber_pool_stack_acquire(fiber_pool);
|
|
|
|
vm_stack = fiber_pool_stack_alloca(&fiber->stack, fiber_pool->vm_stack_size);
|
|
|
|
*vm_stack_size = fiber_pool->vm_stack_size;
|
|
|
|
|
|
|
|
coroutine_initialize(&fiber->context, fiber_entry, fiber_pool_stack_base(&fiber->stack), fiber->stack.available);
|
|
|
|
|
2019-06-27 09:59:25 +03:00
|
|
|
// The stack for this execution context is the one we allocated:
|
2019-06-02 03:49:58 +03:00
|
|
|
sec->machine.stack_start = fiber->stack.current;
|
|
|
|
sec->machine.stack_maxsize = fiber->stack.available;
|
2021-06-26 01:17:26 +03:00
|
|
|
|
|
|
|
fiber->context.argument = (void*)fiber;
|
2019-06-02 03:49:58 +03:00
|
|
|
|
|
|
|
return vm_stack;
|
|
|
|
}
|
2018-11-20 13:18:12 +03:00
|
|
|
|
2022-05-25 09:13:04 +03:00
|
|
|
// Release the stack from the fiber, it's execution context, and return it to
|
|
|
|
// the fiber pool.
|
2019-07-11 10:17:34 +03:00
|
|
|
static void
|
|
|
|
fiber_stack_release(rb_fiber_t * fiber)
|
|
|
|
{
|
|
|
|
rb_execution_context_t *ec = &fiber->cont.saved_ec;
|
|
|
|
|
Fix clang errors when pendantic errors enabled
I've been compiling with:
```
set -lx cflags '-std=c99 -Werror=pedantic -pedantic-errors'
```
But compilation would fail with the following:
```
cont.c:296:90: error: format specifies type 'void *' but the argument has type 'struct fiber_pool_stack *' [-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_pool_stack_alloca(%p): %"PRIuSIZE"/%"PRIuSIZE"\n", stack, offset, stack->available);
~~ ^~~~~
cont.c:467:24: error: format specifies type 'void *' but the argument has type 'struct fiber_pool *' [-Werror,-Wformat-pedantic]
count, fiber_pool, fiber_pool->used, fiber_pool->count, size, fiber_pool->vm_stack_size);
^~~~~~~~~~
cont.c:588:83: error: format specifies type 'void *' but the argument has type 'struct fiber_pool_vacancy *' [-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_pool_stack_acquire: %p used=%"PRIuSIZE"\n", fiber_pool->vacancies, fiber_pool->used);
~~ ^~~~~~~~~~~~~~~~~~~~~
cont.c:736:76: error: format specifies type 'void *' but the argument has type 'rb_fiber_t *' (aka 'struct rb_fiber_struct *')
[-Werror,-Wformat-pedantic]
if (DEBUG) fprintf(stderr, "fiber_stack_release: %p, stack.base=%p\n", fiber, fiber->stack.base);
```
This commit just fixes the pedantic errors
2019-09-27 00:57:45 +03:00
|
|
|
if (DEBUG) fprintf(stderr, "fiber_stack_release: %p, stack.base=%p\n", (void*)fiber, fiber->stack.base);
|
2019-07-12 04:42:34 +03:00
|
|
|
|
|
|
|
// Return the stack back to the fiber pool if it wasn't already:
|
2019-07-11 10:17:34 +03:00
|
|
|
if (fiber->stack.base) {
|
2019-07-12 04:42:34 +03:00
|
|
|
fiber_pool_stack_release(&fiber->stack);
|
2019-07-11 10:17:34 +03:00
|
|
|
fiber->stack.base = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The stack is no longer associated with this execution context:
|
|
|
|
rb_ec_clear_vm_stack(ec);
|
|
|
|
}
|
|
|
|
|
2017-08-10 04:47:13 +03:00
|
|
|
static const char *
|
|
|
|
fiber_status_name(enum fiber_status s)
|
|
|
|
{
|
|
|
|
switch (s) {
|
|
|
|
case FIBER_CREATED: return "created";
|
|
|
|
case FIBER_RESUMED: return "resumed";
|
|
|
|
case FIBER_SUSPENDED: return "suspended";
|
|
|
|
case FIBER_TERMINATED: return "terminated";
|
|
|
|
}
|
|
|
|
VM_UNREACHABLE(fiber_status_name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-10-26 11:32:49 +03:00
|
|
|
static void
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber_verify(const rb_fiber_t *fiber)
|
2017-10-26 11:32:49 +03:00
|
|
|
{
|
|
|
|
#if VM_CHECK_MODE > 0
|
2019-07-08 08:59:28 +03:00
|
|
|
VM_ASSERT(fiber->cont.saved_ec.fiber_ptr == fiber);
|
2017-10-26 11:32:49 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
switch (fiber->status) {
|
2017-10-26 11:32:49 +03:00
|
|
|
case FIBER_RESUMED:
|
2019-07-08 10:00:29 +03:00
|
|
|
VM_ASSERT(fiber->cont.saved_ec.vm_stack != NULL);
|
2019-06-01 08:48:25 +03:00
|
|
|
break;
|
2017-10-26 11:32:49 +03:00
|
|
|
case FIBER_SUSPENDED:
|
2019-07-08 10:00:29 +03:00
|
|
|
VM_ASSERT(fiber->cont.saved_ec.vm_stack != NULL);
|
2019-06-01 08:48:25 +03:00
|
|
|
break;
|
2017-10-26 11:32:49 +03:00
|
|
|
case FIBER_CREATED:
|
|
|
|
case FIBER_TERMINATED:
|
2019-06-01 08:48:25 +03:00
|
|
|
/* TODO */
|
|
|
|
break;
|
2017-10-26 11:32:49 +03:00
|
|
|
default:
|
2019-06-01 08:48:25 +03:00
|
|
|
VM_UNREACHABLE(fiber_verify);
|
2017-10-26 11:32:49 +03:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
inline static void
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber_status_set(rb_fiber_t *fiber, enum fiber_status s)
|
2017-08-10 04:47:13 +03:00
|
|
|
{
|
2019-07-16 03:49:14 +03:00
|
|
|
// if (DEBUG) fprintf(stderr, "fiber: %p, status: %s -> %s\n", (void *)fiber, fiber_status_name(fiber->status), fiber_status_name(s));
|
2019-07-08 08:59:28 +03:00
|
|
|
VM_ASSERT(!FIBER_TERMINATED_P(fiber));
|
|
|
|
VM_ASSERT(fiber->status != s);
|
|
|
|
fiber_verify(fiber);
|
|
|
|
fiber->status = s;
|
2017-08-10 04:47:13 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 04:01:37 +03:00
|
|
|
static rb_context_t *
|
|
|
|
cont_ptr(VALUE obj)
|
|
|
|
{
|
|
|
|
rb_context_t *cont;
|
|
|
|
|
|
|
|
TypedData_Get_Struct(obj, rb_context_t, &cont_data_type, cont);
|
|
|
|
|
|
|
|
return cont;
|
|
|
|
}
|
2007-05-24 02:52:19 +04:00
|
|
|
|
2018-08-21 04:01:37 +03:00
|
|
|
static rb_fiber_t *
|
|
|
|
fiber_ptr(VALUE obj)
|
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_t *fiber;
|
2018-08-21 04:01:37 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
TypedData_Get_Struct(obj, rb_fiber_t, &fiber_data_type, fiber);
|
|
|
|
if (!fiber) rb_raise(rb_eFiberError, "uninitialized fiber");
|
2018-08-21 04:01:37 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
return fiber;
|
2018-08-21 04:01:37 +03:00
|
|
|
}
|
2008-10-22 19:12:07 +04:00
|
|
|
|
2016-12-08 02:47:59 +03:00
|
|
|
NOINLINE(static VALUE cont_capture(volatile int *volatile stat));
|
2007-05-24 02:52:19 +04:00
|
|
|
|
2009-09-21 05:13:24 +04:00
|
|
|
#define THREAD_MUST_BE_RUNNING(th) do { \
|
2019-07-19 00:15:47 +03:00
|
|
|
if (!(th)->ec->tag) rb_raise(rb_eThreadError, "not running thread"); \
|
2009-09-21 05:13:24 +04:00
|
|
|
} while (0)
|
2007-05-24 02:52:19 +04:00
|
|
|
|
2020-09-05 07:26:24 +03:00
|
|
|
rb_thread_t*
|
|
|
|
rb_fiber_threadptr(const rb_fiber_t *fiber)
|
|
|
|
{
|
|
|
|
return fiber->cont.saved_ec.thread_ptr;
|
|
|
|
}
|
|
|
|
|
2017-09-10 22:00:08 +03:00
|
|
|
static VALUE
|
|
|
|
cont_thread_value(const rb_context_t *cont)
|
|
|
|
{
|
2017-10-29 15:57:04 +03:00
|
|
|
return cont->saved_ec.thread_ptr->self;
|
2017-09-10 22:00:08 +03:00
|
|
|
}
|
|
|
|
|
2019-05-31 23:25:24 +03:00
|
|
|
static void
|
|
|
|
cont_compact(void *ptr)
|
|
|
|
{
|
|
|
|
rb_context_t *cont = ptr;
|
|
|
|
|
2019-10-15 00:52:58 +03:00
|
|
|
if (cont->self) {
|
|
|
|
cont->self = rb_gc_location(cont->self);
|
|
|
|
}
|
2019-05-31 23:25:24 +03:00
|
|
|
cont->value = rb_gc_location(cont->value);
|
|
|
|
rb_execution_context_update(&cont->saved_ec);
|
|
|
|
}
|
|
|
|
|
2007-05-24 02:52:19 +04:00
|
|
|
static void
|
|
|
|
cont_mark(void *ptr)
|
|
|
|
{
|
2017-02-13 04:05:23 +03:00
|
|
|
rb_context_t *cont = ptr;
|
|
|
|
|
2007-06-25 06:44:20 +04:00
|
|
|
RUBY_MARK_ENTER("cont");
|
2019-10-15 00:52:58 +03:00
|
|
|
if (cont->self) {
|
|
|
|
rb_gc_mark_movable(cont->self);
|
|
|
|
}
|
2019-08-12 23:09:21 +03:00
|
|
|
rb_gc_mark_movable(cont->value);
|
2016-07-28 14:02:30 +03:00
|
|
|
|
2019-07-19 06:45:44 +03:00
|
|
|
rb_execution_context_mark(&cont->saved_ec);
|
2017-09-10 22:00:08 +03:00
|
|
|
rb_gc_mark(cont_thread_value(cont));
|
2007-05-24 02:52:19 +04:00
|
|
|
|
2017-08-22 03:41:24 +03:00
|
|
|
if (cont->saved_vm_stack.ptr) {
|
2008-11-18 19:19:37 +03:00
|
|
|
#ifdef CAPTURE_JUST_VALID_VM_STACK
|
2019-06-01 08:48:25 +03:00
|
|
|
rb_gc_mark_locations(cont->saved_vm_stack.ptr,
|
|
|
|
cont->saved_vm_stack.ptr + cont->saved_vm_stack.slen + cont->saved_vm_stack.clen);
|
2009-02-05 19:13:54 +03:00
|
|
|
#else
|
2019-06-01 08:48:25 +03:00
|
|
|
rb_gc_mark_locations(cont->saved_vm_stack.ptr,
|
|
|
|
cont->saved_vm_stack.ptr, cont->saved_ec.stack_size);
|
2008-11-18 19:19:37 +03:00
|
|
|
#endif
|
2017-02-13 04:05:23 +03:00
|
|
|
}
|
2007-05-29 05:59:53 +04:00
|
|
|
|
2017-02-13 04:05:23 +03:00
|
|
|
if (cont->machine.stack) {
|
2019-06-01 08:48:25 +03:00
|
|
|
if (cont->type == CONTINUATION_CONTEXT) {
|
|
|
|
/* cont */
|
|
|
|
rb_gc_mark_locations(cont->machine.stack,
|
|
|
|
cont->machine.stack + cont->machine.stack_size);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* fiber */
|
2019-07-08 10:00:29 +03:00
|
|
|
const rb_fiber_t *fiber = (rb_fiber_t*)cont;
|
2017-06-28 07:49:30 +03:00
|
|
|
|
2019-07-08 10:00:29 +03:00
|
|
|
if (!FIBER_TERMINATED_P(fiber)) {
|
2019-06-01 08:48:25 +03:00
|
|
|
rb_gc_mark_locations(cont->machine.stack,
|
|
|
|
cont->machine.stack + cont->machine.stack_size);
|
|
|
|
}
|
|
|
|
}
|
2017-02-13 04:05:23 +03:00
|
|
|
}
|
|
|
|
|
2007-06-25 06:44:20 +04:00
|
|
|
RUBY_MARK_LEAVE("cont");
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
#if 0
|
2018-09-12 23:49:24 +03:00
|
|
|
static int
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber_is_root_p(const rb_fiber_t *fiber)
|
2018-09-12 23:49:24 +03:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
return fiber == fiber->cont.saved_ec.thread_ptr->root_fiber;
|
2018-09-12 23:49:24 +03:00
|
|
|
}
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
#endif
|
2018-09-12 23:49:24 +03:00
|
|
|
|
2022-10-17 19:27:59 +03:00
|
|
|
static void jit_cont_free(struct rb_jit_cont *cont);
|
|
|
|
|
2007-05-24 02:52:19 +04:00
|
|
|
static void
|
|
|
|
cont_free(void *ptr)
|
|
|
|
{
|
2017-03-17 22:59:56 +03:00
|
|
|
rb_context_t *cont = ptr;
|
|
|
|
|
2007-06-25 06:44:20 +04:00
|
|
|
RUBY_FREE_ENTER("cont");
|
2019-06-05 14:39:17 +03:00
|
|
|
|
2017-03-17 22:59:56 +03:00
|
|
|
if (cont->type == CONTINUATION_CONTEXT) {
|
2019-06-02 03:49:58 +03:00
|
|
|
ruby_xfree(cont->saved_ec.vm_stack);
|
2019-06-01 08:48:25 +03:00
|
|
|
ruby_xfree(cont->ensure_array);
|
|
|
|
RUBY_FREE_UNLESS_NULL(cont->machine.stack);
|
2019-07-19 00:15:47 +03:00
|
|
|
}
|
|
|
|
else {
|
2019-07-08 10:00:29 +03:00
|
|
|
rb_fiber_t *fiber = (rb_fiber_t*)cont;
|
|
|
|
coroutine_destroy(&fiber->context);
|
2020-09-24 06:06:38 +03:00
|
|
|
fiber_stack_release(fiber);
|
2017-03-17 22:59:56 +03:00
|
|
|
}
|
2019-06-01 08:48:25 +03:00
|
|
|
|
2017-08-22 03:41:24 +03:00
|
|
|
RUBY_FREE_UNLESS_NULL(cont->saved_vm_stack.ptr);
|
2007-11-09 04:11:49 +03:00
|
|
|
|
2022-10-17 19:27:59 +03:00
|
|
|
if (jit_cont_enabled) {
|
|
|
|
VM_ASSERT(cont->jit_cont != NULL);
|
|
|
|
jit_cont_free(cont->jit_cont);
|
mjit.c: merge MJIT infrastructure
that allows to JIT-compile Ruby methods by generating C code and
using C compiler. See the first comment of mjit.c to know what this
file does.
mjit.c is authored by Vladimir Makarov <vmakarov@redhat.com>.
After he invented great method JIT infrastructure for MRI as MJIT,
Lars Kanis <lars@greiz-reinsdorf.de> sent the patch to support MinGW
in MJIT. In addition to merging it, I ported pthread to Windows native
threads. Now this MJIT infrastructure can be compiled on Visual Studio.
This commit simplifies mjit.c to decrease code at initial merge. For
example, this commit does not provide multiple JIT threads support.
We can resurrect them later if we really want them, but I wanted to minimize
diff to make it easier to review this patch.
`/tmp/_mjitXXX` file is renamed to `/tmp/_ruby_mjitXXX` because non-Ruby
developers may not know the name "mjit" and the file name should make
sure it's from Ruby and not from some harmful programs. TODO: it may be
better to store this to some temporary directory which Ruby is already using
by Tempfile, if it's not bad for performance.
mjit.h: New. It has `mjit_exec` interface similar to `vm_exec`, which is
for triggering MJIT. This drops interface for AOT compared to the original
MJIT.
Makefile.in: define macros to let MJIT know the path of MJIT header.
Probably we can refactor this to reduce the number of macros (TODO).
win32/Makefile.sub: ditto.
common.mk: compile mjit.o and mjit_compile.o. Unlike original MJIT, this
commit separates MJIT infrastructure and JIT compiler code as independent
object files. As initial patch is NOT going to have ultra-fast JIT compiler,
it's likely to replace JIT compiler, e.g. original MJIT's compiler or some
future JIT impelementations which are not public now.
inits.c: define MJIT module. This is added because `MJIT.enabled?` was
necessary for testing.
test/lib/zombie_hunter.rb: skip if `MJIT.enabled?`. Obviously this
wouldn't work with current code when JIT is enabled.
test/ruby/test_io.rb: skip this too. This would make no sense with MJIT.
ruby.c: define MJIT CLI options. As major difference from original MJIT,
"-j:l"/"--jit:llvm" are renamed to "--jit-cc" because I want to support
not only gcc/clang but also cl.exe (Visual Studio) in the future. But it
takes only "--jit-cc=gcc", "--jit-cc=clang" for now. And only long "--jit"
options are allowed since some Ruby committers preferred it at Ruby
developers Meeting on January, and some of options are renamed.
This file also triggers to initialize MJIT thread and variables.
eval.c: finalize MJIT worker thread and variables.
test/ruby/test_rubyoptions.rb: fix number of CLI options for --jit.
thread_pthread.c: change for pthread abstraction in MJIT. Prefix rb_ for
functions which are used by other files.
thread_win32.c: ditto, for Windows. Those pthread porting is one of major
works that YARV-MJIT created, which is my fork of MJIT, in Feature 14235.
thread.c: follow rb_ prefix changes
vm.c: trigger MJIT call on VM invocation. Also trigger `mjit_mark` to avoid
SEGV by race between JIT and GC of ISeq. The improvement was provided by
wanabe <s.wanabe@gmail.com>.
In JIT compiler I created and am going to add in my next commit, I found
that having `mjit_exec` after `vm_loop_start:` is harmful because the
JIT-ed function doesn't proceed other ISeqs on RESTORE_REGS of leave insn.
Executing non-FINISH frame is unexpected for my JIT compiler and
`exception_handler` triggers executions of such ISeqs. So `mjit_exec`
here should be executed only when it directly comes from `vm_exec` call.
`RubyVM::MJIT` module and `.enabled?` method is added so that we can skip
some tests which don't expect JIT threads or compiler file descriptors.
vm_insnhelper.h: trigger MJIT on method calls during VM execution.
vm_core.h: add fields required for mjit.c. `bp` must be `cfp[6]` because
rb_control_frame_struct is likely to be casted to another struct. The
last position is the safest place to add the new field.
vm_insnhelper.c: save initial value of cfp->ep as cfp->bp. This is an
optimization which are done in both MJIT and YARV-MJIT. So this change
is added in this commit. Calculating bp from ep is a little heavy work,
so bp is kind of cache for it.
iseq.c: notify ISeq GC to MJIT. We should know which iseq in MJIT queue
is GCed to avoid SEGV. TODO: unload some GCed units in some safe way.
gc.c: add hooks so that MJIT can wait GC, and vice versa. Simultaneous
JIT and GC executions may cause SEGV and so we should synchronize them.
cont.c: save continuation information in MJIT worker. As MJIT shouldn't
unload JIT-ed code which is being used, MJIT wants to know full list of
saved execution contexts for continuation and detect ISeqs in use.
mjit_compile.c: added empty JIT compiler so that you can reuse this commit
to build your own JIT compiler. This commit tries to compile ISeqs but
all of them are considered as not supported in this commit. So you can't
use JIT compiler in this commit yet while we added --jit option now.
Patch author: Vladimir Makarov <vmakarov@redhat.com>.
Contributors:
Takashi Kokubun <takashikkbn@gmail.com>.
wanabe <s.wanabe@gmail.com>.
Lars Kanis <lars@greiz-reinsdorf.de>.
Part of Feature 12589 and 14235.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62189 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-02-04 09:58:09 +03:00
|
|
|
}
|
2017-03-17 22:59:56 +03:00
|
|
|
/* free rb_cont_t or rb_fiber_t */
|
|
|
|
ruby_xfree(ptr);
|
2007-06-25 06:44:20 +04:00
|
|
|
RUBY_FREE_LEAVE("cont");
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
|
2009-09-08 19:27:31 +04:00
|
|
|
static size_t
|
2009-09-09 06:22:08 +04:00
|
|
|
cont_memsize(const void *ptr)
|
2009-09-08 19:27:31 +04:00
|
|
|
{
|
2009-09-09 06:26:06 +04:00
|
|
|
const rb_context_t *cont = ptr;
|
2009-09-08 19:27:31 +04:00
|
|
|
size_t size = 0;
|
2015-12-09 03:38:32 +03:00
|
|
|
|
|
|
|
size = sizeof(*cont);
|
2017-08-22 03:41:24 +03:00
|
|
|
if (cont->saved_vm_stack.ptr) {
|
2009-09-08 19:27:31 +04:00
|
|
|
#ifdef CAPTURE_JUST_VALID_VM_STACK
|
2019-06-01 08:48:25 +03:00
|
|
|
size_t n = (cont->saved_vm_stack.slen + cont->saved_vm_stack.clen);
|
2009-09-08 19:27:31 +04:00
|
|
|
#else
|
2019-06-01 08:48:25 +03:00
|
|
|
size_t n = cont->saved_ec.vm_stack_size;
|
2009-09-08 19:27:31 +04:00
|
|
|
#endif
|
2019-06-01 08:48:25 +03:00
|
|
|
size += n * sizeof(*cont->saved_vm_stack.ptr);
|
2015-12-09 03:38:32 +03:00
|
|
|
}
|
2009-09-08 19:27:31 +04:00
|
|
|
|
2015-12-09 03:38:32 +03:00
|
|
|
if (cont->machine.stack) {
|
2019-06-01 08:48:25 +03:00
|
|
|
size += cont->machine.stack_size * sizeof(*cont->machine.stack);
|
2015-12-09 03:38:32 +03:00
|
|
|
}
|
2019-06-19 12:06:57 +03:00
|
|
|
|
2009-09-08 19:27:31 +04:00
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2019-05-31 23:25:24 +03:00
|
|
|
void
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_update_self(rb_fiber_t *fiber)
|
2019-05-31 23:25:24 +03:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
if (fiber->cont.self) {
|
|
|
|
fiber->cont.self = rb_gc_location(fiber->cont.self);
|
2019-05-31 23:25:24 +03:00
|
|
|
}
|
|
|
|
else {
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_execution_context_update(&fiber->cont.saved_ec);
|
2019-05-31 23:25:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-16 02:35:08 +04:00
|
|
|
void
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_mark_self(const rb_fiber_t *fiber)
|
2014-10-16 02:35:08 +04:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
if (fiber->cont.self) {
|
2019-08-12 23:09:21 +03:00
|
|
|
rb_gc_mark_movable(fiber->cont.self);
|
2017-10-26 11:32:49 +03:00
|
|
|
}
|
|
|
|
else {
|
2019-07-08 10:00:29 +03:00
|
|
|
rb_execution_context_mark(&fiber->cont.saved_ec);
|
2017-10-26 11:32:49 +03:00
|
|
|
}
|
2014-10-16 02:35:08 +04:00
|
|
|
}
|
|
|
|
|
2019-05-31 23:25:24 +03:00
|
|
|
static void
|
|
|
|
fiber_compact(void *ptr)
|
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_t *fiber = ptr;
|
|
|
|
fiber->first_proc = rb_gc_location(fiber->first_proc);
|
2019-05-31 23:25:24 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
if (fiber->prev) rb_fiber_update_self(fiber->prev);
|
2019-05-31 23:25:24 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
cont_compact(&fiber->cont);
|
|
|
|
fiber_verify(fiber);
|
2019-05-31 23:25:24 +03:00
|
|
|
}
|
|
|
|
|
2008-10-22 19:12:07 +04:00
|
|
|
static void
|
|
|
|
fiber_mark(void *ptr)
|
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_t *fiber = ptr;
|
2008-10-22 19:12:07 +04:00
|
|
|
RUBY_MARK_ENTER("cont");
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber_verify(fiber);
|
2019-08-12 23:09:21 +03:00
|
|
|
rb_gc_mark_movable(fiber->first_proc);
|
2019-07-08 08:59:28 +03:00
|
|
|
if (fiber->prev) rb_fiber_mark_self(fiber->prev);
|
|
|
|
cont_mark(&fiber->cont);
|
2008-10-22 19:12:07 +04:00
|
|
|
RUBY_MARK_LEAVE("cont");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
fiber_free(void *ptr)
|
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_t *fiber = ptr;
|
2008-10-22 19:12:07 +04:00
|
|
|
RUBY_FREE_ENTER("fiber");
|
2017-10-26 17:21:31 +03:00
|
|
|
|
2021-09-02 02:26:46 +03:00
|
|
|
if (DEBUG) fprintf(stderr, "fiber_free: %p[%p]\n", (void *)fiber, fiber->stack.base);
|
2019-07-12 04:42:34 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
if (fiber->cont.saved_ec.local_storage) {
|
2020-01-04 03:45:58 +03:00
|
|
|
rb_id_table_free(fiber->cont.saved_ec.local_storage);
|
2008-10-22 19:12:07 +04:00
|
|
|
}
|
2017-03-17 22:59:56 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
cont_free(&fiber->cont);
|
2008-10-22 19:12:07 +04:00
|
|
|
RUBY_FREE_LEAVE("fiber");
|
|
|
|
}
|
|
|
|
|
2009-09-08 19:27:31 +04:00
|
|
|
static size_t
|
2009-09-09 06:22:08 +04:00
|
|
|
fiber_memsize(const void *ptr)
|
2009-09-08 19:27:31 +04:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
const rb_fiber_t *fiber = ptr;
|
|
|
|
size_t size = sizeof(*fiber);
|
|
|
|
const rb_execution_context_t *saved_ec = &fiber->cont.saved_ec;
|
2018-09-12 23:49:19 +03:00
|
|
|
const rb_thread_t *th = rb_ec_thread_ptr(saved_ec);
|
2015-12-09 03:38:32 +03:00
|
|
|
|
2018-09-12 23:49:19 +03:00
|
|
|
/*
|
|
|
|
* vm.c::thread_memsize already counts th->ec->local_storage
|
|
|
|
*/
|
2019-07-08 08:59:28 +03:00
|
|
|
if (saved_ec->local_storage && fiber != th->root_fiber) {
|
2020-01-04 03:45:58 +03:00
|
|
|
size += rb_id_table_memsize(saved_ec->local_storage);
|
2022-12-01 13:00:33 +03:00
|
|
|
size += rb_obj_memsize_of(saved_ec->storage);
|
2009-09-08 19:27:31 +04:00
|
|
|
}
|
2022-12-01 13:00:33 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
size += cont_memsize(&fiber->cont);
|
2010-10-04 03:00:21 +04:00
|
|
|
return size;
|
2009-09-08 19:27:31 +04:00
|
|
|
}
|
|
|
|
|
2012-02-15 18:00:11 +04:00
|
|
|
VALUE
|
|
|
|
rb_obj_is_fiber(VALUE obj)
|
|
|
|
{
|
2021-08-02 06:06:44 +03:00
|
|
|
return RBOOL(rb_typeddata_is_kind_of(obj, &fiber_data_type));
|
2012-02-15 18:00:11 +04:00
|
|
|
}
|
|
|
|
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
static void
|
|
|
|
cont_save_machine_stack(rb_thread_t *th, rb_context_t *cont)
|
2007-05-24 02:52:19 +04:00
|
|
|
{
|
2009-06-01 05:11:04 +04:00
|
|
|
size_t size;
|
2007-05-24 02:52:19 +04:00
|
|
|
|
2017-10-26 11:32:49 +03:00
|
|
|
SET_MACHINE_STACK_END(&th->ec->machine.stack_end);
|
2007-06-14 12:35:20 +04:00
|
|
|
|
2017-10-26 11:32:49 +03:00
|
|
|
if (th->ec->machine.stack_start > th->ec->machine.stack_end) {
|
2019-06-01 08:48:25 +03:00
|
|
|
size = cont->machine.stack_size = th->ec->machine.stack_start - th->ec->machine.stack_end;
|
|
|
|
cont->machine.stack_src = th->ec->machine.stack_end;
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
else {
|
2019-06-01 08:48:25 +03:00
|
|
|
size = cont->machine.stack_size = th->ec->machine.stack_end - th->ec->machine.stack_start;
|
|
|
|
cont->machine.stack_src = th->ec->machine.stack_start;
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
|
2014-01-28 10:09:58 +04:00
|
|
|
if (cont->machine.stack) {
|
2019-06-01 08:48:25 +03:00
|
|
|
REALLOC_N(cont->machine.stack, VALUE, size);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
else {
|
2019-06-01 08:48:25 +03:00
|
|
|
cont->machine.stack = ALLOC_N(VALUE, size);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2007-12-23 22:03:23 +03:00
|
|
|
FLUSH_REGISTER_WINDOWS;
|
2022-09-20 11:07:42 +03:00
|
|
|
asan_unpoison_memory_region(cont->machine.stack_src, size, false);
|
2014-01-28 10:09:58 +04:00
|
|
|
MEMCPY(cont->machine.stack, cont->machine.stack_src, VALUE, size);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2009-09-08 19:27:31 +04:00
|
|
|
static const rb_data_type_t cont_data_type = {
|
|
|
|
"continuation",
|
2019-05-31 23:25:24 +03:00
|
|
|
{cont_mark, cont_free, cont_memsize, cont_compact},
|
2014-12-01 09:38:04 +03:00
|
|
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
2009-09-08 19:27:31 +04:00
|
|
|
};
|
|
|
|
|
2014-10-16 02:35:08 +04:00
|
|
|
static inline void
|
2011-06-14 16:57:50 +04:00
|
|
|
cont_save_thread(rb_context_t *cont, rb_thread_t *th)
|
2008-10-22 19:12:07 +04:00
|
|
|
{
|
2017-09-10 22:00:08 +03:00
|
|
|
rb_execution_context_t *sec = &cont->saved_ec;
|
2014-10-16 02:35:01 +04:00
|
|
|
|
2017-06-28 17:27:49 +03:00
|
|
|
VM_ASSERT(th->status == THREAD_RUNNABLE);
|
|
|
|
|
2008-10-22 19:12:07 +04:00
|
|
|
/* save thread context */
|
2017-10-26 11:32:49 +03:00
|
|
|
*sec = *th->ec;
|
2014-10-16 02:35:01 +04:00
|
|
|
|
2017-12-25 17:22:21 +03:00
|
|
|
/* saved_ec->machine.stack_end should be NULL */
|
2011-06-14 16:57:50 +04:00
|
|
|
/* because it may happen GC afterward */
|
2017-09-10 22:00:08 +03:00
|
|
|
sec->machine.stack_end = NULL;
|
2011-06-14 16:57:50 +04:00
|
|
|
}
|
|
|
|
|
2022-10-20 03:18:59 +03:00
|
|
|
static rb_nativethread_lock_t jit_cont_lock;
|
|
|
|
|
2022-10-17 19:27:59 +03:00
|
|
|
// Register a new continuation with execution context `ec`. Return JIT info about
|
|
|
|
// the continuation.
|
|
|
|
static struct rb_jit_cont *
|
|
|
|
jit_cont_new(rb_execution_context_t *ec)
|
|
|
|
{
|
|
|
|
struct rb_jit_cont *cont;
|
|
|
|
|
|
|
|
// We need to use calloc instead of something like ZALLOC to avoid triggering GC here.
|
|
|
|
// When this function is called from rb_thread_alloc through rb_threadptr_root_fiber_setup,
|
|
|
|
// the thread is still being prepared and marking it causes SEGV.
|
|
|
|
cont = calloc(1, sizeof(struct rb_jit_cont));
|
|
|
|
if (cont == NULL)
|
|
|
|
rb_memerror();
|
|
|
|
cont->ec = ec;
|
|
|
|
|
2022-10-20 03:18:59 +03:00
|
|
|
rb_native_mutex_lock(&jit_cont_lock);
|
2022-10-17 19:27:59 +03:00
|
|
|
if (first_jit_cont == NULL) {
|
|
|
|
cont->next = cont->prev = NULL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
cont->prev = NULL;
|
|
|
|
cont->next = first_jit_cont;
|
|
|
|
first_jit_cont->prev = cont;
|
|
|
|
}
|
|
|
|
first_jit_cont = cont;
|
2022-10-20 03:18:59 +03:00
|
|
|
rb_native_mutex_unlock(&jit_cont_lock);
|
2022-10-17 19:27:59 +03:00
|
|
|
|
|
|
|
return cont;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unregister continuation `cont`.
|
|
|
|
static void
|
|
|
|
jit_cont_free(struct rb_jit_cont *cont)
|
|
|
|
{
|
2023-03-10 09:31:50 +03:00
|
|
|
if (!cont) return;
|
|
|
|
|
2022-10-20 03:18:59 +03:00
|
|
|
rb_native_mutex_lock(&jit_cont_lock);
|
2022-10-17 19:27:59 +03:00
|
|
|
if (cont == first_jit_cont) {
|
|
|
|
first_jit_cont = cont->next;
|
|
|
|
if (first_jit_cont != NULL)
|
|
|
|
first_jit_cont->prev = NULL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
cont->prev->next = cont->next;
|
|
|
|
if (cont->next != NULL)
|
|
|
|
cont->next->prev = cont->prev;
|
|
|
|
}
|
2022-10-20 03:18:59 +03:00
|
|
|
rb_native_mutex_unlock(&jit_cont_lock);
|
2022-10-17 19:27:59 +03:00
|
|
|
|
|
|
|
free(cont);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call a given callback against all on-stack ISEQs.
|
|
|
|
void
|
2022-10-18 19:07:11 +03:00
|
|
|
rb_jit_cont_each_iseq(rb_iseq_callback callback, void *data)
|
2022-10-17 19:27:59 +03:00
|
|
|
{
|
|
|
|
struct rb_jit_cont *cont;
|
|
|
|
for (cont = first_jit_cont; cont != NULL; cont = cont->next) {
|
|
|
|
if (cont->ec->vm_stack == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const rb_control_frame_t *cfp;
|
|
|
|
for (cfp = RUBY_VM_END_CONTROL_FRAME(cont->ec) - 1; ; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
|
|
|
|
const rb_iseq_t *iseq;
|
|
|
|
if (cfp->pc && (iseq = cfp->iseq) != NULL && imemo_type((VALUE)iseq) == imemo_iseq) {
|
2022-10-18 19:07:11 +03:00
|
|
|
callback(iseq, data);
|
2022-10-17 19:27:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (cfp == cont->ec->cfp)
|
|
|
|
break; // reached the most recent cfp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-20 03:18:59 +03:00
|
|
|
// Finish working with jit_cont.
|
2022-10-17 19:27:59 +03:00
|
|
|
void
|
|
|
|
rb_jit_cont_finish(void)
|
|
|
|
{
|
|
|
|
if (!jit_cont_enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
struct rb_jit_cont *cont, *next;
|
|
|
|
for (cont = first_jit_cont; cont != NULL; cont = next) {
|
|
|
|
next = cont->next;
|
2022-10-20 03:18:59 +03:00
|
|
|
free(cont); // Don't use xfree because it's allocated by calloc.
|
2022-10-17 19:27:59 +03:00
|
|
|
}
|
2022-10-20 03:18:59 +03:00
|
|
|
rb_native_mutex_destroy(&jit_cont_lock);
|
2022-10-17 19:27:59 +03:00
|
|
|
}
|
|
|
|
|
2020-02-29 10:58:33 +03:00
|
|
|
static void
|
2022-10-17 19:27:59 +03:00
|
|
|
cont_init_jit_cont(rb_context_t *cont)
|
2020-02-29 10:58:33 +03:00
|
|
|
{
|
2022-10-17 19:27:59 +03:00
|
|
|
VM_ASSERT(cont->jit_cont == NULL);
|
|
|
|
if (jit_cont_enabled) {
|
|
|
|
cont->jit_cont = jit_cont_new(&(cont->saved_ec));
|
2020-02-29 10:58:33 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-13 02:42:14 +03:00
|
|
|
struct rb_execution_context_struct *
|
|
|
|
rb_fiberptr_get_ec(struct rb_fiber_struct *fiber)
|
|
|
|
{
|
|
|
|
return &fiber->cont.saved_ec;
|
|
|
|
}
|
|
|
|
|
2011-06-14 16:57:50 +04:00
|
|
|
static void
|
|
|
|
cont_init(rb_context_t *cont, rb_thread_t *th)
|
|
|
|
{
|
|
|
|
/* save thread context */
|
|
|
|
cont_save_thread(cont, th);
|
2017-10-29 15:57:04 +03:00
|
|
|
cont->saved_ec.thread_ptr = th;
|
2017-09-10 22:00:08 +03:00
|
|
|
cont->saved_ec.local_storage = NULL;
|
|
|
|
cont->saved_ec.local_storage_recursive_hash = Qnil;
|
|
|
|
cont->saved_ec.local_storage_recursive_hash_for_trace = Qnil;
|
2022-10-17 19:27:59 +03:00
|
|
|
cont_init_jit_cont(cont);
|
2008-10-22 19:12:07 +04:00
|
|
|
}
|
|
|
|
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
static rb_context_t *
|
|
|
|
cont_new(VALUE klass)
|
|
|
|
{
|
|
|
|
rb_context_t *cont;
|
|
|
|
volatile VALUE contval;
|
2009-09-21 05:13:24 +04:00
|
|
|
rb_thread_t *th = GET_THREAD();
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
|
2009-09-21 05:13:24 +04:00
|
|
|
THREAD_MUST_BE_RUNNING(th);
|
2009-09-08 19:27:31 +04:00
|
|
|
contval = TypedData_Make_Struct(klass, rb_context_t, &cont_data_type, cont);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
cont->self = contval;
|
2009-09-21 05:13:24 +04:00
|
|
|
cont_init(cont, th);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
return cont;
|
|
|
|
}
|
|
|
|
|
2021-07-18 13:55:07 +03:00
|
|
|
VALUE
|
|
|
|
rb_fiberptr_self(struct rb_fiber_struct *fiber)
|
2020-09-05 07:26:24 +03:00
|
|
|
{
|
|
|
|
return fiber->cont.self;
|
|
|
|
}
|
|
|
|
|
2021-07-18 13:55:07 +03:00
|
|
|
unsigned int
|
|
|
|
rb_fiberptr_blocking(struct rb_fiber_struct *fiber)
|
2021-02-09 09:39:56 +03:00
|
|
|
{
|
|
|
|
return fiber->blocking;
|
|
|
|
}
|
|
|
|
|
2022-10-20 03:18:59 +03:00
|
|
|
// Start working with jit_cont.
|
2020-02-29 10:58:33 +03:00
|
|
|
void
|
2022-10-20 03:18:59 +03:00
|
|
|
rb_jit_cont_init(void)
|
2020-02-29 10:58:33 +03:00
|
|
|
{
|
2022-10-20 03:18:59 +03:00
|
|
|
if (!jit_cont_enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
rb_native_mutex_initialize(&jit_cont_lock);
|
|
|
|
cont_init_jit_cont(&GET_EC()->fiber_ptr->cont);
|
2020-02-29 10:58:33 +03:00
|
|
|
}
|
|
|
|
|
2017-10-26 11:32:49 +03:00
|
|
|
#if 0
|
|
|
|
void
|
|
|
|
show_vm_stack(const rb_execution_context_t *ec)
|
|
|
|
{
|
|
|
|
VALUE *p = ec->vm_stack;
|
|
|
|
while (p < ec->cfp->sp) {
|
2019-06-01 08:48:25 +03:00
|
|
|
fprintf(stderr, "%3d ", (int)(p - ec->vm_stack));
|
|
|
|
rb_obj_info_dump(*p);
|
|
|
|
p++;
|
2017-10-26 11:32:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
show_vm_pcs(const rb_control_frame_t *cfp,
|
2019-06-01 08:48:25 +03:00
|
|
|
const rb_control_frame_t *end_of_cfp)
|
2017-10-26 11:32:49 +03:00
|
|
|
{
|
|
|
|
int i=0;
|
|
|
|
while (cfp != end_of_cfp) {
|
2019-06-01 08:48:25 +03:00
|
|
|
int pc = 0;
|
|
|
|
if (cfp->iseq) {
|
2022-03-23 22:19:48 +03:00
|
|
|
pc = cfp->pc - ISEQ_BODY(cfp->iseq)->iseq_encoded;
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
|
|
|
fprintf(stderr, "%2d pc: %d\n", i++, pc);
|
|
|
|
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
|
2017-10-26 11:32:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2022-05-26 06:06:21 +03:00
|
|
|
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
static VALUE
|
2016-12-08 02:47:59 +03:00
|
|
|
cont_capture(volatile int *volatile stat)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
2016-12-08 02:27:51 +03:00
|
|
|
rb_context_t *volatile cont;
|
2014-10-16 02:34:53 +04:00
|
|
|
rb_thread_t *th = GET_THREAD();
|
2007-06-14 12:35:20 +04:00
|
|
|
volatile VALUE contval;
|
2017-10-26 11:32:49 +03:00
|
|
|
const rb_execution_context_t *ec = th->ec;
|
2007-05-30 09:56:13 +04:00
|
|
|
|
2009-09-21 05:13:24 +04:00
|
|
|
THREAD_MUST_BE_RUNNING(th);
|
2017-10-28 13:47:19 +03:00
|
|
|
rb_vm_stack_to_heap(th->ec);
|
2007-08-25 06:03:44 +04:00
|
|
|
cont = cont_new(rb_cContinuation);
|
2007-06-14 12:35:20 +04:00
|
|
|
contval = cont->self;
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
|
2008-11-18 19:19:37 +03:00
|
|
|
#ifdef CAPTURE_JUST_VALID_VM_STACK
|
2017-08-22 03:41:24 +03:00
|
|
|
cont->saved_vm_stack.slen = ec->cfp->sp - ec->vm_stack;
|
|
|
|
cont->saved_vm_stack.clen = ec->vm_stack + ec->vm_stack_size - (VALUE*)ec->cfp;
|
|
|
|
cont->saved_vm_stack.ptr = ALLOC_N(VALUE, cont->saved_vm_stack.slen + cont->saved_vm_stack.clen);
|
2017-10-26 11:32:49 +03:00
|
|
|
MEMCPY(cont->saved_vm_stack.ptr,
|
2019-06-01 08:48:25 +03:00
|
|
|
ec->vm_stack,
|
|
|
|
VALUE, cont->saved_vm_stack.slen);
|
2017-08-22 03:41:24 +03:00
|
|
|
MEMCPY(cont->saved_vm_stack.ptr + cont->saved_vm_stack.slen,
|
2019-06-01 08:48:25 +03:00
|
|
|
(VALUE*)ec->cfp,
|
|
|
|
VALUE,
|
|
|
|
cont->saved_vm_stack.clen);
|
2009-02-05 19:13:54 +03:00
|
|
|
#else
|
2017-08-22 03:41:24 +03:00
|
|
|
cont->saved_vm_stack.ptr = ALLOC_N(VALUE, ec->vm_stack_size);
|
|
|
|
MEMCPY(cont->saved_vm_stack.ptr, ec->vm_stack, VALUE, ec->vm_stack_size);
|
2008-11-18 19:19:37 +03:00
|
|
|
#endif
|
2019-07-19 06:45:44 +03:00
|
|
|
// At this point, `cfp` is valid but `vm_stack` should be cleared:
|
|
|
|
rb_ec_set_vm_stack(&cont->saved_ec, NULL, 0);
|
|
|
|
VM_ASSERT(cont->saved_ec.cfp != NULL);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
cont_save_machine_stack(th, cont);
|
2007-05-24 02:52:19 +04:00
|
|
|
|
2013-11-15 21:15:31 +04:00
|
|
|
/* backup ensure_list to array for search in another context */
|
|
|
|
{
|
2019-06-01 08:48:25 +03:00
|
|
|
rb_ensure_list_t *p;
|
|
|
|
int size = 0;
|
|
|
|
rb_ensure_entry_t *entry;
|
|
|
|
for (p=th->ec->ensure_list; p; p=p->next)
|
|
|
|
size++;
|
|
|
|
entry = cont->ensure_array = ALLOC_N(rb_ensure_entry_t,size+1);
|
|
|
|
for (p=th->ec->ensure_list; p; p=p->next) {
|
|
|
|
if (!p->entry.marker)
|
2022-07-25 17:40:45 +03:00
|
|
|
p->entry.marker = rb_ary_hidden_new(0); /* dummy object */
|
2019-06-01 08:48:25 +03:00
|
|
|
*entry++ = p->entry;
|
|
|
|
}
|
|
|
|
entry->marker = 0;
|
2013-11-15 21:15:31 +04:00
|
|
|
}
|
|
|
|
|
2007-05-24 02:52:19 +04:00
|
|
|
if (ruby_setjmp(cont->jmpbuf)) {
|
2019-06-01 08:48:25 +03:00
|
|
|
VALUE value;
|
2007-05-24 02:52:19 +04:00
|
|
|
|
2019-06-01 08:48:25 +03:00
|
|
|
VAR_INITIALIZED(cont);
|
|
|
|
value = cont->value;
|
|
|
|
if (cont->argc == -1) rb_exc_raise(value);
|
|
|
|
cont->value = Qnil;
|
|
|
|
*stat = 1;
|
|
|
|
return value;
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
else {
|
2019-06-01 08:48:25 +03:00
|
|
|
*stat = 0;
|
|
|
|
return contval;
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-16 02:35:08 +04:00
|
|
|
static inline void
|
2010-05-05 22:37:37 +04:00
|
|
|
cont_restore_thread(rb_context_t *cont)
|
2007-05-24 02:52:19 +04:00
|
|
|
{
|
2017-08-10 09:01:57 +03:00
|
|
|
rb_thread_t *th = GET_THREAD();
|
2007-05-24 02:52:19 +04:00
|
|
|
|
|
|
|
/* restore thread context */
|
2007-11-09 04:11:49 +03:00
|
|
|
if (cont->type == CONTINUATION_CONTEXT) {
|
2019-06-01 08:48:25 +03:00
|
|
|
/* continuation */
|
|
|
|
rb_execution_context_t *sec = &cont->saved_ec;
|
2019-07-08 10:00:29 +03:00
|
|
|
rb_fiber_t *fiber = NULL;
|
2007-06-07 07:48:13 +04:00
|
|
|
|
2019-06-01 08:48:25 +03:00
|
|
|
if (sec->fiber_ptr != NULL) {
|
2019-07-08 10:00:29 +03:00
|
|
|
fiber = sec->fiber_ptr;
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
|
|
|
else if (th->root_fiber) {
|
2019-07-08 10:00:29 +03:00
|
|
|
fiber = th->root_fiber;
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
2007-06-06 22:19:42 +04:00
|
|
|
|
2019-07-08 10:00:29 +03:00
|
|
|
if (fiber && th->ec != &fiber->cont.saved_ec) {
|
|
|
|
ec_switch(th, fiber);
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
2017-10-26 11:32:49 +03:00
|
|
|
|
2018-11-26 22:59:08 +03:00
|
|
|
if (th->ec->trace_arg != sec->trace_arg) {
|
|
|
|
rb_raise(rb_eRuntimeError, "can't call across trace_func");
|
|
|
|
}
|
|
|
|
|
2019-06-01 08:48:25 +03:00
|
|
|
/* copy vm stack */
|
2008-11-18 19:19:37 +03:00
|
|
|
#ifdef CAPTURE_JUST_VALID_VM_STACK
|
2019-06-01 08:48:25 +03:00
|
|
|
MEMCPY(th->ec->vm_stack,
|
|
|
|
cont->saved_vm_stack.ptr,
|
|
|
|
VALUE, cont->saved_vm_stack.slen);
|
|
|
|
MEMCPY(th->ec->vm_stack + th->ec->vm_stack_size - cont->saved_vm_stack.clen,
|
|
|
|
cont->saved_vm_stack.ptr + cont->saved_vm_stack.slen,
|
|
|
|
VALUE, cont->saved_vm_stack.clen);
|
2009-02-05 19:13:54 +03:00
|
|
|
#else
|
2019-06-01 08:48:25 +03:00
|
|
|
MEMCPY(th->ec->vm_stack, cont->saved_vm_stack.ptr, VALUE, sec->vm_stack_size);
|
2008-11-18 19:19:37 +03:00
|
|
|
#endif
|
2019-06-01 08:48:25 +03:00
|
|
|
/* other members of ec */
|
2017-10-26 11:32:49 +03:00
|
|
|
|
2019-06-01 08:48:25 +03:00
|
|
|
th->ec->cfp = sec->cfp;
|
|
|
|
th->ec->raised_flag = sec->raised_flag;
|
|
|
|
th->ec->tag = sec->tag;
|
|
|
|
th->ec->root_lep = sec->root_lep;
|
|
|
|
th->ec->root_svar = sec->root_svar;
|
|
|
|
th->ec->ensure_list = sec->ensure_list;
|
|
|
|
th->ec->errinfo = sec->errinfo;
|
2017-10-26 11:32:49 +03:00
|
|
|
|
2019-06-01 08:48:25 +03:00
|
|
|
VM_ASSERT(th->ec->vm_stack != NULL);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
2007-11-09 04:11:49 +03:00
|
|
|
else {
|
2019-06-01 08:48:25 +03:00
|
|
|
/* fiber */
|
|
|
|
fiber_restore_thread(th, (rb_fiber_t*)cont);
|
2007-11-09 04:11:49 +03:00
|
|
|
}
|
2010-05-05 22:37:37 +04:00
|
|
|
}
|
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
NOINLINE(static void fiber_setcontext(rb_fiber_t *new_fiber, rb_fiber_t *old_fiber));
|
2010-05-05 22:37:37 +04:00
|
|
|
|
|
|
|
static void
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber_setcontext(rb_fiber_t *new_fiber, rb_fiber_t *old_fiber)
|
2010-05-05 22:37:37 +04:00
|
|
|
{
|
2017-09-10 18:49:45 +03:00
|
|
|
rb_thread_t *th = GET_THREAD();
|
2010-05-05 22:37:37 +04:00
|
|
|
|
2019-07-12 04:42:34 +03:00
|
|
|
/* save old_fiber's machine stack - to ensure efficient garbage collection */
|
2019-07-08 08:59:28 +03:00
|
|
|
if (!FIBER_TERMINATED_P(old_fiber)) {
|
2019-06-01 08:48:25 +03:00
|
|
|
STACK_GROW_DIR_DETECTION;
|
|
|
|
SET_MACHINE_STACK_END(&th->ec->machine.stack_end);
|
|
|
|
if (STACK_DIR_UPPER(0, 1)) {
|
2019-07-08 10:00:29 +03:00
|
|
|
old_fiber->cont.machine.stack_size = th->ec->machine.stack_start - th->ec->machine.stack_end;
|
|
|
|
old_fiber->cont.machine.stack = th->ec->machine.stack_end;
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
|
|
|
else {
|
2019-07-08 10:00:29 +03:00
|
|
|
old_fiber->cont.machine.stack_size = th->ec->machine.stack_end - th->ec->machine.stack_start;
|
|
|
|
old_fiber->cont.machine.stack = th->ec->machine.stack_start;
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
2010-05-05 22:37:37 +04:00
|
|
|
}
|
2017-09-10 18:49:45 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
/* exchange machine_stack_start between old_fiber and new_fiber */
|
|
|
|
old_fiber->cont.saved_ec.machine.stack_start = th->ec->machine.stack_start;
|
2017-09-10 18:49:45 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
/* old_fiber->machine.stack_end should be NULL */
|
|
|
|
old_fiber->cont.saved_ec.machine.stack_end = NULL;
|
2017-09-10 18:49:45 +03:00
|
|
|
|
2021-09-02 02:26:46 +03:00
|
|
|
// if (DEBUG) fprintf(stderr, "fiber_setcontext: %p[%p] -> %p[%p]\n", (void*)old_fiber, old_fiber->stack.base, (void*)new_fiber, new_fiber->stack.base);
|
2019-07-12 04:42:34 +03:00
|
|
|
|
2022-05-19 14:03:49 +03:00
|
|
|
#if defined(COROUTINE_SANITIZE_ADDRESS)
|
|
|
|
__sanitizer_start_switch_fiber(FIBER_TERMINATED_P(old_fiber) ? NULL : &old_fiber->context.fake_stack, new_fiber->context.stack_base, new_fiber->context.stack_size);
|
|
|
|
#endif
|
|
|
|
|
2010-05-05 22:37:37 +04:00
|
|
|
/* swap machine context */
|
2021-06-26 01:17:26 +03:00
|
|
|
struct coroutine_context * from = coroutine_transfer(&old_fiber->context, &new_fiber->context);
|
|
|
|
|
2022-05-19 14:03:49 +03:00
|
|
|
#if defined(COROUTINE_SANITIZE_ADDRESS)
|
|
|
|
__sanitizer_finish_switch_fiber(old_fiber->context.fake_stack, NULL, NULL);
|
|
|
|
#endif
|
|
|
|
|
2021-06-26 01:17:26 +03:00
|
|
|
if (from == NULL) {
|
|
|
|
rb_syserr_fail(errno, "coroutine_transfer");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* restore thread context */
|
|
|
|
fiber_restore_thread(th, old_fiber);
|
2019-07-12 04:42:34 +03:00
|
|
|
|
2019-07-16 03:49:14 +03:00
|
|
|
// It's possible to get here, and new_fiber is already freed.
|
2021-09-02 02:26:46 +03:00
|
|
|
// if (DEBUG) fprintf(stderr, "fiber_setcontext: %p[%p] <- %p[%p]\n", (void*)old_fiber, old_fiber->stack.base, (void*)new_fiber, new_fiber->stack.base);
|
2010-05-05 22:37:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
NOINLINE(NORETURN(static void cont_restore_1(rb_context_t *)));
|
|
|
|
|
|
|
|
static void
|
|
|
|
cont_restore_1(rb_context_t *cont)
|
|
|
|
{
|
|
|
|
cont_restore_thread(cont);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
|
2007-05-24 02:52:19 +04:00
|
|
|
/* restore machine stack */
|
2022-12-19 03:45:50 +03:00
|
|
|
#if defined(_M_AMD64) && !defined(__MINGW64__)
|
2007-07-08 21:19:01 +04:00
|
|
|
{
|
2019-06-01 08:48:25 +03:00
|
|
|
/* workaround for x64 SEH */
|
|
|
|
jmp_buf buf;
|
|
|
|
setjmp(buf);
|
2019-11-28 04:52:33 +03:00
|
|
|
_JUMP_BUFFER *bp = (void*)&cont->jmpbuf;
|
|
|
|
bp->Frame = ((_JUMP_BUFFER*)((void*)&buf))->Frame;
|
2007-07-08 21:19:01 +04:00
|
|
|
}
|
|
|
|
#endif
|
2014-01-28 10:09:58 +04:00
|
|
|
if (cont->machine.stack_src) {
|
2019-06-01 08:48:25 +03:00
|
|
|
FLUSH_REGISTER_WINDOWS;
|
|
|
|
MEMCPY(cont->machine.stack_src, cont->machine.stack,
|
2019-07-19 00:15:47 +03:00
|
|
|
VALUE, cont->machine.stack_size);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
2007-05-24 02:52:19 +04:00
|
|
|
|
|
|
|
ruby_longjmp(cont->jmpbuf, 1);
|
|
|
|
}
|
|
|
|
|
2007-05-28 05:25:55 +04:00
|
|
|
NORETURN(NOINLINE(static void cont_restore_0(rb_context_t *, VALUE *)));
|
2007-05-24 02:52:19 +04:00
|
|
|
|
|
|
|
static void
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
cont_restore_0(rb_context_t *cont, VALUE *addr_in_prev_frame)
|
2007-05-24 02:52:19 +04:00
|
|
|
{
|
2014-01-28 10:09:58 +04:00
|
|
|
if (cont->machine.stack_src) {
|
2009-01-17 05:11:38 +03:00
|
|
|
#ifdef HAVE_ALLOCA
|
|
|
|
#define STACK_PAD_SIZE 1
|
|
|
|
#else
|
2007-05-24 02:52:19 +04:00
|
|
|
#define STACK_PAD_SIZE 1024
|
2009-01-17 05:11:38 +03:00
|
|
|
#endif
|
2019-06-01 08:48:25 +03:00
|
|
|
VALUE space[STACK_PAD_SIZE];
|
2007-05-24 02:52:19 +04:00
|
|
|
|
2009-01-06 13:09:54 +03:00
|
|
|
#if !STACK_GROW_DIRECTION
|
2019-06-01 08:48:25 +03:00
|
|
|
if (addr_in_prev_frame > &space[0]) {
|
|
|
|
/* Stack grows downward */
|
2009-01-06 13:09:54 +03:00
|
|
|
#endif
|
|
|
|
#if STACK_GROW_DIRECTION <= 0
|
2019-06-01 08:48:25 +03:00
|
|
|
volatile VALUE *const end = cont->machine.stack_src;
|
|
|
|
if (&space[0] > end) {
|
2009-01-06 13:09:54 +03:00
|
|
|
# ifdef HAVE_ALLOCA
|
2019-06-01 08:48:25 +03:00
|
|
|
volatile VALUE *sp = ALLOCA_N(VALUE, &space[0] - end);
|
2022-06-13 08:03:57 +03:00
|
|
|
// We need to make sure that the stack pointer is moved,
|
|
|
|
// but some compilers may remove the allocation by optimization.
|
|
|
|
// We hope that the following read/write will prevent such an optimization.
|
|
|
|
*sp = Qfalse;
|
2019-06-01 08:48:25 +03:00
|
|
|
space[0] = *sp;
|
2009-01-06 13:09:54 +03:00
|
|
|
# else
|
2019-06-01 08:48:25 +03:00
|
|
|
cont_restore_0(cont, &space[0]);
|
2009-01-06 13:09:54 +03:00
|
|
|
# endif
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
2009-01-06 13:09:54 +03:00
|
|
|
#endif
|
|
|
|
#if !STACK_GROW_DIRECTION
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* Stack grows upward */
|
2009-01-06 13:09:54 +03:00
|
|
|
#endif
|
|
|
|
#if STACK_GROW_DIRECTION >= 0
|
2019-06-01 08:48:25 +03:00
|
|
|
volatile VALUE *const end = cont->machine.stack_src + cont->machine.stack_size;
|
|
|
|
if (&space[STACK_PAD_SIZE] < end) {
|
2009-01-06 13:09:54 +03:00
|
|
|
# ifdef HAVE_ALLOCA
|
2019-06-01 08:48:25 +03:00
|
|
|
volatile VALUE *sp = ALLOCA_N(VALUE, end - &space[STACK_PAD_SIZE]);
|
|
|
|
space[0] = *sp;
|
2009-01-06 13:09:54 +03:00
|
|
|
# else
|
2019-06-01 08:48:25 +03:00
|
|
|
cont_restore_0(cont, &space[STACK_PAD_SIZE-1]);
|
2009-01-06 13:09:54 +03:00
|
|
|
# endif
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
2009-01-06 13:09:54 +03:00
|
|
|
#endif
|
|
|
|
#if !STACK_GROW_DIRECTION
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
2007-05-24 02:52:19 +04:00
|
|
|
#endif
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
cont_restore_1(cont);
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Document-class: Continuation
|
|
|
|
*
|
2012-07-14 12:31:21 +04:00
|
|
|
* Continuation objects are generated by Kernel#callcc,
|
|
|
|
* after having +require+d <i>continuation</i>. They hold
|
2010-10-27 04:26:29 +04:00
|
|
|
* a return address and execution context, allowing a nonlocal return
|
2019-03-22 14:04:59 +03:00
|
|
|
* to the end of the #callcc block from anywhere within a
|
2012-07-14 12:31:21 +04:00
|
|
|
* program. Continuations are somewhat analogous to a structured
|
2010-10-27 04:26:29 +04:00
|
|
|
* version of C's <code>setjmp/longjmp</code> (although they contain
|
|
|
|
* more state, so you might consider them closer to threads).
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2007-05-24 02:52:19 +04:00
|
|
|
* For instance:
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2010-10-27 04:26:29 +04:00
|
|
|
* require "continuation"
|
2007-05-24 02:52:19 +04:00
|
|
|
* arr = [ "Freddie", "Herbie", "Ron", "Max", "Ringo" ]
|
2010-10-27 04:26:29 +04:00
|
|
|
* callcc{|cc| $cc = cc}
|
2007-05-24 02:52:19 +04:00
|
|
|
* puts(message = arr.shift)
|
|
|
|
* $cc.call unless message =~ /Max/
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2007-05-24 02:52:19 +04:00
|
|
|
* <em>produces:</em>
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2007-05-24 02:52:19 +04:00
|
|
|
* Freddie
|
|
|
|
* Herbie
|
|
|
|
* Ron
|
|
|
|
* Max
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2015-09-20 04:07:40 +03:00
|
|
|
* Also you can call callcc in other methods:
|
|
|
|
*
|
|
|
|
* require "continuation"
|
|
|
|
*
|
|
|
|
* def g
|
|
|
|
* arr = [ "Freddie", "Herbie", "Ron", "Max", "Ringo" ]
|
|
|
|
* cc = callcc { |cc| cc }
|
|
|
|
* puts arr.shift
|
|
|
|
* return cc, arr.size
|
|
|
|
* end
|
|
|
|
*
|
|
|
|
* def f
|
|
|
|
* c, size = g
|
|
|
|
* c.call(c) if size > 1
|
|
|
|
* end
|
|
|
|
*
|
|
|
|
* f
|
|
|
|
*
|
2007-05-24 02:52:19 +04:00
|
|
|
* This (somewhat contrived) example allows the inner loop to abandon
|
|
|
|
* processing early:
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2010-10-27 04:26:29 +04:00
|
|
|
* require "continuation"
|
2007-05-24 02:52:19 +04:00
|
|
|
* callcc {|cont|
|
|
|
|
* for i in 0..4
|
2019-06-02 03:49:58 +03:00
|
|
|
* print "#{i}: "
|
2007-05-24 02:52:19 +04:00
|
|
|
* for j in i*5...(i+1)*5
|
|
|
|
* cont.call() if j == 17
|
|
|
|
* printf "%3d", j
|
|
|
|
* end
|
|
|
|
* end
|
|
|
|
* }
|
2010-10-27 04:26:29 +04:00
|
|
|
* puts
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2007-05-24 02:52:19 +04:00
|
|
|
* <em>produces:</em>
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2007-05-24 02:52:19 +04:00
|
|
|
* 0: 0 1 2 3 4
|
|
|
|
* 1: 5 6 7 8 9
|
|
|
|
* 2: 10 11 12 13 14
|
|
|
|
* 3: 15 16
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* call-seq:
|
2010-05-18 01:07:33 +04:00
|
|
|
* callcc {|cont| block } -> obj
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2012-07-14 12:31:21 +04:00
|
|
|
* Generates a Continuation object, which it passes to
|
2010-10-27 04:26:29 +04:00
|
|
|
* the associated block. You need to <code>require
|
|
|
|
* 'continuation'</code> before using this method. Performing a
|
2012-07-14 12:31:21 +04:00
|
|
|
* <em>cont</em><code>.call</code> will cause the #callcc
|
2010-10-27 04:26:29 +04:00
|
|
|
* to return (as will falling through the end of the block). The
|
2012-07-14 12:31:21 +04:00
|
|
|
* value returned by the #callcc is the value of the
|
2010-10-27 04:26:29 +04:00
|
|
|
* block, or the value passed to <em>cont</em><code>.call</code>. See
|
2012-07-14 12:31:21 +04:00
|
|
|
* class Continuation for more details. Also see
|
|
|
|
* Kernel#throw for an alternative mechanism for
|
2010-10-27 04:26:29 +04:00
|
|
|
* unwinding a call stack.
|
2007-05-24 02:52:19 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
rb_callcc(VALUE self)
|
|
|
|
{
|
|
|
|
volatile int called;
|
|
|
|
volatile VALUE val = cont_capture(&called);
|
|
|
|
|
|
|
|
if (called) {
|
2019-06-01 08:48:25 +03:00
|
|
|
return val;
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
else {
|
2019-06-01 08:48:25 +03:00
|
|
|
return rb_yield(val);
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
static VALUE
|
2014-06-18 10:16:39 +04:00
|
|
|
make_passing_arg(int argc, const VALUE *argv)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
2012-12-29 16:22:04 +04:00
|
|
|
switch (argc) {
|
2018-12-28 16:03:09 +03:00
|
|
|
case -1:
|
2019-06-01 08:48:25 +03:00
|
|
|
return argv[0];
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
case 0:
|
2019-06-01 08:48:25 +03:00
|
|
|
return Qnil;
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
case 1:
|
2019-06-01 08:48:25 +03:00
|
|
|
return argv[0];
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
default:
|
2019-06-01 08:48:25 +03:00
|
|
|
return rb_ary_new4(argc, argv);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 09:20:15 +03:00
|
|
|
typedef VALUE e_proc(VALUE);
|
|
|
|
|
2013-11-15 21:15:31 +04:00
|
|
|
/* CAUTION!! : Currently, error in rollback_func is not supported */
|
|
|
|
/* same as rb_protect if set rollback_func to NULL */
|
|
|
|
void
|
2019-08-26 09:20:15 +03:00
|
|
|
ruby_register_rollback_func_for_ensure(e_proc *ensure_func, e_proc *rollback_func)
|
2013-11-15 21:15:31 +04:00
|
|
|
{
|
|
|
|
st_table **table_p = &GET_VM()->ensure_rollback_table;
|
|
|
|
if (UNLIKELY(*table_p == NULL)) {
|
2019-06-01 08:48:25 +03:00
|
|
|
*table_p = st_init_numtable();
|
2013-11-15 21:15:31 +04:00
|
|
|
}
|
|
|
|
st_insert(*table_p, (st_data_t)ensure_func, (st_data_t)rollback_func);
|
|
|
|
}
|
|
|
|
|
2019-08-26 09:20:15 +03:00
|
|
|
static inline e_proc *
|
|
|
|
lookup_rollback_func(e_proc *ensure_func)
|
2013-11-15 21:15:31 +04:00
|
|
|
{
|
|
|
|
st_table *table = GET_VM()->ensure_rollback_table;
|
|
|
|
st_data_t val;
|
|
|
|
if (table && st_lookup(table, (st_data_t)ensure_func, &val))
|
2019-08-26 09:20:15 +03:00
|
|
|
return (e_proc *) val;
|
|
|
|
return (e_proc *) Qundef;
|
2013-11-15 21:15:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
rollback_ensure_stack(VALUE self,rb_ensure_list_t *current,rb_ensure_entry_t *target)
|
|
|
|
{
|
|
|
|
rb_ensure_list_t *p;
|
|
|
|
rb_ensure_entry_t *entry;
|
2018-11-13 03:40:52 +03:00
|
|
|
size_t i, j;
|
2013-11-15 21:15:31 +04:00
|
|
|
size_t cur_size;
|
|
|
|
size_t target_size;
|
|
|
|
size_t base_point;
|
2019-08-26 09:20:15 +03:00
|
|
|
e_proc *func;
|
2013-11-15 21:15:31 +04:00
|
|
|
|
|
|
|
cur_size = 0;
|
|
|
|
for (p=current; p; p=p->next)
|
2019-06-01 08:48:25 +03:00
|
|
|
cur_size++;
|
2013-11-15 21:15:31 +04:00
|
|
|
target_size = 0;
|
|
|
|
for (entry=target; entry->marker; entry++)
|
2019-06-01 08:48:25 +03:00
|
|
|
target_size++;
|
2013-11-15 21:15:31 +04:00
|
|
|
|
|
|
|
/* search common stack point */
|
|
|
|
p = current;
|
|
|
|
base_point = cur_size;
|
|
|
|
while (base_point) {
|
2019-06-01 08:48:25 +03:00
|
|
|
if (target_size >= base_point &&
|
|
|
|
p->entry.marker == target[target_size - base_point].marker)
|
|
|
|
break;
|
|
|
|
base_point --;
|
|
|
|
p = p->next;
|
2013-11-15 21:15:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* rollback function check */
|
|
|
|
for (i=0; i < target_size - base_point; i++) {
|
2019-06-01 08:48:25 +03:00
|
|
|
if (!lookup_rollback_func(target[i].e_proc)) {
|
|
|
|
rb_raise(rb_eRuntimeError, "continuation called from out of critical rb_ensure scope");
|
|
|
|
}
|
2013-11-15 21:15:31 +04:00
|
|
|
}
|
|
|
|
/* pop ensure stack */
|
|
|
|
while (cur_size > base_point) {
|
2019-06-01 08:48:25 +03:00
|
|
|
/* escape from ensure block */
|
|
|
|
(*current->entry.e_proc)(current->entry.data2);
|
|
|
|
current = current->next;
|
|
|
|
cur_size--;
|
2013-11-15 21:15:31 +04:00
|
|
|
}
|
|
|
|
/* push ensure stack */
|
2018-11-13 03:40:52 +03:00
|
|
|
for (j = 0; j < i; j++) {
|
2019-08-26 09:20:15 +03:00
|
|
|
func = lookup_rollback_func(target[i - j - 1].e_proc);
|
2022-11-15 07:24:08 +03:00
|
|
|
if (!UNDEF_P((VALUE)func)) {
|
2018-11-13 03:40:52 +03:00
|
|
|
(*func)(target[i - j - 1].data2);
|
|
|
|
}
|
2013-11-15 21:15:31 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 18:24:14 +03:00
|
|
|
NORETURN(static VALUE rb_cont_call(int argc, VALUE *argv, VALUE contval));
|
|
|
|
|
2007-05-24 02:52:19 +04:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* cont.call(args, ...)
|
|
|
|
* cont[args, ...]
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2019-03-22 14:04:59 +03:00
|
|
|
* Invokes the continuation. The program continues from the end of
|
|
|
|
* the #callcc block. If no arguments are given, the original #callcc
|
|
|
|
* returns +nil+. If one argument is given, #callcc returns
|
|
|
|
* it. Otherwise, an array containing <i>args</i> is returned.
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2007-05-24 02:52:19 +04:00
|
|
|
* callcc {|cont| cont.call } #=> nil
|
|
|
|
* callcc {|cont| cont.call 1 } #=> 1
|
|
|
|
* callcc {|cont| cont.call 1, 2, 3 } #=> [1, 2, 3]
|
|
|
|
*/
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
rb_cont_call(int argc, VALUE *argv, VALUE contval)
|
|
|
|
{
|
2018-08-21 04:01:37 +03:00
|
|
|
rb_context_t *cont = cont_ptr(contval);
|
2007-05-24 02:52:19 +04:00
|
|
|
rb_thread_t *th = GET_THREAD();
|
|
|
|
|
2017-09-10 22:00:08 +03:00
|
|
|
if (cont_thread_value(cont) != th->self) {
|
2019-06-01 08:48:25 +03:00
|
|
|
rb_raise(rb_eRuntimeError, "continuation called across threads");
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
2017-11-06 08:41:48 +03:00
|
|
|
if (cont->saved_ec.fiber_ptr) {
|
2019-06-01 08:48:25 +03:00
|
|
|
if (th->ec->fiber_ptr != cont->saved_ec.fiber_ptr) {
|
|
|
|
rb_raise(rb_eRuntimeError, "continuation called across fiber");
|
|
|
|
}
|
2007-06-06 05:55:09 +04:00
|
|
|
}
|
2017-10-26 11:32:49 +03:00
|
|
|
rollback_ensure_stack(contval, th->ec->ensure_list, cont->ensure_array);
|
2007-05-24 02:52:19 +04:00
|
|
|
|
2008-12-01 06:00:48 +03:00
|
|
|
cont->argc = argc;
|
2007-06-02 11:48:29 +04:00
|
|
|
cont->value = make_passing_arg(argc, argv);
|
2007-05-24 02:52:19 +04:00
|
|
|
|
2007-08-23 11:55:37 +04:00
|
|
|
cont_restore_0(cont, &contval);
|
2020-05-10 18:24:14 +03:00
|
|
|
UNREACHABLE_RETURN(Qnil);
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
/*********/
|
|
|
|
/* fiber */
|
|
|
|
/*********/
|
|
|
|
|
2008-12-27 03:25:47 +03:00
|
|
|
/*
|
|
|
|
* Document-class: Fiber
|
|
|
|
*
|
|
|
|
* Fibers are primitives for implementing light weight cooperative
|
2009-02-22 17:23:33 +03:00
|
|
|
* concurrency in Ruby. Basically they are a means of creating code blocks
|
|
|
|
* that can be paused and resumed, much like threads. The main difference
|
|
|
|
* is that they are never preempted and that the scheduling must be done by
|
|
|
|
* the programmer and not the VM.
|
2008-12-27 03:25:47 +03:00
|
|
|
*
|
|
|
|
* As opposed to other stackless light weight concurrency models, each fiber
|
2016-12-27 11:52:32 +03:00
|
|
|
* comes with a stack. This enables the fiber to be paused from deeply
|
|
|
|
* nested function calls within the fiber block. See the ruby(1)
|
|
|
|
* manpage to configure the size of the fiber stack(s).
|
2008-12-27 03:25:47 +03:00
|
|
|
*
|
2015-12-10 08:16:17 +03:00
|
|
|
* When a fiber is created it will not run automatically. Rather it must
|
2019-03-22 14:04:59 +03:00
|
|
|
* be explicitly asked to run using the Fiber#resume method.
|
2009-02-22 17:23:33 +03:00
|
|
|
* The code running inside the fiber can give up control by calling
|
2019-03-22 14:04:59 +03:00
|
|
|
* Fiber.yield in which case it yields control back to caller (the
|
|
|
|
* caller of the Fiber#resume).
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
|
|
|
* Upon yielding or termination the Fiber returns the value of the last
|
2008-12-27 03:25:47 +03:00
|
|
|
* executed expression
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
* For instance:
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
* fiber = Fiber.new do
|
|
|
|
* Fiber.yield 1
|
|
|
|
* 2
|
|
|
|
* end
|
|
|
|
*
|
|
|
|
* puts fiber.resume
|
|
|
|
* puts fiber.resume
|
|
|
|
* puts fiber.resume
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
* <em>produces</em>
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
* 1
|
|
|
|
* 2
|
|
|
|
* FiberError: dead fiber called
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2019-03-22 14:04:59 +03:00
|
|
|
* The Fiber#resume method accepts an arbitrary number of parameters,
|
|
|
|
* if it is the first call to #resume then they will be passed as
|
|
|
|
* block arguments. Otherwise they will be the return value of the
|
|
|
|
* call to Fiber.yield
|
2008-12-27 03:25:47 +03:00
|
|
|
*
|
|
|
|
* Example:
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
* fiber = Fiber.new do |first|
|
|
|
|
* second = Fiber.yield first + 2
|
|
|
|
* end
|
|
|
|
*
|
|
|
|
* puts fiber.resume 10
|
2019-08-17 08:24:45 +03:00
|
|
|
* puts fiber.resume 1_000_000
|
|
|
|
* puts fiber.resume "The fiber will be dead before I can cause trouble"
|
2008-12-27 03:25:47 +03:00
|
|
|
*
|
|
|
|
* <em>produces</em>
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
* 12
|
2019-08-17 08:24:45 +03:00
|
|
|
* 1000000
|
2008-12-27 03:25:47 +03:00
|
|
|
* FiberError: dead fiber called
|
|
|
|
*
|
2020-12-12 19:55:17 +03:00
|
|
|
* == Non-blocking Fibers
|
|
|
|
*
|
2020-12-24 12:00:23 +03:00
|
|
|
* The concept of <em>non-blocking fiber</em> was introduced in Ruby 3.0.
|
|
|
|
* A non-blocking fiber, when reaching a operation that would normally block
|
|
|
|
* the fiber (like <code>sleep</code>, or wait for another process or I/O)
|
2021-07-17 07:37:28 +03:00
|
|
|
* will yield control to other fibers and allow the <em>scheduler</em> to
|
|
|
|
* handle blocking and waking up (resuming) this fiber when it can proceed.
|
2020-12-24 12:00:23 +03:00
|
|
|
*
|
|
|
|
* For a Fiber to behave as non-blocking, it need to be created in Fiber.new with
|
|
|
|
* <tt>blocking: false</tt> (which is the default), and Fiber.scheduler
|
2020-12-12 19:55:17 +03:00
|
|
|
* should be set with Fiber.set_scheduler. If Fiber.scheduler is not set in
|
2020-12-24 12:00:23 +03:00
|
|
|
* the current thread, blocking and non-blocking fibers' behavior is identical.
|
2020-12-12 19:55:17 +03:00
|
|
|
*
|
|
|
|
* Ruby doesn't provide a scheduler class: it is expected to be implemented by
|
2022-12-21 23:10:21 +03:00
|
|
|
* the user and correspond to Fiber::Scheduler.
|
2020-12-12 19:55:17 +03:00
|
|
|
*
|
|
|
|
* There is also Fiber.schedule method, which is expected to immediately perform
|
2020-12-24 12:00:23 +03:00
|
|
|
* the given block in a non-blocking manner. Its actual implementation is up to
|
|
|
|
* the scheduler.
|
2020-12-12 19:55:17 +03:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
*/
|
|
|
|
|
2009-09-08 19:27:31 +04:00
|
|
|
static const rb_data_type_t fiber_data_type = {
|
|
|
|
"fiber",
|
2019-05-31 23:25:24 +03:00
|
|
|
{fiber_mark, fiber_free, fiber_memsize, fiber_compact,},
|
2014-12-01 09:38:04 +03:00
|
|
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
2009-09-08 19:27:31 +04:00
|
|
|
};
|
|
|
|
|
2008-11-28 18:23:09 +03:00
|
|
|
static VALUE
|
2007-11-09 04:11:49 +03:00
|
|
|
fiber_alloc(VALUE klass)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
2009-09-08 19:27:31 +04:00
|
|
|
return TypedData_Wrap_Struct(klass, &fiber_data_type, 0);
|
2008-11-28 18:23:09 +03:00
|
|
|
}
|
2007-11-09 04:11:49 +03:00
|
|
|
|
2008-11-28 18:23:09 +03:00
|
|
|
static rb_fiber_t*
|
2020-05-14 13:10:55 +03:00
|
|
|
fiber_t_alloc(VALUE fiber_value, unsigned int blocking)
|
2008-11-28 18:23:09 +03:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_t *fiber;
|
2009-09-21 05:13:24 +04:00
|
|
|
rb_thread_t *th = GET_THREAD();
|
2008-11-28 18:23:09 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
if (DATA_PTR(fiber_value) != 0) {
|
2019-06-01 08:48:25 +03:00
|
|
|
rb_raise(rb_eRuntimeError, "cannot initialize twice");
|
2010-11-03 20:08:35 +03:00
|
|
|
}
|
|
|
|
|
2009-09-21 05:13:24 +04:00
|
|
|
THREAD_MUST_BE_RUNNING(th);
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber = ZALLOC(rb_fiber_t);
|
|
|
|
fiber->cont.self = fiber_value;
|
|
|
|
fiber->cont.type = FIBER_CONTEXT;
|
2020-05-14 13:10:55 +03:00
|
|
|
fiber->blocking = blocking;
|
2023-05-18 17:33:42 +03:00
|
|
|
fiber->killed = 0;
|
2019-07-08 08:59:28 +03:00
|
|
|
cont_init(&fiber->cont, th);
|
2019-07-19 06:45:44 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber->cont.saved_ec.fiber_ptr = fiber;
|
2019-07-19 06:45:44 +03:00
|
|
|
rb_ec_clear_vm_stack(&fiber->cont.saved_ec);
|
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber->prev = NULL;
|
2017-08-10 04:47:13 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
/* fiber->status == 0 == CREATED
|
|
|
|
* So that we don't need to set status: fiber_status_set(fiber, FIBER_CREATED); */
|
|
|
|
VM_ASSERT(FIBER_CREATED_P(fiber));
|
2007-11-09 04:11:49 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
DATA_PTR(fiber_value) = fiber;
|
2008-11-28 18:23:09 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
return fiber;
|
2007-11-09 04:11:49 +03:00
|
|
|
}
|
|
|
|
|
2022-12-01 13:00:33 +03:00
|
|
|
static rb_fiber_t *
|
|
|
|
root_fiber_alloc(rb_thread_t *th)
|
|
|
|
{
|
|
|
|
VALUE fiber_value = fiber_alloc(rb_cFiber);
|
|
|
|
rb_fiber_t *fiber = th->ec->fiber_ptr;
|
|
|
|
|
|
|
|
VM_ASSERT(DATA_PTR(fiber_value) == NULL);
|
|
|
|
VM_ASSERT(fiber->cont.type == FIBER_CONTEXT);
|
|
|
|
VM_ASSERT(FIBER_RESUMED_P(fiber));
|
|
|
|
|
|
|
|
th->root_fiber = fiber;
|
|
|
|
DATA_PTR(fiber_value) = fiber;
|
|
|
|
fiber->cont.self = fiber_value;
|
|
|
|
|
|
|
|
coroutine_initialize_main(&fiber->context);
|
|
|
|
|
|
|
|
return fiber;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline rb_fiber_t*
|
|
|
|
fiber_current(void)
|
|
|
|
{
|
|
|
|
rb_execution_context_t *ec = GET_EC();
|
|
|
|
if (ec->fiber_ptr->cont.self == 0) {
|
|
|
|
root_fiber_alloc(rb_ec_thread_ptr(ec));
|
|
|
|
}
|
|
|
|
return ec->fiber_ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline VALUE
|
|
|
|
current_fiber_storage(void)
|
|
|
|
{
|
|
|
|
rb_execution_context_t *ec = GET_EC();
|
|
|
|
return ec->storage;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline VALUE
|
|
|
|
inherit_fiber_storage(void)
|
|
|
|
{
|
|
|
|
return rb_obj_dup(current_fiber_storage());
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
fiber_storage_set(struct rb_fiber_struct *fiber, VALUE storage)
|
|
|
|
{
|
|
|
|
fiber->cont.saved_ec.storage = storage;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline VALUE
|
2023-02-25 09:27:11 +03:00
|
|
|
fiber_storage_get(rb_fiber_t *fiber, int allocate)
|
2022-12-01 13:00:33 +03:00
|
|
|
{
|
|
|
|
VALUE storage = fiber->cont.saved_ec.storage;
|
2023-02-25 09:27:11 +03:00
|
|
|
if (storage == Qnil && allocate) {
|
2022-12-01 13:00:33 +03:00
|
|
|
storage = rb_hash_new();
|
|
|
|
fiber_storage_set(fiber, storage);
|
|
|
|
}
|
|
|
|
return storage;
|
|
|
|
}
|
|
|
|
|
2022-12-21 08:32:05 +03:00
|
|
|
static void
|
|
|
|
storage_access_must_be_from_same_fiber(VALUE self)
|
|
|
|
{
|
2022-12-20 20:21:43 +03:00
|
|
|
rb_fiber_t *fiber = fiber_ptr(self);
|
|
|
|
rb_fiber_t *current = fiber_current();
|
|
|
|
if (fiber != current) {
|
|
|
|
rb_raise(rb_eArgError, "Fiber storage can only be accessed from the Fiber it belongs to");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-01 13:00:33 +03:00
|
|
|
/**
|
2022-12-21 23:10:21 +03:00
|
|
|
* call-seq: fiber.storage -> hash (dup)
|
2022-12-01 13:00:33 +03:00
|
|
|
*
|
2022-12-21 23:10:21 +03:00
|
|
|
* Returns a copy of the storage hash for the fiber. The method can only be called on the
|
|
|
|
* Fiber.current.
|
2022-12-01 13:00:33 +03:00
|
|
|
*/
|
|
|
|
static VALUE
|
|
|
|
rb_fiber_storage_get(VALUE self)
|
|
|
|
{
|
2022-12-20 20:21:43 +03:00
|
|
|
storage_access_must_be_from_same_fiber(self);
|
2023-02-25 09:27:11 +03:00
|
|
|
|
|
|
|
VALUE storage = fiber_storage_get(fiber_ptr(self), FALSE);
|
|
|
|
|
|
|
|
if (storage == Qnil) {
|
|
|
|
return Qnil;
|
2023-02-26 07:20:43 +03:00
|
|
|
}
|
|
|
|
else {
|
2023-02-25 09:27:11 +03:00
|
|
|
return rb_obj_dup(storage);
|
|
|
|
}
|
2022-12-01 13:00:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
fiber_storage_validate_each(VALUE key, VALUE value, VALUE _argument)
|
|
|
|
{
|
2023-08-24 06:19:33 +03:00
|
|
|
Check_Type(key, T_SYMBOL);
|
2022-12-01 13:00:33 +03:00
|
|
|
|
|
|
|
return ST_CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
fiber_storage_validate(VALUE value)
|
|
|
|
{
|
2022-12-10 03:54:53 +03:00
|
|
|
// nil is an allowed value and will be lazily initialized.
|
|
|
|
if (value == Qnil) return;
|
|
|
|
|
2022-12-01 13:00:33 +03:00
|
|
|
if (!RB_TYPE_P(value, T_HASH)) {
|
|
|
|
rb_raise(rb_eTypeError, "storage must be a hash");
|
|
|
|
}
|
|
|
|
|
2022-12-10 03:54:53 +03:00
|
|
|
if (RB_OBJ_FROZEN(value)) {
|
|
|
|
rb_raise(rb_eFrozenError, "storage must not be frozen");
|
|
|
|
}
|
|
|
|
|
2022-12-01 13:00:33 +03:00
|
|
|
rb_hash_foreach(value, fiber_storage_validate_each, Qundef);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-12-21 23:10:21 +03:00
|
|
|
* call-seq: fiber.storage = hash
|
2022-12-01 13:00:33 +03:00
|
|
|
*
|
2022-12-21 23:10:21 +03:00
|
|
|
* Sets the storage hash for the fiber. This feature is experimental
|
|
|
|
* and may change in the future. The method can only be called on the
|
|
|
|
* Fiber.current.
|
2022-12-01 13:00:33 +03:00
|
|
|
*
|
|
|
|
* You should be careful about using this method as you may inadvertently clear
|
|
|
|
* important fiber-storage state. You should mostly prefer to assign specific
|
2022-12-21 23:10:21 +03:00
|
|
|
* keys in the storage using Fiber::[]=.
|
2022-12-01 13:00:33 +03:00
|
|
|
*
|
2022-12-21 23:10:21 +03:00
|
|
|
* You can also use <tt>Fiber.new(storage: nil)</tt> to create a fiber with an empty
|
2022-12-01 13:00:33 +03:00
|
|
|
* storage.
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
*
|
|
|
|
* while request = request_queue.pop
|
|
|
|
* # Reset the per-request state:
|
|
|
|
* Fiber.current.storage = nil
|
|
|
|
* handle_request(request)
|
|
|
|
* end
|
|
|
|
*/
|
2007-11-09 04:11:49 +03:00
|
|
|
static VALUE
|
2022-12-01 13:00:33 +03:00
|
|
|
rb_fiber_storage_set(VALUE self, VALUE value)
|
2019-06-02 03:49:58 +03:00
|
|
|
{
|
2022-12-20 20:10:57 +03:00
|
|
|
if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) {
|
|
|
|
rb_category_warn(RB_WARN_CATEGORY_EXPERIMENTAL,
|
|
|
|
"Fiber#storage= is experimental and may be removed in the future!");
|
|
|
|
}
|
|
|
|
|
2022-12-20 20:21:43 +03:00
|
|
|
storage_access_must_be_from_same_fiber(self);
|
2022-12-01 13:00:33 +03:00
|
|
|
fiber_storage_validate(value);
|
|
|
|
|
|
|
|
fiber_ptr(self)->cont.saved_ec.storage = rb_obj_dup(value);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* call-seq: Fiber[key] -> value
|
|
|
|
*
|
2022-12-21 01:02:25 +03:00
|
|
|
* Returns the value of the fiber storage variable identified by +key+.
|
2022-12-01 13:00:33 +03:00
|
|
|
*
|
|
|
|
* The +key+ must be a symbol, and the value is set by Fiber#[]= or
|
|
|
|
* Fiber#store.
|
|
|
|
*
|
2022-12-21 23:10:21 +03:00
|
|
|
* See also Fiber::[]=.
|
2022-12-01 13:00:33 +03:00
|
|
|
*/
|
|
|
|
static VALUE
|
|
|
|
rb_fiber_storage_aref(VALUE class, VALUE key)
|
|
|
|
{
|
2023-08-24 06:19:33 +03:00
|
|
|
Check_Type(key, T_SYMBOL);
|
2022-12-01 13:00:33 +03:00
|
|
|
|
2023-02-25 09:27:11 +03:00
|
|
|
VALUE storage = fiber_storage_get(fiber_current(), FALSE);
|
2022-12-01 13:00:33 +03:00
|
|
|
if (storage == Qnil) return Qnil;
|
|
|
|
|
|
|
|
return rb_hash_aref(storage, key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* call-seq: Fiber[key] = value
|
|
|
|
*
|
2022-12-21 01:02:25 +03:00
|
|
|
* Assign +value+ to the fiber storage variable identified by +key+.
|
2022-12-01 13:00:33 +03:00
|
|
|
* The variable is created if it doesn't exist.
|
|
|
|
*
|
|
|
|
* +key+ must be a Symbol, otherwise a TypeError is raised.
|
|
|
|
*
|
2022-12-21 23:10:21 +03:00
|
|
|
* See also Fiber::[].
|
2022-12-01 13:00:33 +03:00
|
|
|
*/
|
|
|
|
static VALUE
|
|
|
|
rb_fiber_storage_aset(VALUE class, VALUE key, VALUE value)
|
|
|
|
{
|
2023-08-24 06:19:33 +03:00
|
|
|
Check_Type(key, T_SYMBOL);
|
2022-12-01 13:00:33 +03:00
|
|
|
|
2023-02-25 09:27:11 +03:00
|
|
|
VALUE storage = fiber_storage_get(fiber_current(), value != Qnil);
|
|
|
|
if (storage == Qnil) return Qnil;
|
2022-12-01 13:00:33 +03:00
|
|
|
|
2023-02-25 09:27:11 +03:00
|
|
|
if (value == Qnil) {
|
|
|
|
return rb_hash_delete(storage, key);
|
2023-02-26 07:20:43 +03:00
|
|
|
}
|
|
|
|
else {
|
2023-02-25 09:27:11 +03:00
|
|
|
return rb_hash_aset(storage, key, value);
|
|
|
|
}
|
2022-12-01 13:00:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
fiber_initialize(VALUE self, VALUE proc, struct fiber_pool * fiber_pool, unsigned int blocking, VALUE storage)
|
|
|
|
{
|
|
|
|
if (storage == Qundef || storage == Qtrue) {
|
|
|
|
// The default, inherit storage (dup) from the current fiber:
|
|
|
|
storage = inherit_fiber_storage();
|
|
|
|
}
|
|
|
|
else /* nil, hash, etc. */ {
|
|
|
|
fiber_storage_validate(storage);
|
|
|
|
storage = rb_obj_dup(storage);
|
|
|
|
}
|
|
|
|
|
2020-05-14 13:10:55 +03:00
|
|
|
rb_fiber_t *fiber = fiber_t_alloc(self, blocking);
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2022-12-01 13:00:33 +03:00
|
|
|
fiber->cont.saved_ec.storage = storage;
|
2019-06-02 03:49:58 +03:00
|
|
|
fiber->first_proc = proc;
|
|
|
|
fiber->stack.base = NULL;
|
|
|
|
fiber->stack.pool = fiber_pool;
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
fiber_prepare_stack(rb_fiber_t *fiber)
|
2007-11-09 04:11:49 +03:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_context_t *cont = &fiber->cont;
|
2017-09-10 22:00:08 +03:00
|
|
|
rb_execution_context_t *sec = &cont->saved_ec;
|
2019-06-02 03:49:58 +03:00
|
|
|
|
|
|
|
size_t vm_stack_size = 0;
|
2019-06-27 09:59:25 +03:00
|
|
|
VALUE *vm_stack = fiber_initialize_coroutine(fiber, &vm_stack_size);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
|
2008-10-22 19:12:07 +04:00
|
|
|
/* initialize cont */
|
2017-08-22 03:41:24 +03:00
|
|
|
cont->saved_vm_stack.ptr = NULL;
|
2019-06-02 03:49:58 +03:00
|
|
|
rb_ec_initialize_vm_stack(sec, vm_stack, vm_stack_size / sizeof(VALUE));
|
2016-07-28 14:02:30 +03:00
|
|
|
|
2017-09-10 22:00:08 +03:00
|
|
|
sec->tag = NULL;
|
|
|
|
sec->local_storage = NULL;
|
|
|
|
sec->local_storage_recursive_hash = Qnil;
|
|
|
|
sec->local_storage_recursive_hash_for_trace = Qnil;
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2020-05-14 13:10:55 +03:00
|
|
|
static struct fiber_pool *
|
|
|
|
rb_fiber_pool_default(VALUE pool)
|
|
|
|
{
|
|
|
|
return &shared_fiber_pool;
|
|
|
|
}
|
|
|
|
|
2022-12-01 13:00:33 +03:00
|
|
|
VALUE rb_fiber_inherit_storage(struct rb_execution_context_struct *ec, struct rb_fiber_struct *fiber)
|
|
|
|
{
|
|
|
|
VALUE storage = rb_obj_dup(ec->storage);
|
|
|
|
fiber->cont.saved_ec.storage = storage;
|
|
|
|
return storage;
|
|
|
|
}
|
|
|
|
|
2020-05-14 13:10:55 +03:00
|
|
|
/* :nodoc: */
|
|
|
|
static VALUE
|
|
|
|
rb_fiber_initialize_kw(int argc, VALUE* argv, VALUE self, int kw_splat)
|
|
|
|
{
|
|
|
|
VALUE pool = Qnil;
|
2020-10-16 04:25:58 +03:00
|
|
|
VALUE blocking = Qfalse;
|
2022-12-01 13:00:33 +03:00
|
|
|
VALUE storage = Qundef;
|
2020-05-14 13:10:55 +03:00
|
|
|
|
|
|
|
if (kw_splat != RB_NO_KEYWORDS) {
|
2020-11-12 13:27:20 +03:00
|
|
|
VALUE options = Qnil;
|
2022-12-01 13:00:33 +03:00
|
|
|
VALUE arguments[3] = {Qundef};
|
2020-11-12 13:27:20 +03:00
|
|
|
|
|
|
|
argc = rb_scan_args_kw(kw_splat, argc, argv, ":", &options);
|
2022-12-01 13:00:33 +03:00
|
|
|
rb_get_kwargs(options, fiber_initialize_keywords, 0, 3, arguments);
|
2020-11-12 13:27:20 +03:00
|
|
|
|
2022-11-15 07:24:08 +03:00
|
|
|
if (!UNDEF_P(arguments[0])) {
|
2020-11-12 13:27:20 +03:00
|
|
|
blocking = arguments[0];
|
|
|
|
}
|
|
|
|
|
2022-11-15 07:24:08 +03:00
|
|
|
if (!UNDEF_P(arguments[1])) {
|
2020-11-12 13:27:20 +03:00
|
|
|
pool = arguments[1];
|
|
|
|
}
|
2022-12-01 13:00:33 +03:00
|
|
|
|
|
|
|
storage = arguments[2];
|
2020-05-14 13:10:55 +03:00
|
|
|
}
|
|
|
|
|
2022-12-01 13:00:33 +03:00
|
|
|
return fiber_initialize(self, rb_block_proc(), rb_fiber_pool_default(pool), RTEST(blocking), storage);
|
2020-05-14 13:10:55 +03:00
|
|
|
}
|
|
|
|
|
2020-12-12 19:55:17 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
2022-12-01 13:00:33 +03:00
|
|
|
* Fiber.new(blocking: false, storage: true) { |*args| ... } -> fiber
|
2020-12-12 19:55:17 +03:00
|
|
|
*
|
2022-12-01 13:00:33 +03:00
|
|
|
* Creates new Fiber. Initially, the fiber is not running and can be resumed
|
|
|
|
* with #resume. Arguments to the first #resume call will be passed to the
|
|
|
|
* block:
|
2020-12-12 19:55:17 +03:00
|
|
|
*
|
2022-12-01 13:00:33 +03:00
|
|
|
* f = Fiber.new do |initial|
|
|
|
|
* current = initial
|
|
|
|
* loop do
|
|
|
|
* puts "current: #{current.inspect}"
|
|
|
|
* current = Fiber.yield
|
|
|
|
* end
|
|
|
|
* end
|
|
|
|
* f.resume(100) # prints: current: 100
|
|
|
|
* f.resume(1, 2, 3) # prints: current: [1, 2, 3]
|
|
|
|
* f.resume # prints: current: nil
|
|
|
|
* # ... and so on ...
|
|
|
|
*
|
|
|
|
* If <tt>blocking: false</tt> is passed to <tt>Fiber.new</tt>, _and_ current
|
|
|
|
* thread has a Fiber.scheduler defined, the Fiber becomes non-blocking (see
|
|
|
|
* "Non-blocking Fibers" section in class docs).
|
|
|
|
*
|
|
|
|
* If the <tt>storage</tt> is unspecified, the default is to inherit a copy of
|
|
|
|
* the storage from the current fiber. This is the same as specifying
|
|
|
|
* <tt>storage: true</tt>.
|
|
|
|
*
|
|
|
|
* Fiber[:x] = 1
|
|
|
|
* Fiber.new do
|
|
|
|
* Fiber[:x] # => 1
|
|
|
|
* Fiber[:x] = 2
|
|
|
|
* end.resume
|
|
|
|
* Fiber[:x] # => 1
|
|
|
|
*
|
|
|
|
* If the given <tt>storage</tt> is <tt>nil</tt>, this function will lazy
|
|
|
|
* initialize the internal storage, which starts as an empty hash.
|
|
|
|
*
|
|
|
|
* Fiber[:x] = "Hello World"
|
|
|
|
* Fiber.new(storage: nil) do
|
|
|
|
* Fiber[:x] # nil
|
|
|
|
* end
|
|
|
|
*
|
|
|
|
* Otherwise, the given <tt>storage</tt> is used as the new fiber's storage,
|
|
|
|
* and it must be an instance of Hash.
|
|
|
|
*
|
2022-12-21 08:32:05 +03:00
|
|
|
* Explicitly using <tt>storage: true</tt> is currently experimental and may
|
2022-12-01 13:00:33 +03:00
|
|
|
* change in the future.
|
2020-12-12 19:55:17 +03:00
|
|
|
*/
|
2008-11-28 18:23:09 +03:00
|
|
|
static VALUE
|
2019-06-02 03:49:58 +03:00
|
|
|
rb_fiber_initialize(int argc, VALUE* argv, VALUE self)
|
2007-08-26 07:31:20 +04:00
|
|
|
{
|
2020-05-14 13:10:55 +03:00
|
|
|
return rb_fiber_initialize_kw(argc, argv, self, rb_keyword_given_p());
|
2007-08-26 07:31:20 +04:00
|
|
|
}
|
|
|
|
|
2022-12-01 13:00:33 +03:00
|
|
|
VALUE
|
|
|
|
rb_fiber_new_storage(rb_block_call_func_t func, VALUE obj, VALUE storage)
|
|
|
|
{
|
|
|
|
return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 1, storage);
|
|
|
|
}
|
|
|
|
|
2008-11-28 18:23:09 +03:00
|
|
|
VALUE
|
2019-08-26 09:35:28 +03:00
|
|
|
rb_fiber_new(rb_block_call_func_t func, VALUE obj)
|
2007-08-26 07:31:20 +04:00
|
|
|
{
|
2022-12-01 13:00:33 +03:00
|
|
|
return rb_fiber_new_storage(func, obj, Qtrue);
|
2020-05-14 13:10:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2021-03-12 12:15:56 +03:00
|
|
|
rb_fiber_s_schedule_kw(int argc, VALUE* argv, int kw_splat)
|
2020-05-14 13:10:55 +03:00
|
|
|
{
|
|
|
|
rb_thread_t * th = GET_THREAD();
|
|
|
|
VALUE scheduler = th->scheduler;
|
|
|
|
VALUE fiber = Qnil;
|
|
|
|
|
|
|
|
if (scheduler != Qnil) {
|
2022-10-15 11:43:45 +03:00
|
|
|
fiber = rb_fiber_scheduler_fiber(scheduler, argc, argv, kw_splat);
|
2020-12-28 13:52:14 +03:00
|
|
|
}
|
|
|
|
else {
|
2020-05-14 13:10:55 +03:00
|
|
|
rb_raise(rb_eRuntimeError, "No scheduler is available!");
|
|
|
|
}
|
|
|
|
|
|
|
|
return fiber;
|
|
|
|
}
|
|
|
|
|
2020-12-12 19:55:17 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* Fiber.schedule { |*args| ... } -> fiber
|
|
|
|
*
|
|
|
|
* The method is <em>expected</em> to immediately run the provided block of code in a
|
|
|
|
* separate non-blocking fiber.
|
|
|
|
*
|
|
|
|
* puts "Go to sleep!"
|
|
|
|
*
|
|
|
|
* Fiber.set_scheduler(MyScheduler.new)
|
|
|
|
*
|
|
|
|
* Fiber.schedule do
|
|
|
|
* puts "Going to sleep"
|
|
|
|
* sleep(1)
|
|
|
|
* puts "I slept well"
|
|
|
|
* end
|
|
|
|
*
|
|
|
|
* puts "Wakey-wakey, sleepyhead"
|
|
|
|
*
|
|
|
|
* Assuming MyScheduler is properly implemented, this program will produce:
|
|
|
|
*
|
|
|
|
* Go to sleep!
|
|
|
|
* Going to sleep
|
|
|
|
* Wakey-wakey, sleepyhead
|
|
|
|
* ...1 sec pause here...
|
|
|
|
* I slept well
|
|
|
|
*
|
|
|
|
* ...e.g. on the first blocking operation inside the Fiber (<tt>sleep(1)</tt>),
|
2020-12-24 12:00:23 +03:00
|
|
|
* the control is yielded to the outside code (main fiber), and <em>at the end
|
|
|
|
* of that execution</em>, the scheduler takes care of properly resuming all the
|
2020-12-12 19:55:17 +03:00
|
|
|
* blocked fibers.
|
|
|
|
*
|
|
|
|
* Note that the behavior described above is how the method is <em>expected</em>
|
|
|
|
* to behave, actual behavior is up to the current scheduler's implementation of
|
2022-12-21 23:10:21 +03:00
|
|
|
* Fiber::Scheduler#fiber method. Ruby doesn't enforce this method to
|
2020-12-12 19:55:17 +03:00
|
|
|
* behave in any particular way.
|
|
|
|
*
|
|
|
|
* If the scheduler is not set, the method raises
|
|
|
|
* <tt>RuntimeError (No scheduler is available!)</tt>.
|
|
|
|
*
|
|
|
|
*/
|
2020-05-14 13:10:55 +03:00
|
|
|
static VALUE
|
2021-03-12 12:15:56 +03:00
|
|
|
rb_fiber_s_schedule(int argc, VALUE *argv, VALUE obj)
|
2020-05-14 13:10:55 +03:00
|
|
|
{
|
2021-03-12 12:15:56 +03:00
|
|
|
return rb_fiber_s_schedule_kw(argc, argv, rb_keyword_given_p());
|
2007-08-26 07:31:20 +04:00
|
|
|
}
|
|
|
|
|
2020-12-12 19:55:17 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* Fiber.scheduler -> obj or nil
|
|
|
|
*
|
2020-12-24 12:00:23 +03:00
|
|
|
* Returns the Fiber scheduler, that was last set for the current thread with Fiber.set_scheduler.
|
|
|
|
* Returns +nil+ if no scheduler is set (which is the default), and non-blocking fibers'
|
2022-12-21 23:10:21 +03:00
|
|
|
* behavior is the same as blocking.
|
2020-12-12 19:55:17 +03:00
|
|
|
* (see "Non-blocking fibers" section in class docs for details about the scheduler concept).
|
|
|
|
*
|
|
|
|
*/
|
2020-10-16 04:25:58 +03:00
|
|
|
static VALUE
|
2021-03-12 12:15:56 +03:00
|
|
|
rb_fiber_s_scheduler(VALUE klass)
|
2020-10-16 04:25:58 +03:00
|
|
|
{
|
2021-02-09 09:39:56 +03:00
|
|
|
return rb_fiber_scheduler_get();
|
2020-10-16 04:25:58 +03:00
|
|
|
}
|
|
|
|
|
2021-02-12 06:54:52 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* Fiber.current_scheduler -> obj or nil
|
|
|
|
*
|
|
|
|
* Returns the Fiber scheduler, that was last set for the current thread with Fiber.set_scheduler
|
2021-06-12 14:15:08 +03:00
|
|
|
* if and only if the current fiber is non-blocking.
|
2021-02-12 06:54:52 +03:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
static VALUE
|
|
|
|
rb_fiber_current_scheduler(VALUE klass)
|
|
|
|
{
|
|
|
|
return rb_fiber_scheduler_current();
|
|
|
|
}
|
|
|
|
|
2020-12-12 19:55:17 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* Fiber.set_scheduler(scheduler) -> scheduler
|
|
|
|
*
|
2020-12-24 12:00:23 +03:00
|
|
|
* Sets the Fiber scheduler for the current thread. If the scheduler is set, non-blocking
|
2020-12-12 19:55:17 +03:00
|
|
|
* fibers (created by Fiber.new with <tt>blocking: false</tt>, or by Fiber.schedule)
|
|
|
|
* call that scheduler's hook methods on potentially blocking operations, and the current
|
|
|
|
* thread will call scheduler's +close+ method on finalization (allowing the scheduler to
|
|
|
|
* properly manage all non-finished fibers).
|
|
|
|
*
|
2022-12-21 23:10:21 +03:00
|
|
|
* +scheduler+ can be an object of any class corresponding to Fiber::Scheduler. Its
|
2020-12-12 19:55:17 +03:00
|
|
|
* implementation is up to the user.
|
|
|
|
*
|
|
|
|
* See also the "Non-blocking fibers" section in class docs.
|
|
|
|
*
|
|
|
|
*/
|
2020-10-16 04:25:58 +03:00
|
|
|
static VALUE
|
|
|
|
rb_fiber_set_scheduler(VALUE klass, VALUE scheduler)
|
|
|
|
{
|
2021-02-09 09:39:56 +03:00
|
|
|
return rb_fiber_scheduler_set(scheduler);
|
2020-10-16 04:25:58 +03:00
|
|
|
}
|
|
|
|
|
2022-01-19 10:01:45 +03:00
|
|
|
NORETURN(static void rb_fiber_terminate(rb_fiber_t *fiber, int need_interrupt, VALUE err));
|
2007-08-21 22:51:39 +04:00
|
|
|
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
void
|
2021-06-26 01:17:26 +03:00
|
|
|
rb_fiber_start(rb_fiber_t *fiber)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
2021-06-26 01:17:26 +03:00
|
|
|
rb_thread_t * volatile th = fiber->cont.saved_ec.thread_ptr;
|
|
|
|
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
rb_proc_t *proc;
|
2017-06-23 10:25:52 +03:00
|
|
|
enum ruby_tag_type state;
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
|
2020-03-09 20:22:11 +03:00
|
|
|
VM_ASSERT(th->ec == GET_EC());
|
2019-07-08 08:59:28 +03:00
|
|
|
VM_ASSERT(FIBER_RESUMED_P(fiber));
|
2017-08-10 04:47:13 +03:00
|
|
|
|
2020-05-14 13:10:55 +03:00
|
|
|
if (fiber->blocking) {
|
|
|
|
th->blocking += 1;
|
|
|
|
}
|
|
|
|
|
2017-10-26 14:02:13 +03:00
|
|
|
EC_PUSH_TAG(th->ec);
|
2017-12-06 06:16:08 +03:00
|
|
|
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
|
2019-07-08 10:00:29 +03:00
|
|
|
rb_context_t *cont = &VAR_FROM_MEMORY(fiber)->cont;
|
2019-06-01 08:48:25 +03:00
|
|
|
int argc;
|
|
|
|
const VALUE *argv, args = cont->value;
|
2019-07-08 10:00:29 +03:00
|
|
|
GetProcPtr(fiber->first_proc, proc);
|
2019-06-01 08:48:25 +03:00
|
|
|
argv = (argc = cont->argc) > 1 ? RARRAY_CONST_PTR(args) : &args;
|
|
|
|
cont->value = Qnil;
|
|
|
|
th->ec->errinfo = Qnil;
|
2019-07-08 10:00:29 +03:00
|
|
|
th->ec->root_lep = rb_vm_proc_local_ep(fiber->first_proc);
|
2019-06-01 08:48:25 +03:00
|
|
|
th->ec->root_svar = Qfalse;
|
2015-08-21 12:51:01 +03:00
|
|
|
|
2019-06-01 08:48:25 +03:00
|
|
|
EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_FIBER_SWITCH, th->self, 0, 0, 0, Qnil);
|
2019-10-04 22:51:57 +03:00
|
|
|
cont->value = rb_vm_invoke_proc(th->ec, proc, argc, argv, cont->kw_splat, VM_BLOCK_HANDLER_NONE);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
2017-10-26 14:02:13 +03:00
|
|
|
EC_POP_TAG();
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
|
2023-08-03 07:43:04 +03:00
|
|
|
int need_interrupt = TRUE;
|
2021-06-03 14:07:26 +03:00
|
|
|
VALUE err = Qfalse;
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
if (state) {
|
2021-02-22 00:33:11 +03:00
|
|
|
err = th->ec->errinfo;
|
2019-07-08 10:00:29 +03:00
|
|
|
VM_ASSERT(FIBER_RESUMED_P(fiber));
|
2017-08-10 04:47:13 +03:00
|
|
|
|
2021-02-22 00:33:11 +03:00
|
|
|
if (state == TAG_RAISE) {
|
|
|
|
// noop...
|
2021-02-23 21:37:22 +03:00
|
|
|
}
|
2023-05-18 17:33:42 +03:00
|
|
|
else if (state == TAG_FATAL && err == RUBY_FATAL_FIBER_KILLED) {
|
|
|
|
need_interrupt = FALSE;
|
|
|
|
err = Qfalse;
|
|
|
|
}
|
2021-02-23 21:37:22 +03:00
|
|
|
else if (state == TAG_FATAL) {
|
2019-06-01 08:48:25 +03:00
|
|
|
rb_threadptr_pending_interrupt_enque(th, err);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
err = rb_vm_make_jump_tag_but_local_jump(state, err);
|
|
|
|
}
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2021-02-22 00:33:11 +03:00
|
|
|
rb_fiber_terminate(fiber, need_interrupt, err);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2022-10-20 03:18:59 +03:00
|
|
|
// Set up a "root fiber", which is the fiber that every Ractor has.
|
2017-10-26 11:32:49 +03:00
|
|
|
void
|
2018-04-04 11:19:28 +03:00
|
|
|
rb_threadptr_root_fiber_setup(rb_thread_t *th)
|
2017-10-26 11:32:49 +03:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_t *fiber = ruby_mimmalloc(sizeof(rb_fiber_t));
|
2019-11-01 09:56:02 +03:00
|
|
|
if (!fiber) {
|
|
|
|
rb_bug("%s", strerror(errno)); /* ... is it possible to call rb_bug here? */
|
|
|
|
}
|
2019-07-08 08:59:28 +03:00
|
|
|
MEMZERO(fiber, rb_fiber_t, 1);
|
|
|
|
fiber->cont.type = FIBER_CONTEXT;
|
|
|
|
fiber->cont.saved_ec.fiber_ptr = fiber;
|
|
|
|
fiber->cont.saved_ec.thread_ptr = th;
|
2020-05-14 13:10:55 +03:00
|
|
|
fiber->blocking = 1;
|
2023-05-18 17:33:42 +03:00
|
|
|
fiber->killed = 0;
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber_status_set(fiber, FIBER_RESUMED); /* skip CREATED */
|
|
|
|
th->ec = &fiber->cont.saved_ec;
|
2023-03-09 10:14:33 +03:00
|
|
|
// When rb_threadptr_root_fiber_setup is called for the first time, rb_rjit_enabled and
|
2022-10-20 03:18:59 +03:00
|
|
|
// rb_yjit_enabled_p() are still false. So this does nothing and rb_jit_cont_init() that is
|
|
|
|
// called later will take care of it. However, you still have to call cont_init_jit_cont()
|
|
|
|
// here for other Ractors, which are not initialized by rb_jit_cont_init().
|
|
|
|
cont_init_jit_cont(&fiber->cont);
|
2017-10-26 11:32:49 +03:00
|
|
|
}
|
|
|
|
|
2017-10-26 17:21:31 +03:00
|
|
|
void
|
|
|
|
rb_threadptr_root_fiber_release(rb_thread_t *th)
|
|
|
|
{
|
|
|
|
if (th->root_fiber) {
|
2019-06-01 08:48:25 +03:00
|
|
|
/* ignore. A root fiber object will free th->ec */
|
2017-10-26 17:21:31 +03:00
|
|
|
}
|
|
|
|
else {
|
2020-03-09 20:22:11 +03:00
|
|
|
rb_execution_context_t *ec = GET_EC();
|
|
|
|
|
2019-06-01 08:48:25 +03:00
|
|
|
VM_ASSERT(th->ec->fiber_ptr->cont.type == FIBER_CONTEXT);
|
|
|
|
VM_ASSERT(th->ec->fiber_ptr->cont.self == 0);
|
2019-06-05 14:39:17 +03:00
|
|
|
|
2020-03-09 20:22:11 +03:00
|
|
|
if (th->ec == ec) {
|
|
|
|
rb_ractor_set_current_ec(th->ractor, NULL);
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
2020-03-09 20:22:11 +03:00
|
|
|
fiber_free(th->ec->fiber_ptr);
|
2019-06-01 08:48:25 +03:00
|
|
|
th->ec = NULL;
|
2017-10-26 17:21:31 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-20 11:29:35 +03:00
|
|
|
void
|
|
|
|
rb_threadptr_root_fiber_terminate(rb_thread_t *th)
|
|
|
|
{
|
|
|
|
rb_fiber_t *fiber = th->ec->fiber_ptr;
|
|
|
|
|
|
|
|
fiber->status = FIBER_TERMINATED;
|
|
|
|
|
|
|
|
// The vm_stack is `alloca`ed on the thread stack, so it's gone too:
|
|
|
|
rb_ec_clear_vm_stack(th->ec);
|
|
|
|
}
|
|
|
|
|
2014-10-16 02:35:08 +04:00
|
|
|
static inline rb_fiber_t*
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
return_fiber(bool terminate)
|
2014-10-16 02:35:08 +04:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_t *fiber = fiber_current();
|
|
|
|
rb_fiber_t *prev = fiber->prev;
|
2014-10-16 02:35:08 +04:00
|
|
|
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
if (prev) {
|
|
|
|
fiber->prev = NULL;
|
2021-07-14 04:42:18 +03:00
|
|
|
prev->resuming_fiber = NULL;
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
return prev;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!terminate) {
|
|
|
|
rb_raise(rb_eFiberError, "attempt to yield on a not resumed fiber");
|
|
|
|
}
|
|
|
|
|
2019-06-01 08:48:25 +03:00
|
|
|
rb_thread_t *th = GET_THREAD();
|
|
|
|
rb_fiber_t *root_fiber = th->root_fiber;
|
2017-10-26 17:38:22 +03:00
|
|
|
|
2019-06-01 08:48:25 +03:00
|
|
|
VM_ASSERT(root_fiber != NULL);
|
2014-10-16 02:35:08 +04:00
|
|
|
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
// search resuming fiber
|
2021-07-14 04:42:18 +03:00
|
|
|
for (fiber = root_fiber; fiber->resuming_fiber; fiber = fiber->resuming_fiber) {
|
2019-06-01 08:48:25 +03:00
|
|
|
}
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
|
|
|
|
return fiber;
|
2014-10-16 02:35:08 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VALUE
|
|
|
|
rb_fiber_current(void)
|
|
|
|
{
|
|
|
|
return fiber_current()->cont.self;
|
|
|
|
}
|
|
|
|
|
2019-07-11 10:17:34 +03:00
|
|
|
// Prepare to execute next_fiber on the given thread.
|
2021-02-22 00:33:11 +03:00
|
|
|
static inline void
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber_store(rb_fiber_t *next_fiber, rb_thread_t *th)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_t *fiber;
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
|
2017-11-06 08:41:48 +03:00
|
|
|
if (th->ec->fiber_ptr != NULL) {
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber = th->ec->fiber_ptr;
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
else {
|
2018-11-22 05:17:44 +03:00
|
|
|
/* create root fiber */
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber = root_fiber_alloc(th);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
if (FIBER_CREATED_P(next_fiber)) {
|
2019-06-02 03:49:58 +03:00
|
|
|
fiber_prepare_stack(next_fiber);
|
2017-08-10 04:47:13 +03:00
|
|
|
}
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
VM_ASSERT(FIBER_RESUMED_P(fiber) || FIBER_TERMINATED_P(fiber));
|
|
|
|
VM_ASSERT(FIBER_RUNNABLE_P(next_fiber));
|
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
if (FIBER_RESUMED_P(fiber)) fiber_status_set(fiber, FIBER_SUSPENDED);
|
2017-09-10 21:37:55 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber_status_set(next_fiber, FIBER_RESUMED);
|
|
|
|
fiber_setcontext(next_fiber, fiber);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2023-05-18 17:33:42 +03:00
|
|
|
static void
|
|
|
|
fiber_check_killed(rb_fiber_t *fiber)
|
|
|
|
{
|
|
|
|
VM_ASSERT(fiber == fiber_current());
|
|
|
|
|
|
|
|
if (fiber->killed) {
|
|
|
|
rb_thread_t *thread = fiber->cont.saved_ec.thread_ptr;
|
|
|
|
|
|
|
|
thread->ec->errinfo = RUBY_FATAL_FIBER_KILLED;
|
|
|
|
EC_JUMP_TAG(thread->ec, RUBY_TAG_FATAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-08-21 22:51:39 +04:00
|
|
|
static inline VALUE
|
2021-07-14 04:42:18 +03:00
|
|
|
fiber_switch(rb_fiber_t *fiber, int argc, const VALUE *argv, int kw_splat, rb_fiber_t *resuming_fiber, bool yielding)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
2007-06-02 11:48:29 +04:00
|
|
|
VALUE value;
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_context_t *cont = &fiber->cont;
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
rb_thread_t *th = GET_THREAD();
|
|
|
|
|
2017-10-26 17:38:22 +03:00
|
|
|
/* make sure the root_fiber object is available */
|
|
|
|
if (th->root_fiber == NULL) root_fiber_alloc(th);
|
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
if (th->ec->fiber_ptr == fiber) {
|
2019-06-01 08:48:25 +03:00
|
|
|
/* ignore fiber context switch
|
2021-01-05 17:13:53 +03:00
|
|
|
* because destination fiber is the same as current fiber
|
2019-06-01 08:48:25 +03:00
|
|
|
*/
|
|
|
|
return make_passing_arg(argc, argv);
|
2011-11-21 01:17:57 +04:00
|
|
|
}
|
|
|
|
|
2017-09-10 22:00:08 +03:00
|
|
|
if (cont_thread_value(cont) != th->self) {
|
2019-06-01 08:48:25 +03:00
|
|
|
rb_raise(rb_eFiberError, "fiber called across threads");
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
2021-06-26 01:17:26 +03:00
|
|
|
|
|
|
|
if (FIBER_TERMINATED_P(fiber)) {
|
2019-06-01 08:48:25 +03:00
|
|
|
value = rb_exc_new2(rb_eFiberError, "dead fiber called");
|
|
|
|
|
|
|
|
if (!FIBER_TERMINATED_P(th->ec->fiber_ptr)) {
|
|
|
|
rb_exc_raise(value);
|
|
|
|
VM_UNREACHABLE(fiber_switch);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* th->ec->fiber_ptr is also dead => switch to root fiber */
|
|
|
|
/* (this means we're being called from rb_fiber_terminate, */
|
|
|
|
/* and the terminated fiber's return_fiber() is already dead) */
|
|
|
|
VM_ASSERT(FIBER_SUSPENDED_P(th->root_fiber));
|
|
|
|
|
|
|
|
cont = &th->root_fiber->cont;
|
|
|
|
cont->argc = -1;
|
|
|
|
cont->value = value;
|
|
|
|
|
|
|
|
fiber_setcontext(th->root_fiber, th->ec->fiber_ptr);
|
|
|
|
|
|
|
|
VM_UNREACHABLE(fiber_switch);
|
|
|
|
}
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2020-05-14 13:10:55 +03:00
|
|
|
VM_ASSERT(FIBER_RUNNABLE_P(fiber));
|
|
|
|
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
rb_fiber_t *current_fiber = fiber_current();
|
|
|
|
|
2021-07-14 04:42:18 +03:00
|
|
|
VM_ASSERT(!current_fiber->resuming_fiber);
|
|
|
|
|
|
|
|
if (resuming_fiber) {
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
current_fiber->resuming_fiber = resuming_fiber;
|
2019-07-08 10:00:29 +03:00
|
|
|
fiber->prev = fiber_current();
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
fiber->yielding = 0;
|
2007-08-21 22:51:39 +04:00
|
|
|
}
|
2007-08-25 12:51:59 +04:00
|
|
|
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
VM_ASSERT(!current_fiber->yielding);
|
|
|
|
if (yielding) {
|
|
|
|
current_fiber->yielding = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current_fiber->blocking) {
|
2020-05-14 13:10:55 +03:00
|
|
|
th->blocking -= 1;
|
|
|
|
}
|
2017-08-10 04:47:13 +03:00
|
|
|
|
2008-12-01 06:00:48 +03:00
|
|
|
cont->argc = argc;
|
2019-09-26 03:57:00 +03:00
|
|
|
cont->kw_splat = kw_splat;
|
2007-06-02 11:48:29 +04:00
|
|
|
cont->value = make_passing_arg(argc, argv);
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2021-02-22 00:33:11 +03:00
|
|
|
fiber_store(fiber, th);
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2021-06-26 01:17:26 +03:00
|
|
|
// We cannot free the stack until the pthread is joined:
|
|
|
|
#ifndef COROUTINE_PTHREAD_CONTEXT
|
2021-07-14 04:42:18 +03:00
|
|
|
if (resuming_fiber && FIBER_TERMINATED_P(fiber)) {
|
2019-06-02 03:49:58 +03:00
|
|
|
fiber_stack_release(fiber);
|
|
|
|
}
|
2021-06-26 01:17:26 +03:00
|
|
|
#endif
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2020-05-14 13:10:55 +03:00
|
|
|
if (fiber_current()->blocking) {
|
|
|
|
th->blocking += 1;
|
|
|
|
}
|
|
|
|
|
2017-11-06 10:44:28 +03:00
|
|
|
RUBY_VM_CHECK_INTS(th->ec);
|
2007-08-21 22:51:39 +04:00
|
|
|
|
2017-10-29 16:19:14 +03:00
|
|
|
EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_FIBER_SWITCH, th->self, 0, 0, 0, Qnil);
|
2015-08-21 12:51:01 +03:00
|
|
|
|
2021-02-22 00:33:11 +03:00
|
|
|
current_fiber = th->ec->fiber_ptr;
|
|
|
|
value = current_fiber->cont.value;
|
2023-05-18 17:33:42 +03:00
|
|
|
|
|
|
|
fiber_check_killed(current_fiber);
|
|
|
|
|
|
|
|
if (current_fiber->cont.argc == -1) {
|
|
|
|
// Fiber#raise will trigger this path.
|
|
|
|
rb_exc_raise(value);
|
|
|
|
}
|
|
|
|
|
2007-06-02 11:48:29 +04:00
|
|
|
return value;
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2007-08-21 22:51:39 +04:00
|
|
|
VALUE
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_transfer(VALUE fiber_value, int argc, const VALUE *argv)
|
2007-08-15 21:56:22 +04:00
|
|
|
{
|
2021-07-14 04:42:18 +03:00
|
|
|
return fiber_switch(fiber_ptr(fiber_value), argc, argv, RB_NO_KEYWORDS, NULL, false);
|
2007-08-15 21:56:22 +04:00
|
|
|
}
|
|
|
|
|
2020-12-12 19:55:17 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* fiber.blocking? -> true or false
|
|
|
|
*
|
|
|
|
* Returns +true+ if +fiber+ is blocking and +false+ otherwise.
|
|
|
|
* Fiber is non-blocking if it was created via passing <tt>blocking: false</tt>
|
|
|
|
* to Fiber.new, or via Fiber.schedule.
|
|
|
|
*
|
2020-12-24 12:00:23 +03:00
|
|
|
* Note that, even if the method returns +false+, the fiber behaves differently
|
2020-12-12 19:55:17 +03:00
|
|
|
* only if Fiber.scheduler is set in the current thread.
|
|
|
|
*
|
|
|
|
* See the "Non-blocking fibers" section in class docs for details.
|
|
|
|
*
|
|
|
|
*/
|
2020-05-14 13:10:55 +03:00
|
|
|
VALUE
|
|
|
|
rb_fiber_blocking_p(VALUE fiber)
|
|
|
|
{
|
2022-10-12 02:59:05 +03:00
|
|
|
return RBOOL(fiber_ptr(fiber)->blocking);
|
2020-05-14 13:10:55 +03:00
|
|
|
}
|
|
|
|
|
2022-10-06 13:00:49 +03:00
|
|
|
static VALUE
|
2022-10-12 02:59:05 +03:00
|
|
|
fiber_blocking_yield(VALUE fiber_value)
|
2022-10-06 13:00:49 +03:00
|
|
|
{
|
2022-10-12 02:59:05 +03:00
|
|
|
rb_fiber_t *fiber = fiber_ptr(fiber_value);
|
|
|
|
rb_thread_t * volatile th = fiber->cont.saved_ec.thread_ptr;
|
|
|
|
|
2023-06-10 17:13:01 +03:00
|
|
|
VM_ASSERT(fiber->blocking == 0);
|
|
|
|
|
2022-10-12 02:59:05 +03:00
|
|
|
// fiber->blocking is `unsigned int : 1`, so we use it as a boolean:
|
|
|
|
fiber->blocking = 1;
|
|
|
|
|
|
|
|
// Once the fiber is blocking, and current, we increment the thread blocking state:
|
|
|
|
th->blocking += 1;
|
|
|
|
|
|
|
|
return rb_yield(fiber_value);
|
2022-10-06 13:00:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2022-10-12 02:59:05 +03:00
|
|
|
fiber_blocking_ensure(VALUE fiber_value)
|
2022-10-06 13:00:49 +03:00
|
|
|
{
|
2022-10-12 02:59:05 +03:00
|
|
|
rb_fiber_t *fiber = fiber_ptr(fiber_value);
|
|
|
|
rb_thread_t * volatile th = fiber->cont.saved_ec.thread_ptr;
|
|
|
|
|
|
|
|
// We are no longer blocking:
|
|
|
|
fiber->blocking = 0;
|
|
|
|
th->blocking -= 1;
|
|
|
|
|
2022-10-06 13:00:49 +03:00
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* Fiber.blocking{|fiber| ...} -> result
|
|
|
|
*
|
|
|
|
* Forces the fiber to be blocking for the duration of the block. Returns the
|
|
|
|
* result of the block.
|
|
|
|
*
|
|
|
|
* See the "Non-blocking fibers" section in class docs for details.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
VALUE
|
|
|
|
rb_fiber_blocking(VALUE class)
|
|
|
|
{
|
2022-10-12 02:59:05 +03:00
|
|
|
VALUE fiber_value = rb_fiber_current();
|
|
|
|
rb_fiber_t *fiber = fiber_ptr(fiber_value);
|
|
|
|
|
|
|
|
// If we are already blocking, this is essentially a no-op:
|
|
|
|
if (fiber->blocking) {
|
|
|
|
return rb_yield(fiber_value);
|
2023-02-26 07:20:43 +03:00
|
|
|
}
|
|
|
|
else {
|
2022-10-12 02:59:05 +03:00
|
|
|
return rb_ensure(fiber_blocking_yield, fiber_value, fiber_blocking_ensure, fiber_value);
|
|
|
|
}
|
2022-10-06 13:00:49 +03:00
|
|
|
}
|
|
|
|
|
2020-12-12 19:55:17 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
2020-12-24 12:00:23 +03:00
|
|
|
* Fiber.blocking? -> false or 1
|
2020-12-12 19:55:17 +03:00
|
|
|
*
|
|
|
|
* Returns +false+ if the current fiber is non-blocking.
|
|
|
|
* Fiber is non-blocking if it was created via passing <tt>blocking: false</tt>
|
|
|
|
* to Fiber.new, or via Fiber.schedule.
|
|
|
|
*
|
2020-12-24 12:00:23 +03:00
|
|
|
* If the current Fiber is blocking, the method returns 1.
|
|
|
|
* Future developments may allow for situations where larger integers
|
|
|
|
* could be returned.
|
2020-12-12 19:55:17 +03:00
|
|
|
*
|
2020-12-24 12:00:23 +03:00
|
|
|
* Note that, even if the method returns +false+, Fiber behaves differently
|
2020-12-12 19:55:17 +03:00
|
|
|
* only if Fiber.scheduler is set in the current thread.
|
|
|
|
*
|
|
|
|
* See the "Non-blocking fibers" section in class docs for details.
|
|
|
|
*
|
|
|
|
*/
|
2020-10-16 04:25:58 +03:00
|
|
|
static VALUE
|
2021-03-12 12:15:56 +03:00
|
|
|
rb_fiber_s_blocking_p(VALUE klass)
|
2020-10-16 04:25:58 +03:00
|
|
|
{
|
|
|
|
rb_thread_t *thread = GET_THREAD();
|
|
|
|
unsigned blocking = thread->blocking;
|
|
|
|
|
|
|
|
if (blocking == 0)
|
|
|
|
return Qfalse;
|
|
|
|
|
|
|
|
return INT2NUM(blocking);
|
|
|
|
}
|
|
|
|
|
2017-10-26 11:32:49 +03:00
|
|
|
void
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_close(rb_fiber_t *fiber)
|
2017-10-26 11:32:49 +03:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber_status_set(fiber, FIBER_TERMINATED);
|
2017-10-26 11:32:49 +03:00
|
|
|
}
|
|
|
|
|
2014-10-16 02:35:08 +04:00
|
|
|
static void
|
2021-06-26 01:17:26 +03:00
|
|
|
rb_fiber_terminate(rb_fiber_t *fiber, int need_interrupt, VALUE error)
|
2014-10-16 02:35:08 +04:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
VALUE value = fiber->cont.value;
|
2017-08-10 04:47:13 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
VM_ASSERT(FIBER_RESUMED_P(fiber));
|
|
|
|
rb_fiber_close(fiber);
|
2017-10-26 11:32:49 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber->cont.machine.stack = NULL;
|
|
|
|
fiber->cont.machine.stack_size = 0;
|
2017-10-26 11:32:49 +03:00
|
|
|
|
2021-06-26 01:17:26 +03:00
|
|
|
rb_fiber_t *next_fiber = return_fiber(true);
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
if (need_interrupt) RUBY_VM_SET_INTERRUPT(&next_fiber->cont.saved_ec);
|
2021-06-26 01:17:26 +03:00
|
|
|
|
|
|
|
if (RTEST(error))
|
2021-07-14 04:42:18 +03:00
|
|
|
fiber_switch(next_fiber, -1, &error, RB_NO_KEYWORDS, NULL, false);
|
2021-02-22 00:33:11 +03:00
|
|
|
else
|
2021-07-14 04:42:18 +03:00
|
|
|
fiber_switch(next_fiber, 1, &value, RB_NO_KEYWORDS, NULL, false);
|
2022-01-19 10:01:45 +03:00
|
|
|
ruby_stop(0);
|
2014-10-16 02:35:08 +04:00
|
|
|
}
|
|
|
|
|
2021-07-14 04:42:18 +03:00
|
|
|
static VALUE
|
|
|
|
fiber_resume_kw(rb_fiber_t *fiber, int argc, const VALUE *argv, int kw_splat)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
rb_fiber_t *current_fiber = fiber_current();
|
2007-08-21 22:51:39 +04:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
if (argc == -1 && FIBER_CREATED_P(fiber)) {
|
2018-12-28 16:03:14 +03:00
|
|
|
rb_raise(rb_eFiberError, "cannot raise exception on unborn fiber");
|
|
|
|
}
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
else if (FIBER_TERMINATED_P(fiber)) {
|
|
|
|
rb_raise(rb_eFiberError, "attempt to resume a terminated fiber");
|
2007-08-21 22:51:39 +04:00
|
|
|
}
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
else if (fiber == current_fiber) {
|
|
|
|
rb_raise(rb_eFiberError, "attempt to resume the current fiber");
|
|
|
|
}
|
|
|
|
else if (fiber->prev != NULL) {
|
|
|
|
rb_raise(rb_eFiberError, "attempt to resume a resumed fiber (double resume)");
|
|
|
|
}
|
2021-07-14 04:42:18 +03:00
|
|
|
else if (fiber->resuming_fiber) {
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
rb_raise(rb_eFiberError, "attempt to resume a resuming fiber");
|
|
|
|
}
|
|
|
|
else if (fiber->prev == NULL &&
|
|
|
|
(!fiber->yielding && fiber->status != FIBER_CREATED)) {
|
|
|
|
rb_raise(rb_eFiberError, "attempt to resume a transferring fiber");
|
2011-11-09 08:26:39 +04:00
|
|
|
}
|
2007-08-21 22:51:39 +04:00
|
|
|
|
2022-05-25 01:51:28 +03:00
|
|
|
return fiber_switch(fiber, argc, argv, kw_splat, fiber, false);
|
2019-09-26 03:57:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
VALUE
|
2021-07-14 04:42:18 +03:00
|
|
|
rb_fiber_resume_kw(VALUE self, int argc, const VALUE *argv, int kw_splat)
|
2019-09-26 03:57:00 +03:00
|
|
|
{
|
2021-07-14 04:42:18 +03:00
|
|
|
return fiber_resume_kw(fiber_ptr(self), argc, argv, kw_splat);
|
|
|
|
}
|
|
|
|
|
|
|
|
VALUE
|
|
|
|
rb_fiber_resume(VALUE self, int argc, const VALUE *argv)
|
|
|
|
{
|
|
|
|
return fiber_resume_kw(fiber_ptr(self), argc, argv, RB_NO_KEYWORDS);
|
2019-09-26 03:57:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
VALUE
|
|
|
|
rb_fiber_yield_kw(int argc, const VALUE *argv, int kw_splat)
|
|
|
|
{
|
2021-07-14 04:42:18 +03:00
|
|
|
return fiber_switch(return_fiber(false), argc, argv, kw_splat, NULL, true);
|
2007-08-21 22:51:39 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
VALUE
|
2014-06-18 10:16:39 +04:00
|
|
|
rb_fiber_yield(int argc, const VALUE *argv)
|
2007-08-21 22:51:39 +04:00
|
|
|
{
|
2021-07-14 04:42:18 +03:00
|
|
|
return fiber_switch(return_fiber(false), argc, argv, RB_NO_KEYWORDS, NULL, true);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2012-02-15 18:00:11 +04:00
|
|
|
void
|
2018-11-06 13:19:55 +03:00
|
|
|
rb_fiber_reset_root_local_storage(rb_thread_t *th)
|
2012-02-15 18:00:11 +04:00
|
|
|
{
|
2017-11-06 08:41:48 +03:00
|
|
|
if (th->root_fiber && th->root_fiber != th->ec->fiber_ptr) {
|
2019-06-01 08:48:25 +03:00
|
|
|
th->ec->local_storage = th->root_fiber->cont.saved_ec.local_storage;
|
2012-02-15 18:00:11 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-12-27 03:25:47 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* fiber.alive? -> true or false
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2010-10-27 04:26:29 +04:00
|
|
|
* Returns true if the fiber can still be resumed (or transferred
|
|
|
|
* to). After finishing execution of the fiber block this method will
|
2021-03-22 12:59:25 +03:00
|
|
|
* always return +false+.
|
2008-12-27 03:25:47 +03:00
|
|
|
*/
|
2007-08-06 20:41:17 +04:00
|
|
|
VALUE
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_alive_p(VALUE fiber_value)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
2022-01-01 09:41:00 +03:00
|
|
|
return RBOOL(!FIBER_TERMINATED_P(fiber_ptr(fiber_value)));
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2008-12-27 03:25:47 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* fiber.resume(args, ...) -> obj
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2019-03-22 14:04:59 +03:00
|
|
|
* Resumes the fiber from the point at which the last Fiber.yield was
|
|
|
|
* called, or starts running it if it is the first call to
|
|
|
|
* #resume. Arguments passed to resume will be the value of the
|
|
|
|
* Fiber.yield expression or will be passed as block parameters to
|
|
|
|
* the fiber's block if this is the first #resume.
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
* Alternatively, when resume is called it evaluates to the arguments passed
|
2019-03-22 14:04:59 +03:00
|
|
|
* to the next Fiber.yield statement inside the fiber's block
|
2008-12-27 03:25:47 +03:00
|
|
|
* or to the block value if it runs to completion without any
|
2019-03-22 14:04:59 +03:00
|
|
|
* Fiber.yield
|
2008-12-27 03:25:47 +03:00
|
|
|
*/
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
static VALUE
|
2019-07-08 08:59:28 +03:00
|
|
|
rb_fiber_m_resume(int argc, VALUE *argv, VALUE fiber)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
2019-10-04 22:51:57 +03:00
|
|
|
return rb_fiber_resume_kw(fiber, argc, argv, rb_keyword_given_p());
|
2007-08-21 22:51:39 +04:00
|
|
|
}
|
|
|
|
|
2020-12-22 00:42:01 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* fiber.backtrace -> array
|
|
|
|
* fiber.backtrace(start) -> array
|
|
|
|
* fiber.backtrace(start, count) -> array
|
|
|
|
* fiber.backtrace(start..end) -> array
|
|
|
|
*
|
|
|
|
* Returns the current execution stack of the fiber. +start+, +count+ and +end+ allow
|
|
|
|
* to select only parts of the backtrace.
|
|
|
|
*
|
|
|
|
* def level3
|
|
|
|
* Fiber.yield
|
|
|
|
* end
|
|
|
|
*
|
|
|
|
* def level2
|
|
|
|
* level3
|
|
|
|
* end
|
|
|
|
*
|
|
|
|
* def level1
|
|
|
|
* level2
|
|
|
|
* end
|
|
|
|
*
|
|
|
|
* f = Fiber.new { level1 }
|
|
|
|
*
|
|
|
|
* # It is empty before the fiber started
|
|
|
|
* f.backtrace
|
|
|
|
* #=> []
|
|
|
|
*
|
|
|
|
* f.resume
|
|
|
|
*
|
|
|
|
* f.backtrace
|
|
|
|
* #=> ["test.rb:2:in `yield'", "test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'", "test.rb:13:in `block in <main>'"]
|
|
|
|
* p f.backtrace(1) # start from the item 1
|
|
|
|
* #=> ["test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'", "test.rb:13:in `block in <main>'"]
|
|
|
|
* p f.backtrace(2, 2) # start from item 2, take 2
|
|
|
|
* #=> ["test.rb:6:in `level2'", "test.rb:10:in `level1'"]
|
|
|
|
* p f.backtrace(1..3) # take items from 1 to 3
|
|
|
|
* #=> ["test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'"]
|
|
|
|
*
|
|
|
|
* f.resume
|
|
|
|
*
|
2020-12-22 05:07:13 +03:00
|
|
|
* # It is nil after the fiber is finished
|
2020-12-22 00:42:01 +03:00
|
|
|
* f.backtrace
|
2020-12-22 05:07:13 +03:00
|
|
|
* #=> nil
|
2020-12-22 00:42:01 +03:00
|
|
|
*
|
|
|
|
*/
|
2020-08-15 06:36:18 +03:00
|
|
|
static VALUE
|
|
|
|
rb_fiber_backtrace(int argc, VALUE *argv, VALUE fiber)
|
|
|
|
{
|
|
|
|
return rb_vm_backtrace(argc, argv, &fiber_ptr(fiber)->cont.saved_ec);
|
|
|
|
}
|
|
|
|
|
2020-12-22 00:42:01 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* fiber.backtrace_locations -> array
|
|
|
|
* fiber.backtrace_locations(start) -> array
|
|
|
|
* fiber.backtrace_locations(start, count) -> array
|
|
|
|
* fiber.backtrace_locations(start..end) -> array
|
|
|
|
*
|
|
|
|
* Like #backtrace, but returns each line of the execution stack as a
|
|
|
|
* Thread::Backtrace::Location. Accepts the same arguments as #backtrace.
|
|
|
|
*
|
|
|
|
* f = Fiber.new { Fiber.yield }
|
|
|
|
* f.resume
|
|
|
|
* loc = f.backtrace_locations.first
|
|
|
|
* loc.label #=> "yield"
|
|
|
|
* loc.path #=> "test.rb"
|
|
|
|
* loc.lineno #=> 1
|
|
|
|
*
|
|
|
|
*
|
|
|
|
*/
|
2020-08-15 06:36:18 +03:00
|
|
|
static VALUE
|
|
|
|
rb_fiber_backtrace_locations(int argc, VALUE *argv, VALUE fiber)
|
|
|
|
{
|
|
|
|
return rb_vm_backtrace_locations(argc, argv, &fiber_ptr(fiber)->cont.saved_ec);
|
|
|
|
}
|
|
|
|
|
2008-12-27 03:25:47 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* fiber.transfer(args, ...) -> obj
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
* Transfer control to another fiber, resuming it from where it last
|
2009-02-22 17:23:33 +03:00
|
|
|
* stopped or starting it if it was not resumed before. The calling
|
2010-10-27 04:26:29 +04:00
|
|
|
* fiber will be suspended much like in a call to
|
2021-03-22 12:59:25 +03:00
|
|
|
* Fiber.yield.
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2020-12-24 12:00:23 +03:00
|
|
|
* The fiber which receives the transfer call treats it much like
|
2008-12-27 03:25:47 +03:00
|
|
|
* a resume call. Arguments passed to transfer are treated like those
|
|
|
|
* passed to resume.
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2020-12-23 00:23:14 +03:00
|
|
|
* The two style of control passing to and from fiber (one is #resume and
|
|
|
|
* Fiber::yield, another is #transfer to and from fiber) can't be freely
|
|
|
|
* mixed.
|
|
|
|
*
|
|
|
|
* * If the Fiber's lifecycle had started with transfer, it will never
|
2020-12-23 09:14:18 +03:00
|
|
|
* be able to yield or be resumed control passing, only
|
|
|
|
* finish or transfer back. (It still can resume other fibers that
|
|
|
|
* are allowed to be resumed.)
|
2020-12-23 00:23:14 +03:00
|
|
|
* * If the Fiber's lifecycle had started with resume, it can yield
|
|
|
|
* or transfer to another Fiber, but can receive control back only
|
|
|
|
* the way compatible with the way it was given away: if it had
|
|
|
|
* transferred, it only can be transferred back, and if it had
|
|
|
|
* yielded, it only can be resumed back. After that, it again can
|
2020-12-23 09:14:18 +03:00
|
|
|
* transfer or yield.
|
2020-12-23 00:23:14 +03:00
|
|
|
*
|
|
|
|
* If those rules are broken FiberError is raised.
|
|
|
|
*
|
2020-12-24 12:00:23 +03:00
|
|
|
* For an individual Fiber design, yield/resume is easier to use
|
|
|
|
* (the Fiber just gives away control, it doesn't need to think
|
2020-12-23 09:14:18 +03:00
|
|
|
* about who the control is given to), while transfer is more flexible
|
|
|
|
* for complex cases, allowing to build arbitrary graphs of Fibers
|
|
|
|
* dependent on each other.
|
|
|
|
*
|
2012-05-02 14:13:28 +04:00
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
*
|
2020-12-23 00:23:14 +03:00
|
|
|
* manager = nil # For local var to be visible inside worker block
|
2012-05-02 14:13:28 +04:00
|
|
|
*
|
2020-12-23 00:23:14 +03:00
|
|
|
* # This fiber would be started with transfer
|
|
|
|
* # It can't yield, and can't be resumed
|
|
|
|
* worker = Fiber.new { |work|
|
|
|
|
* puts "Worker: starts"
|
|
|
|
* puts "Worker: Performed #{work.inspect}, transferring back"
|
|
|
|
* # Fiber.yield # this would raise FiberError: attempt to yield on a not resumed fiber
|
|
|
|
* # manager.resume # this would raise FiberError: attempt to resume a resumed fiber (double resume)
|
|
|
|
* manager.transfer(work.capitalize)
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* # This fiber would be started with resume
|
|
|
|
* # It can yield or transfer, and can be transferred
|
|
|
|
* # back or resumed
|
|
|
|
* manager = Fiber.new {
|
|
|
|
* puts "Manager: starts"
|
|
|
|
* puts "Manager: transferring 'something' to worker"
|
|
|
|
* result = worker.transfer('something')
|
|
|
|
* puts "Manager: worker returned #{result.inspect}"
|
|
|
|
* # worker.resume # this would raise FiberError: attempt to resume a transferring fiber
|
|
|
|
* Fiber.yield # this is OK, the fiber transferred from and to, now it can yield
|
|
|
|
* puts "Manager: finished"
|
|
|
|
* }
|
2012-05-02 14:13:28 +04:00
|
|
|
*
|
2020-12-23 00:23:14 +03:00
|
|
|
* puts "Starting the manager"
|
|
|
|
* manager.resume
|
|
|
|
* puts "Resuming the manager"
|
|
|
|
* # manager.transfer # this would raise FiberError: attempt to transfer to a yielding fiber
|
|
|
|
* manager.resume
|
2012-05-02 14:13:28 +04:00
|
|
|
*
|
2014-05-25 05:17:52 +04:00
|
|
|
* <em>produces</em>
|
2012-05-02 14:13:28 +04:00
|
|
|
*
|
2020-12-23 00:23:14 +03:00
|
|
|
* Starting the manager
|
|
|
|
* Manager: starts
|
|
|
|
* Manager: transferring 'something' to worker
|
|
|
|
* Worker: starts
|
|
|
|
* Worker: Performed "something", transferring back
|
|
|
|
* Manager: worker returned "Something"
|
|
|
|
* Resuming the manager
|
|
|
|
* Manager: finished
|
2012-05-02 14:13:28 +04:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
*/
|
2007-08-21 22:51:39 +04:00
|
|
|
static VALUE
|
2021-07-14 04:42:18 +03:00
|
|
|
rb_fiber_m_transfer(int argc, VALUE *argv, VALUE self)
|
2020-11-18 03:23:51 +03:00
|
|
|
{
|
2021-07-14 04:42:18 +03:00
|
|
|
return rb_fiber_transfer_kw(self, argc, argv, rb_keyword_given_p());
|
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
fiber_transfer_kw(rb_fiber_t *fiber, int argc, const VALUE *argv, int kw_splat)
|
|
|
|
{
|
|
|
|
if (fiber->resuming_fiber) {
|
|
|
|
rb_raise(rb_eFiberError, "attempt to transfer to a resuming fiber");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fiber->yielding) {
|
|
|
|
rb_raise(rb_eFiberError, "attempt to transfer to a yielding fiber");
|
|
|
|
}
|
|
|
|
|
|
|
|
return fiber_switch(fiber, argc, argv, kw_splat, NULL, false);
|
2020-11-18 03:23:51 +03:00
|
|
|
}
|
|
|
|
|
2021-06-03 07:29:58 +03:00
|
|
|
VALUE
|
2021-07-14 04:42:18 +03:00
|
|
|
rb_fiber_transfer_kw(VALUE self, int argc, const VALUE *argv, int kw_splat)
|
2007-08-21 22:51:39 +04:00
|
|
|
{
|
2021-07-14 04:42:18 +03:00
|
|
|
return fiber_transfer_kw(fiber_ptr(self), argc, argv, kw_splat);
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2008-12-27 03:25:47 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* Fiber.yield(args, ...) -> obj
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2008-12-27 03:25:47 +03:00
|
|
|
* Yields control back to the context that resumed the fiber, passing
|
|
|
|
* along any arguments that were passed to it. The fiber will resume
|
2019-03-22 14:04:59 +03:00
|
|
|
* processing at this point when #resume is called next.
|
|
|
|
* Any arguments passed to the next #resume will be the value that
|
|
|
|
* this Fiber.yield expression evaluates to.
|
2008-12-27 03:25:47 +03:00
|
|
|
*/
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
static VALUE
|
2007-08-21 22:51:39 +04:00
|
|
|
rb_fiber_s_yield(int argc, VALUE *argv, VALUE klass)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
2019-10-04 22:51:57 +03:00
|
|
|
return rb_fiber_yield_kw(argc, argv, rb_keyword_given_p());
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2021-07-14 04:42:18 +03:00
|
|
|
static VALUE
|
2023-05-18 17:33:42 +03:00
|
|
|
fiber_raise(rb_fiber_t *fiber, VALUE exception)
|
2021-07-14 04:42:18 +03:00
|
|
|
{
|
2023-05-18 17:33:42 +03:00
|
|
|
if (FIBER_SUSPENDED_P(fiber) && !fiber->yielding) {
|
2021-07-14 04:42:18 +03:00
|
|
|
return fiber_transfer_kw(fiber, -1, &exception, RB_NO_KEYWORDS);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return fiber_resume_kw(fiber, -1, &exception, RB_NO_KEYWORDS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VALUE
|
2021-09-30 07:26:56 +03:00
|
|
|
rb_fiber_raise(VALUE fiber, int argc, const VALUE *argv)
|
2021-07-14 04:42:18 +03:00
|
|
|
{
|
2023-05-18 17:33:42 +03:00
|
|
|
VALUE exception = rb_make_exception(argc, argv);
|
|
|
|
|
|
|
|
return fiber_raise(fiber_ptr(fiber), exception);
|
2021-07-14 04:42:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* fiber.raise -> obj
|
|
|
|
* fiber.raise(string) -> obj
|
|
|
|
* fiber.raise(exception [, string [, array]]) -> obj
|
|
|
|
*
|
|
|
|
* Raises an exception in the fiber at the point at which the last
|
|
|
|
* +Fiber.yield+ was called. If the fiber has not been started or has
|
|
|
|
* already run to completion, raises +FiberError+. If the fiber is
|
|
|
|
* yielding, it is resumed. If it is transferring, it is transferred into.
|
|
|
|
* But if it is resuming, raises +FiberError+.
|
|
|
|
*
|
|
|
|
* With no arguments, raises a +RuntimeError+. With a single +String+
|
|
|
|
* argument, raises a +RuntimeError+ with the string as a message. Otherwise,
|
|
|
|
* the first parameter should be the name of an +Exception+ class (or an
|
|
|
|
* object that returns an +Exception+ object when sent an +exception+
|
|
|
|
* message). The optional second parameter sets the message associated with
|
|
|
|
* the exception, and the third parameter is an array of callback information.
|
|
|
|
* Exceptions are caught by the +rescue+ clause of <code>begin...end</code>
|
|
|
|
* blocks.
|
|
|
|
*/
|
|
|
|
static VALUE
|
|
|
|
rb_fiber_m_raise(int argc, VALUE *argv, VALUE self)
|
|
|
|
{
|
|
|
|
return rb_fiber_raise(self, argc, argv);
|
|
|
|
}
|
|
|
|
|
2023-05-18 17:33:42 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* fiber.kill -> nil
|
|
|
|
*
|
|
|
|
* Terminates +fiber+ by raising an uncatchable exception, returning
|
|
|
|
* the terminated Fiber.
|
|
|
|
*
|
|
|
|
* If the fiber has not been started, transition directly to the terminated state.
|
|
|
|
*
|
|
|
|
* If the fiber is already terminated, does nothing.
|
|
|
|
*/
|
|
|
|
static VALUE
|
|
|
|
rb_fiber_m_kill(VALUE self)
|
|
|
|
{
|
|
|
|
rb_fiber_t *fiber = fiber_ptr(self);
|
|
|
|
|
|
|
|
if (fiber->killed) return Qfalse;
|
|
|
|
fiber->killed = 1;
|
|
|
|
|
|
|
|
if (fiber->status == FIBER_CREATED) {
|
|
|
|
fiber->status = FIBER_TERMINATED;
|
|
|
|
}
|
|
|
|
else if (fiber->status != FIBER_TERMINATED) {
|
|
|
|
if (fiber_current() == fiber) {
|
|
|
|
fiber_check_killed(fiber);
|
|
|
|
} else {
|
|
|
|
fiber_raise(fiber_ptr(self), Qnil);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2008-12-27 03:25:47 +03:00
|
|
|
/*
|
|
|
|
* call-seq:
|
2020-12-24 12:00:23 +03:00
|
|
|
* Fiber.current -> fiber
|
2009-02-22 17:23:33 +03:00
|
|
|
*
|
2021-03-22 12:59:25 +03:00
|
|
|
* Returns the current fiber. If you are not running in the context of
|
2008-12-27 03:25:47 +03:00
|
|
|
* a fiber this method will return the root fiber.
|
|
|
|
*/
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
static VALUE
|
2007-08-21 22:51:39 +04:00
|
|
|
rb_fiber_s_current(VALUE klass)
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
{
|
2007-08-21 22:51:39 +04:00
|
|
|
return rb_fiber_current();
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
}
|
|
|
|
|
2017-08-10 05:58:36 +03:00
|
|
|
static VALUE
|
2019-07-08 08:59:28 +03:00
|
|
|
fiber_to_s(VALUE fiber_value)
|
2017-08-10 05:58:36 +03:00
|
|
|
{
|
2019-07-08 08:59:28 +03:00
|
|
|
const rb_fiber_t *fiber = fiber_ptr(fiber_value);
|
2017-08-10 05:58:36 +03:00
|
|
|
const rb_proc_t *proc;
|
2019-10-24 10:28:15 +03:00
|
|
|
char status_info[0x20];
|
|
|
|
|
2021-07-14 04:42:18 +03:00
|
|
|
if (fiber->resuming_fiber) {
|
relax Fiber#transfer's restriction
Using Fiber#transfer with Fiber#resume for a same Fiber is
limited (once Fiber#transfer is called for a fiber, the fiber
can not be resumed more). This restriction was introduced to
protect the resume/yield chain, but we realized that it is too much
to protect the chain. Instead of the current restriction, we
introduce some other protections.
(1) can not transfer to the resuming fiber.
(2) can not transfer to the yielding fiber.
(3) can not resume transferred fiber.
(4) can not yield from not-resumed fiber.
[Bug #17221]
Also at the end of a transferred fiber, it had continued on root fiber.
However, if the root fiber resumed a fiber (and that fiber can resumed
another fiber), this behavior also breaks the resume/yield chain.
So at the end of a transferred fiber, switch to the edge of resume
chain from root fiber.
For example, root fiber resumed f1 and f1 resumed f2, transferred to
f3 and f3 terminated, then continue from the fiber f2 (it was continued
from root fiber without this patch).
2020-10-07 19:15:32 +03:00
|
|
|
snprintf(status_info, 0x20, " (%s by resuming)", fiber_status_name(fiber->status));
|
2019-10-24 10:28:15 +03:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
snprintf(status_info, 0x20, " (%s)", fiber_status_name(fiber->status));
|
|
|
|
}
|
2017-08-10 05:58:36 +03:00
|
|
|
|
2019-07-08 08:59:28 +03:00
|
|
|
if (!rb_obj_is_proc(fiber->first_proc)) {
|
2019-07-08 10:00:29 +03:00
|
|
|
VALUE str = rb_any_to_s(fiber_value);
|
2019-06-01 08:48:25 +03:00
|
|
|
strlcat(status_info, ">", sizeof(status_info));
|
|
|
|
rb_str_set_len(str, RSTRING_LEN(str)-1);
|
|
|
|
rb_str_cat_cstr(str, status_info);
|
|
|
|
return str;
|
2017-09-02 04:47:43 +03:00
|
|
|
}
|
2019-07-08 08:59:28 +03:00
|
|
|
GetProcPtr(fiber->first_proc, proc);
|
|
|
|
return rb_block_to_s(fiber_value, &proc->block, status_info);
|
2017-08-10 05:58:36 +03:00
|
|
|
}
|
2010-05-08 08:50:09 +04:00
|
|
|
|
2018-08-29 11:04:09 +03:00
|
|
|
#ifdef HAVE_WORKING_FORK
|
|
|
|
void
|
|
|
|
rb_fiber_atfork(rb_thread_t *th)
|
|
|
|
{
|
2018-08-30 22:14:37 +03:00
|
|
|
if (th->root_fiber) {
|
|
|
|
if (&th->root_fiber->cont.saved_ec != th->ec) {
|
|
|
|
th->root_fiber = th->ec->fiber_ptr;
|
|
|
|
}
|
|
|
|
th->root_fiber->prev = 0;
|
2018-08-29 11:04:09 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
#ifdef RB_EXPERIMENTAL_FIBER_POOL
|
|
|
|
static void
|
|
|
|
fiber_pool_free(void *ptr)
|
|
|
|
{
|
|
|
|
struct fiber_pool * fiber_pool = ptr;
|
|
|
|
RUBY_FREE_ENTER("fiber_pool");
|
|
|
|
|
2022-03-16 12:41:00 +03:00
|
|
|
fiber_pool_allocation_free(fiber_pool->allocations);
|
2019-06-02 03:49:58 +03:00
|
|
|
ruby_xfree(fiber_pool);
|
|
|
|
|
|
|
|
RUBY_FREE_LEAVE("fiber_pool");
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
fiber_pool_memsize(const void *ptr)
|
|
|
|
{
|
|
|
|
const struct fiber_pool * fiber_pool = ptr;
|
|
|
|
size_t size = sizeof(*fiber_pool);
|
|
|
|
|
|
|
|
size += fiber_pool->count * fiber_pool->size;
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const rb_data_type_t FiberPoolDataType = {
|
|
|
|
"fiber_pool",
|
|
|
|
{NULL, fiber_pool_free, fiber_pool_memsize,},
|
|
|
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
|
|
};
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
fiber_pool_alloc(VALUE klass)
|
|
|
|
{
|
2022-03-16 12:41:45 +03:00
|
|
|
struct fiber_pool *fiber_pool;
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2022-03-16 12:41:45 +03:00
|
|
|
return TypedData_Make_Struct(klass, struct fiber_pool, &FiberPoolDataType, fiber_pool);
|
2019-06-02 03:49:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
rb_fiber_pool_initialize(int argc, VALUE* argv, VALUE self)
|
|
|
|
{
|
|
|
|
rb_thread_t *th = GET_THREAD();
|
|
|
|
VALUE size = Qnil, count = Qnil, vm_stack_size = Qnil;
|
|
|
|
struct fiber_pool * fiber_pool = NULL;
|
|
|
|
|
2019-12-20 03:19:39 +03:00
|
|
|
// Maybe these should be keyword arguments.
|
2019-06-02 03:49:58 +03:00
|
|
|
rb_scan_args(argc, argv, "03", &size, &count, &vm_stack_size);
|
|
|
|
|
|
|
|
if (NIL_P(size)) {
|
2022-03-13 15:10:33 +03:00
|
|
|
size = SIZET2NUM(th->vm->default_params.fiber_machine_stack_size);
|
2019-06-02 03:49:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (NIL_P(count)) {
|
|
|
|
count = INT2NUM(128);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NIL_P(vm_stack_size)) {
|
2022-03-13 15:10:33 +03:00
|
|
|
vm_stack_size = SIZET2NUM(th->vm->default_params.fiber_vm_stack_size);
|
2019-06-02 03:49:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
TypedData_Get_Struct(self, struct fiber_pool, &FiberPoolDataType, fiber_pool);
|
|
|
|
|
|
|
|
fiber_pool_initialize(fiber_pool, NUM2SIZET(size), NUM2SIZET(count), NUM2SIZET(vm_stack_size));
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-05-08 08:50:09 +04:00
|
|
|
/*
|
|
|
|
* Document-class: FiberError
|
|
|
|
*
|
|
|
|
* Raised when an invalid operation is attempted on a Fiber, in
|
|
|
|
* particular when attempting to call/resume a dead fiber,
|
|
|
|
* attempting to yield from the root fiber, or calling a fiber across
|
|
|
|
* threads.
|
|
|
|
*
|
|
|
|
* fiber = Fiber.new{}
|
|
|
|
* fiber.resume #=> nil
|
|
|
|
* fiber.resume #=> FiberError: dead fiber called
|
|
|
|
*/
|
|
|
|
|
2007-05-24 02:52:19 +04:00
|
|
|
void
|
|
|
|
Init_Cont(void)
|
|
|
|
{
|
2010-05-05 22:37:37 +04:00
|
|
|
rb_thread_t *th = GET_THREAD();
|
2019-06-02 03:49:58 +03:00
|
|
|
size_t vm_stack_size = th->vm->default_params.fiber_vm_stack_size;
|
|
|
|
size_t machine_stack_size = th->vm->default_params.fiber_machine_stack_size;
|
|
|
|
size_t stack_size = machine_stack_size + vm_stack_size;
|
2010-05-05 22:37:37 +04:00
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
SYSTEM_INFO info;
|
|
|
|
GetSystemInfo(&info);
|
|
|
|
pagesize = info.dwPageSize;
|
|
|
|
#else /* not WIN32 */
|
|
|
|
pagesize = sysconf(_SC_PAGESIZE);
|
|
|
|
#endif
|
2017-10-26 11:32:49 +03:00
|
|
|
SET_MACHINE_STACK_END(&th->ec->machine.stack_end);
|
2010-05-05 22:37:37 +04:00
|
|
|
|
2019-07-16 07:11:55 +03:00
|
|
|
fiber_pool_initialize(&shared_fiber_pool, stack_size, FIBER_POOL_INITIAL_SIZE, vm_stack_size);
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2020-05-14 13:10:55 +03:00
|
|
|
fiber_initialize_keywords[0] = rb_intern_const("blocking");
|
|
|
|
fiber_initialize_keywords[1] = rb_intern_const("pool");
|
2022-12-01 13:00:33 +03:00
|
|
|
fiber_initialize_keywords[2] = rb_intern_const("storage");
|
2020-05-14 13:10:55 +03:00
|
|
|
|
2021-03-12 10:46:40 +03:00
|
|
|
const char *fiber_shared_fiber_pool_free_stacks = getenv("RUBY_SHARED_FIBER_POOL_FREE_STACKS");
|
2019-07-16 07:35:55 +03:00
|
|
|
if (fiber_shared_fiber_pool_free_stacks) {
|
|
|
|
shared_fiber_pool.free_stacks = atoi(fiber_shared_fiber_pool_free_stacks);
|
2023-05-25 05:17:49 +03:00
|
|
|
|
|
|
|
if (shared_fiber_pool.free_stacks < 0) {
|
|
|
|
rb_warn("Setting RUBY_SHARED_FIBER_POOL_FREE_STACKS to a negative value is not allowed.");
|
|
|
|
shared_fiber_pool.free_stacks = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shared_fiber_pool.free_stacks > 1) {
|
|
|
|
rb_warn("Setting RUBY_SHARED_FIBER_POOL_FREE_STACKS to a value greater than 1 is operating system specific, and may cause crashes.");
|
|
|
|
}
|
2019-07-16 07:35:55 +03:00
|
|
|
}
|
|
|
|
|
* cont.c: support Fiber. Check test/ruby/test_fiber.rb for detail.
Fiber is known as "Micro Thread", "Coroutine", and other terms.
At this time, only Fiber#pass is supported to change context.
I want to know more suitable method name/API for Fiber (... do you
know more suitable class name instead of Fiber?) as "suspend/resume",
"call", "yield", "start/kick/stop/restart", ....
* eval.c, eval_intern.h, thread.c, yarvcore.c, yarvcore.h: ditto.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12395 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2007-05-27 23:12:43 +04:00
|
|
|
rb_cFiber = rb_define_class("Fiber", rb_cObject);
|
2008-11-28 18:23:09 +03:00
|
|
|
rb_define_alloc_func(rb_cFiber, fiber_alloc);
|
2007-06-02 11:48:29 +04:00
|
|
|
rb_eFiberError = rb_define_class("FiberError", rb_eStandardError);
|
2007-09-26 14:26:35 +04:00
|
|
|
rb_define_singleton_method(rb_cFiber, "yield", rb_fiber_s_yield, -1);
|
2021-01-04 04:36:38 +03:00
|
|
|
rb_define_singleton_method(rb_cFiber, "current", rb_fiber_s_current, 0);
|
2022-10-06 13:00:49 +03:00
|
|
|
rb_define_singleton_method(rb_cFiber, "blocking", rb_fiber_blocking, 0);
|
2022-12-01 13:00:33 +03:00
|
|
|
rb_define_singleton_method(rb_cFiber, "[]", rb_fiber_storage_aref, 1);
|
|
|
|
rb_define_singleton_method(rb_cFiber, "[]=", rb_fiber_storage_aset, 2);
|
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
rb_define_method(rb_cFiber, "initialize", rb_fiber_initialize, -1);
|
2020-05-14 13:10:55 +03:00
|
|
|
rb_define_method(rb_cFiber, "blocking?", rb_fiber_blocking_p, 0);
|
2022-12-01 13:00:33 +03:00
|
|
|
rb_define_method(rb_cFiber, "storage", rb_fiber_storage_get, 0);
|
|
|
|
rb_define_method(rb_cFiber, "storage=", rb_fiber_storage_set, 1);
|
2007-09-26 14:26:35 +04:00
|
|
|
rb_define_method(rb_cFiber, "resume", rb_fiber_m_resume, -1);
|
2021-07-14 04:42:18 +03:00
|
|
|
rb_define_method(rb_cFiber, "raise", rb_fiber_m_raise, -1);
|
2023-05-18 17:33:42 +03:00
|
|
|
rb_define_method(rb_cFiber, "kill", rb_fiber_m_kill, 0);
|
2020-08-15 06:36:18 +03:00
|
|
|
rb_define_method(rb_cFiber, "backtrace", rb_fiber_backtrace, -1);
|
|
|
|
rb_define_method(rb_cFiber, "backtrace_locations", rb_fiber_backtrace_locations, -1);
|
2017-08-10 05:58:36 +03:00
|
|
|
rb_define_method(rb_cFiber, "to_s", fiber_to_s, 0);
|
|
|
|
rb_define_alias(rb_cFiber, "inspect", "to_s");
|
2021-01-04 04:36:38 +03:00
|
|
|
rb_define_method(rb_cFiber, "transfer", rb_fiber_m_transfer, -1);
|
|
|
|
rb_define_method(rb_cFiber, "alive?", rb_fiber_alive_p, 0);
|
2019-06-02 03:49:58 +03:00
|
|
|
|
2021-03-12 12:15:56 +03:00
|
|
|
rb_define_singleton_method(rb_cFiber, "blocking?", rb_fiber_s_blocking_p, 0);
|
|
|
|
rb_define_singleton_method(rb_cFiber, "scheduler", rb_fiber_s_scheduler, 0);
|
2020-10-16 04:25:58 +03:00
|
|
|
rb_define_singleton_method(rb_cFiber, "set_scheduler", rb_fiber_set_scheduler, 1);
|
2021-02-12 06:54:52 +03:00
|
|
|
rb_define_singleton_method(rb_cFiber, "current_scheduler", rb_fiber_current_scheduler, 0);
|
2020-10-16 04:25:58 +03:00
|
|
|
|
2021-03-12 12:15:56 +03:00
|
|
|
rb_define_singleton_method(rb_cFiber, "schedule", rb_fiber_s_schedule, -1);
|
2020-05-14 13:10:55 +03:00
|
|
|
|
2019-06-02 03:49:58 +03:00
|
|
|
#ifdef RB_EXPERIMENTAL_FIBER_POOL
|
2022-03-13 15:07:49 +03:00
|
|
|
rb_cFiberPool = rb_define_class_under(rb_cFiber, "Pool", rb_cObject);
|
2019-06-02 03:49:58 +03:00
|
|
|
rb_define_alloc_func(rb_cFiberPool, fiber_pool_alloc);
|
|
|
|
rb_define_method(rb_cFiberPool, "initialize", rb_fiber_pool_initialize, -1);
|
|
|
|
#endif
|
2021-01-04 04:36:38 +03:00
|
|
|
|
|
|
|
rb_provide("fiber.so");
|
2007-05-24 02:52:19 +04:00
|
|
|
}
|
|
|
|
|
2013-04-05 14:29:38 +04:00
|
|
|
RUBY_SYMBOL_EXPORT_BEGIN
|
2010-07-22 01:38:25 +04:00
|
|
|
|
2007-08-25 06:03:44 +04:00
|
|
|
void
|
2009-01-20 10:17:28 +03:00
|
|
|
ruby_Init_Continuation_body(void)
|
2007-08-25 06:03:44 +04:00
|
|
|
{
|
|
|
|
rb_cContinuation = rb_define_class("Continuation", rb_cObject);
|
|
|
|
rb_undef_alloc_func(rb_cContinuation);
|
|
|
|
rb_undef_method(CLASS_OF(rb_cContinuation), "new");
|
|
|
|
rb_define_method(rb_cContinuation, "call", rb_cont_call, -1);
|
|
|
|
rb_define_method(rb_cContinuation, "[]", rb_cont_call, -1);
|
|
|
|
rb_define_global_function("callcc", rb_callcc, 0);
|
|
|
|
}
|
|
|
|
|
2013-04-05 14:29:38 +04:00
|
|
|
RUBY_SYMBOL_EXPORT_END
|