mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
Ractor: name the courier machinery after the courier, not after move
The off-heap courier started out as the move path's own structure, so every part of it was named move_*. Copy now travels in the same structure and through the same capture walk, with courier_build.copy selecting whether the source is read or taken apart, which left the prefix claiming something untrue: move_capture is what a copy goes through too. Rename the shared machinery to courier_*: the struct and its build/ materialize/mark/free entry points, the node kinds, the capture and materialize walks, and the array allocators. The two builders become rb_ractor_courier_build_copy and rb_ractor_courier_build_move so the pair reads as one family. What is still move-only keeps the move_ prefix -- move_preflight (copy has its own walk, copy_courier_supported_p) and move_neutralize_source, which is exactly the step copy skips. In re.c the MatchData transfer helpers become rb_match_blob_*, matching the neighbouring rb_backtrace_blob_* it sits beside in the same node. No behaviour change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2e53b0fd83
commit
d94c7ee6c6
5 files changed
+202
-201
Notes:
git
2026-08-20 02:57:22 +00:00
No files matched your search
+5
-5
@@ -65,11 +65,11 @@ long rb_reg_search0(VALUE, VALUE, long, int, int, VALUE *);
|
||||
VALUE rb_reg_match_p(VALUE re, VALUE str, long pos);
|
||||
VALUE rb_reg_regsub_match(VALUE str, VALUE src, VALUE match);
|
||||
VALUE rb_match_init_copy(VALUE copy, VALUE orig);
|
||||
/* MatchData transfer for the move courier (ractor.c). */
|
||||
void *rb_match_move_dump(VALUE match, VALUE *regexp_out, VALUE *str_out, int *num_regs_out, bool release_source);
|
||||
VALUE rb_match_move_alloc(VALUE klass, int num_regs);
|
||||
void rb_match_move_load(VALUE match, VALUE regexp, VALUE str, int num_regs, const void *blob);
|
||||
void rb_match_move_free(void *blob);
|
||||
/* MatchData transfer for the Ractor courier (ractor.c). */
|
||||
void *rb_match_blob_dump(VALUE match, VALUE *regexp_out, VALUE *str_out, int *num_regs_out, bool release_source);
|
||||
VALUE rb_match_blob_alloc(VALUE klass, int num_regs);
|
||||
void rb_match_blob_load(VALUE match, VALUE regexp, VALUE str, int num_regs, const void *blob);
|
||||
void rb_match_blob_free(void *blob);
|
||||
bool rb_reg_start_with_p(VALUE re, VALUE str);
|
||||
VALUE rb_reg_hash(VALUE re);
|
||||
VALUE rb_reg_equal(VALUE re1, VALUE re2);
|
||||
|
||||
@@ -2357,25 +2357,26 @@ rb_obj_traverse_replace(VALUE obj,
|
||||
}
|
||||
}
|
||||
|
||||
/* Move courier: serializes the payload of Ractor#send(move: true) into an xmalloc'd
|
||||
/* Courier: serializes a Ractor message payload -- copied or moved -- into an xmalloc'd
|
||||
* structure that belongs to no objspace, so no sender GC can mark, sweep, compact or
|
||||
* race with it. A node array with id references handles sharing and cycles, and the
|
||||
* receiver rebuilds it in its own objspace in two passes. */
|
||||
* receiver rebuilds it in its own objspace in two passes. Copy and move differ only in
|
||||
* whether the source is read or taken apart: see courier_build.copy. */
|
||||
|
||||
enum move_node_kind {
|
||||
MOVE_KIND_REF, /* an immediate or a shareable object: carried by value */
|
||||
MOVE_KIND_BACKTRACE, /* an exception's backtrace: frames copied into an off-heap blob */
|
||||
MOVE_KIND_STRING,
|
||||
MOVE_KIND_ARRAY,
|
||||
MOVE_KIND_HASH,
|
||||
MOVE_KIND_OBJECT,
|
||||
MOVE_KIND_STRUCT,
|
||||
MOVE_KIND_MATCH,
|
||||
MOVE_KIND_IO,
|
||||
enum courier_node_kind {
|
||||
COURIER_KIND_REF, /* an immediate or a shareable object: carried by value */
|
||||
COURIER_KIND_BACKTRACE, /* an exception's backtrace: frames copied into an off-heap blob */
|
||||
COURIER_KIND_STRING,
|
||||
COURIER_KIND_ARRAY,
|
||||
COURIER_KIND_HASH,
|
||||
COURIER_KIND_OBJECT,
|
||||
COURIER_KIND_STRUCT,
|
||||
COURIER_KIND_MATCH,
|
||||
COURIER_KIND_IO,
|
||||
};
|
||||
|
||||
struct move_node {
|
||||
enum move_node_kind kind;
|
||||
struct courier_node {
|
||||
enum courier_node_kind kind;
|
||||
bool frozen;
|
||||
/* The instance and generic ivars every non-REF node can have (a String or Array
|
||||
* can hold generic ivars too) */
|
||||
@@ -2404,11 +2405,11 @@ struct move_node {
|
||||
|
||||
/* A child slot holds a node id, or -- with this bit set -- an index into c->refs.
|
||||
* The courier is in-process, so a shareable payload can travel as the VALUE itself
|
||||
* instead of costing a whole move_node; the basket holding the courier marks c->refs. */
|
||||
#define MOVE_ID_REF_BIT 0x80000000u
|
||||
* instead of costing a whole courier_node; the basket holding the courier marks c->refs. */
|
||||
#define COURIER_ID_REF_BIT 0x80000000u
|
||||
|
||||
struct rb_ractor_move_courier {
|
||||
struct move_node *nodes;
|
||||
struct rb_ractor_courier {
|
||||
struct courier_node *nodes;
|
||||
uint32_t count;
|
||||
uint32_t capa;
|
||||
VALUE *refs; /* shareable payloads, embedded by value */
|
||||
@@ -2417,37 +2418,37 @@ struct rb_ractor_move_courier {
|
||||
uint32_t root;
|
||||
};
|
||||
|
||||
struct move_build {
|
||||
struct rb_ractor_move_courier *c;
|
||||
struct courier_build {
|
||||
struct rb_ractor_courier *c;
|
||||
st_table *seen; /* src VALUE -> (node id + 1) */
|
||||
/* Copy mode: read the sources instead of taking them apart. No husk, no buffer
|
||||
* hand-over, no freeing of the source's internals. */
|
||||
bool copy;
|
||||
};
|
||||
|
||||
static uint32_t move_capture(struct move_build *b, VALUE obj);
|
||||
static uint32_t courier_capture(struct courier_build *b, VALUE obj);
|
||||
|
||||
/* Off the hot path: the preflight sizes both arrays, so this only runs if its count
|
||||
* came out short. Swap a fresh array in rather than realloc -- the courier is a GC
|
||||
* root while it is being built, and a realloc leaves the old pointer live over a
|
||||
* window where it may already have been freed. */
|
||||
NOINLINE(static void move_grow_nodes(struct rb_ractor_move_courier *c));
|
||||
NOINLINE(static void move_grow_refs(struct rb_ractor_move_courier *c));
|
||||
NOINLINE(static void courier_grow_nodes(struct rb_ractor_courier *c));
|
||||
NOINLINE(static void courier_grow_refs(struct rb_ractor_courier *c));
|
||||
|
||||
static void
|
||||
move_grow_nodes(struct rb_ractor_move_courier *c)
|
||||
courier_grow_nodes(struct rb_ractor_courier *c)
|
||||
{
|
||||
uint32_t capa = c->capa ? c->capa * 2 : 8;
|
||||
struct move_node *nodes = ALLOC_N(struct move_node, capa);
|
||||
if (c->count > 0) MEMCPY(nodes, c->nodes, struct move_node, c->count);
|
||||
struct move_node *old_nodes = c->nodes;
|
||||
struct courier_node *nodes = ALLOC_N(struct courier_node, capa);
|
||||
if (c->count > 0) MEMCPY(nodes, c->nodes, struct courier_node, c->count);
|
||||
struct courier_node *old_nodes = c->nodes;
|
||||
c->nodes = nodes;
|
||||
c->capa = capa;
|
||||
ruby_xfree(old_nodes);
|
||||
}
|
||||
|
||||
static void
|
||||
move_grow_refs(struct rb_ractor_move_courier *c)
|
||||
courier_grow_refs(struct rb_ractor_courier *c)
|
||||
{
|
||||
uint32_t capa = c->refs_capa ? c->refs_capa * 2 : 8;
|
||||
VALUE *refs = ALLOC_N(VALUE, capa);
|
||||
@@ -2459,13 +2460,13 @@ move_grow_refs(struct rb_ractor_move_courier *c)
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
move_alloc_node(struct rb_ractor_move_courier *c)
|
||||
courier_alloc_node(struct rb_ractor_courier *c)
|
||||
{
|
||||
if (RB_UNLIKELY(c->count == c->capa)) move_grow_nodes(c);
|
||||
if (RB_UNLIKELY(c->count == c->capa)) courier_grow_nodes(c);
|
||||
uint32_t id = c->count++;
|
||||
/* Initialize to a harmless REF/Qnil so the courier mark (a GC root while sending)
|
||||
* is safe even mid-construction; a captured node overwrites it later. */
|
||||
c->nodes[id].kind = MOVE_KIND_REF;
|
||||
c->nodes[id].kind = COURIER_KIND_REF;
|
||||
c->nodes[id].frozen = false;
|
||||
c->nodes[id].niv = 0;
|
||||
c->nodes[id].iv_ids = NULL;
|
||||
@@ -2477,10 +2478,10 @@ move_alloc_node(struct rb_ractor_move_courier *c)
|
||||
/* Size the arrays from the preflight's count, so capture never grows them. A count
|
||||
* that turns out short is not a problem: the growth path below still works. */
|
||||
static void
|
||||
move_courier_reserve(struct rb_ractor_move_courier *c, uint32_t nodes, uint32_t refs)
|
||||
courier_reserve(struct rb_ractor_courier *c, uint32_t nodes, uint32_t refs)
|
||||
{
|
||||
if (nodes > 0) {
|
||||
c->nodes = ALLOC_N(struct move_node, nodes);
|
||||
c->nodes = ALLOC_N(struct courier_node, nodes);
|
||||
c->capa = nodes;
|
||||
}
|
||||
if (refs > 0) {
|
||||
@@ -2493,20 +2494,20 @@ move_courier_reserve(struct rb_ractor_move_courier *c, uint32_t nodes, uint32_t
|
||||
* is the same word however often it appears, and an array of immediates would otherwise
|
||||
* pay a lookup and an insert per element. */
|
||||
static uint32_t
|
||||
move_alloc_ref(struct rb_ractor_move_courier *c, VALUE v)
|
||||
courier_alloc_ref(struct rb_ractor_courier *c, VALUE v)
|
||||
{
|
||||
/* The count is bumped only after the slot holds a real VALUE: the courier is a GC
|
||||
* root while it is being built and must never be walkable half-written. */
|
||||
if (RB_UNLIKELY(c->refs_count == c->refs_capa)) move_grow_refs(c);
|
||||
if (RB_UNLIKELY(c->refs_count == c->refs_capa)) courier_grow_refs(c);
|
||||
c->refs[c->refs_count] = v;
|
||||
return MOVE_ID_REF_BIT | c->refs_count++;
|
||||
return COURIER_ID_REF_BIT | c->refs_count++;
|
||||
}
|
||||
|
||||
/* Resolve a child slot to the object it names. */
|
||||
static VALUE
|
||||
move_child(const struct rb_ractor_move_courier *c, VALUE shells, uint32_t id)
|
||||
courier_child(const struct rb_ractor_courier *c, VALUE shells, uint32_t id)
|
||||
{
|
||||
if (id & MOVE_ID_REF_BIT) return c->refs[id & ~MOVE_ID_REF_BIT];
|
||||
if (id & COURIER_ID_REF_BIT) return c->refs[id & ~COURIER_ID_REF_BIT];
|
||||
return RARRAY_AREF(shells, id);
|
||||
}
|
||||
|
||||
@@ -2557,25 +2558,25 @@ move_neutralize_source(VALUE obj)
|
||||
}
|
||||
}
|
||||
|
||||
struct move_hash_ctx {
|
||||
struct move_build *b;
|
||||
struct courier_hash_ctx {
|
||||
struct courier_build *b;
|
||||
uint32_t *kv;
|
||||
long i;
|
||||
};
|
||||
|
||||
static int
|
||||
move_capture_hash_i(st_data_t key, st_data_t val, st_data_t arg)
|
||||
courier_capture_hash_i(st_data_t key, st_data_t val, st_data_t arg)
|
||||
{
|
||||
struct move_hash_ctx *hc = (struct move_hash_ctx *)arg;
|
||||
uint32_t kid = move_capture(hc->b, (VALUE)key);
|
||||
uint32_t vid = move_capture(hc->b, (VALUE)val);
|
||||
struct courier_hash_ctx *hc = (struct courier_hash_ctx *)arg;
|
||||
uint32_t kid = courier_capture(hc->b, (VALUE)key);
|
||||
uint32_t vid = courier_capture(hc->b, (VALUE)val);
|
||||
hc->kv[hc->i++] = kid;
|
||||
hc->kv[hc->i++] = vid;
|
||||
return ST_CONTINUE;
|
||||
}
|
||||
|
||||
struct move_obj_ctx {
|
||||
struct move_build *b;
|
||||
struct courier_obj_ctx {
|
||||
struct courier_build *b;
|
||||
ID *ids;
|
||||
uint32_t *vals;
|
||||
long n;
|
||||
@@ -2583,15 +2584,15 @@ struct move_obj_ctx {
|
||||
};
|
||||
|
||||
static int
|
||||
move_capture_ivar_i(ID name, VALUE val, st_data_t arg)
|
||||
courier_capture_ivar_i(ID name, VALUE val, st_data_t arg)
|
||||
{
|
||||
struct move_obj_ctx *oc = (struct move_obj_ctx *)arg;
|
||||
struct courier_obj_ctx *oc = (struct courier_obj_ctx *)arg;
|
||||
if (oc->n == oc->capa) {
|
||||
oc->capa = oc->capa ? oc->capa * 2 : 4;
|
||||
REALLOC_N(oc->ids, ID, oc->capa);
|
||||
REALLOC_N(oc->vals, uint32_t, oc->capa);
|
||||
}
|
||||
uint32_t vid = move_capture(oc->b, val);
|
||||
uint32_t vid = courier_capture(oc->b, val);
|
||||
oc->ids[oc->n] = name;
|
||||
oc->vals[oc->n] = vid;
|
||||
oc->n++;
|
||||
@@ -2602,10 +2603,10 @@ move_capture_ivar_i(ID name, VALUE val, st_data_t arg)
|
||||
* Handles both a T_OBJECT's inline ivars and the generic ivars of a String, Array and
|
||||
* so on. */
|
||||
static void
|
||||
move_capture_ivars(struct move_build *b, VALUE obj, uint32_t id)
|
||||
courier_capture_ivars(struct courier_build *b, VALUE obj, uint32_t id)
|
||||
{
|
||||
struct move_obj_ctx oc = { b, NULL, NULL, 0, 0 };
|
||||
rb_ivar_foreach_buffered(obj, move_capture_ivar_i, (st_data_t)&oc);
|
||||
struct courier_obj_ctx oc = { b, NULL, NULL, 0, 0 };
|
||||
rb_ivar_foreach_buffered(obj, courier_capture_ivar_i, (st_data_t)&oc);
|
||||
b->c->nodes[id].niv = (uint32_t)oc.n;
|
||||
b->c->nodes[id].iv_ids = oc.ids;
|
||||
b->c->nodes[id].iv_vals = oc.vals;
|
||||
@@ -2613,15 +2614,15 @@ move_capture_ivars(struct move_build *b, VALUE obj, uint32_t id)
|
||||
|
||||
/* Capture obj into the courier, recurse into its children, return its node id. The id
|
||||
* is registered before recursing (a cycle back resolves to the same node); node fields
|
||||
* are written after (recursion can realloc c->nodes); the source is neutralized exactly
|
||||
* once after the switch. */
|
||||
* are written after (recursion can realloc c->nodes); a move neutralizes the source
|
||||
* exactly once after the switch. */
|
||||
static uint32_t
|
||||
move_capture(struct move_build *b, VALUE obj)
|
||||
courier_capture(struct courier_build *b, VALUE obj)
|
||||
{
|
||||
/* An immediate is never in seen (only captured objects are inserted), so it can
|
||||
* skip the lookup entirely: that is the whole cost of an array of numbers. */
|
||||
if (RB_SPECIAL_CONST_P(obj)) {
|
||||
return move_alloc_ref(b->c, obj);
|
||||
return courier_alloc_ref(b->c, obj);
|
||||
}
|
||||
|
||||
/* Seen first, and only then shareable: move husks each source as it goes, and a
|
||||
@@ -2634,10 +2635,10 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
}
|
||||
|
||||
if (rb_ractor_shareable_p(obj)) {
|
||||
return move_alloc_ref(b->c, obj);
|
||||
return courier_alloc_ref(b->c, obj);
|
||||
}
|
||||
|
||||
uint32_t id = move_alloc_node(b->c);
|
||||
uint32_t id = courier_alloc_node(b->c);
|
||||
st_insert(b->seen, (st_data_t)obj, (st_data_t)(uintptr_t)(id + 1));
|
||||
|
||||
/* Reject an unmovable object before anything is mutated. */
|
||||
@@ -2647,7 +2648,7 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
|
||||
bool frozen = OBJ_FROZEN(obj);
|
||||
b->c->nodes[id].frozen = frozen;
|
||||
move_capture_ivars(b, obj, id); /* shared: instance and generic ivars */
|
||||
courier_capture_ivars(b, obj, id); /* shared: instance and generic ivars */
|
||||
|
||||
switch (BUILTIN_TYPE(obj)) {
|
||||
case T_STRING: {
|
||||
@@ -2677,7 +2678,7 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
memset(ptr + len, 0, termlen);
|
||||
capa = len;
|
||||
}
|
||||
b->c->nodes[id].kind = MOVE_KIND_STRING;
|
||||
b->c->nodes[id].kind = COURIER_KIND_STRING;
|
||||
b->c->nodes[id].u.str.klass = RBASIC_CLASS(obj);
|
||||
b->c->nodes[id].u.str.ptr = ptr;
|
||||
b->c->nodes[id].u.str.len = len;
|
||||
@@ -2690,9 +2691,9 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
long len = RARRAY_LEN(obj);
|
||||
uint32_t *elems = len ? ALLOC_N(uint32_t, len) : NULL;
|
||||
for (long i = 0; i < len; i++) {
|
||||
elems[i] = move_capture(b, RARRAY_AREF(obj, i));
|
||||
elems[i] = courier_capture(b, RARRAY_AREF(obj, i));
|
||||
}
|
||||
b->c->nodes[id].kind = MOVE_KIND_ARRAY;
|
||||
b->c->nodes[id].kind = COURIER_KIND_ARRAY;
|
||||
b->c->nodes[id].u.ary.klass = RBASIC_CLASS(obj);
|
||||
b->c->nodes[id].u.ary.len = len;
|
||||
b->c->nodes[id].u.ary.elems = elems;
|
||||
@@ -2706,12 +2707,12 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
}
|
||||
|
||||
case T_HASH: {
|
||||
uint32_t ifnone_id = move_capture(b, RHASH_IFNONE(obj));
|
||||
uint32_t ifnone_id = courier_capture(b, RHASH_IFNONE(obj));
|
||||
long size = RHASH_SIZE(obj);
|
||||
uint32_t *kv = size ? ALLOC_N(uint32_t, size * 2) : NULL;
|
||||
struct move_hash_ctx hc = { b, kv, 0 };
|
||||
rb_hash_stlike_foreach(obj, move_capture_hash_i, (st_data_t)&hc);
|
||||
b->c->nodes[id].kind = MOVE_KIND_HASH;
|
||||
struct courier_hash_ctx hc = { b, kv, 0 };
|
||||
rb_hash_stlike_foreach(obj, courier_capture_hash_i, (st_data_t)&hc);
|
||||
b->c->nodes[id].kind = COURIER_KIND_HASH;
|
||||
b->c->nodes[id].u.hash.klass = RBASIC_CLASS(obj);
|
||||
b->c->nodes[id].u.hash.size = size;
|
||||
b->c->nodes[id].u.hash.kv = kv;
|
||||
@@ -2724,7 +2725,7 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
}
|
||||
|
||||
case T_OBJECT:
|
||||
b->c->nodes[id].kind = MOVE_KIND_OBJECT;
|
||||
b->c->nodes[id].kind = COURIER_KIND_OBJECT;
|
||||
/* Keep the real class: even a singleton class is shareable, so a cross-objspace
|
||||
* reference is safe. rebuild re-attaches it after allocating with a
|
||||
* non-singleton class. */
|
||||
@@ -2735,9 +2736,9 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
long len = RSTRUCT_LEN(obj);
|
||||
uint32_t *elems = len ? ALLOC_N(uint32_t, len) : NULL;
|
||||
for (long i = 0; i < len; i++) {
|
||||
elems[i] = move_capture(b, RSTRUCT_GET(obj, (int)i));
|
||||
elems[i] = courier_capture(b, RSTRUCT_GET(obj, (int)i));
|
||||
}
|
||||
b->c->nodes[id].kind = MOVE_KIND_STRUCT;
|
||||
b->c->nodes[id].kind = COURIER_KIND_STRUCT;
|
||||
b->c->nodes[id].u.strct.len = len;
|
||||
b->c->nodes[id].u.strct.elems = elems;
|
||||
b->c->nodes[id].u.strct.klass = RBASIC_CLASS(obj);
|
||||
@@ -2753,10 +2754,10 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
* registers (freeing the source's onig and char_offset). */
|
||||
VALUE re, st;
|
||||
int nregs;
|
||||
void *regs = rb_match_move_dump(obj, &re, &st, &nregs, !b->copy);
|
||||
uint32_t rid = move_capture(b, re);
|
||||
uint32_t sid = move_capture(b, st);
|
||||
b->c->nodes[id].kind = MOVE_KIND_MATCH;
|
||||
void *regs = rb_match_blob_dump(obj, &re, &st, &nregs, !b->copy);
|
||||
uint32_t rid = courier_capture(b, re);
|
||||
uint32_t sid = courier_capture(b, st);
|
||||
b->c->nodes[id].kind = COURIER_KIND_MATCH;
|
||||
b->c->nodes[id].u.match.regexp_id = rid;
|
||||
b->c->nodes[id].u.match.str_id = sid;
|
||||
b->c->nodes[id].u.match.num_regs = nregs;
|
||||
@@ -2773,11 +2774,11 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
* so capture them as ordinary child nodes, detached; rebuild writes them back. */
|
||||
struct rb_io *fptr = RFILE(obj)->fptr;
|
||||
VM_ASSERT(!RTEST(fptr->tied_io_for_writing) && !RTEST(fptr->wakeup_mutex));
|
||||
uint32_t pathv_id = move_capture(b, fptr->pathv);
|
||||
uint32_t ecopts_id = move_capture(b, fptr->encs.ecopts);
|
||||
uint32_t wc_pre_id = move_capture(b, fptr->writeconv_pre_ecopts);
|
||||
uint32_t wc_ac_id = move_capture(b, fptr->writeconv_asciicompat);
|
||||
uint32_t timeout_id = move_capture(b, fptr->timeout);
|
||||
uint32_t pathv_id = courier_capture(b, fptr->pathv);
|
||||
uint32_t ecopts_id = courier_capture(b, fptr->encs.ecopts);
|
||||
uint32_t wc_pre_id = courier_capture(b, fptr->writeconv_pre_ecopts);
|
||||
uint32_t wc_ac_id = courier_capture(b, fptr->writeconv_asciicompat);
|
||||
uint32_t timeout_id = courier_capture(b, fptr->timeout);
|
||||
fptr->self = Qnil; /* it points at the moved-from T_MOVED; attach rebuilds it */
|
||||
fptr->pathv = Qnil;
|
||||
fptr->encs.ecopts = Qnil;
|
||||
@@ -2787,7 +2788,7 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
fptr->write_lock = Qnil;
|
||||
fptr->wakeup_mutex = Qnil;
|
||||
fptr->tied_io_for_writing = 0; /* io.c tests it as a C boolean, so 0 rather than Qnil */
|
||||
b->c->nodes[id].kind = MOVE_KIND_IO;
|
||||
b->c->nodes[id].kind = COURIER_KIND_IO;
|
||||
b->c->nodes[id].u.io.fptr = fptr;
|
||||
b->c->nodes[id].u.io.klass = RBASIC_CLASS(obj);
|
||||
b->c->nodes[id].u.io.pathv_id = pathv_id;
|
||||
@@ -2804,7 +2805,7 @@ move_capture(struct move_build *b, VALUE obj)
|
||||
if (b->copy && rb_backtrace_p(obj)) {
|
||||
int size;
|
||||
void *blob = rb_backtrace_blob_dump(obj, &size);
|
||||
b->c->nodes[id].kind = MOVE_KIND_BACKTRACE;
|
||||
b->c->nodes[id].kind = COURIER_KIND_BACKTRACE;
|
||||
b->c->nodes[id].u.bt.blob = blob;
|
||||
b->c->nodes[id].u.bt.size = size;
|
||||
break;
|
||||
@@ -2842,7 +2843,7 @@ move_preflight_hash_i(st_data_t key, st_data_t val, st_data_t arg)
|
||||
return ST_CONTINUE;
|
||||
}
|
||||
|
||||
/* A read-only pre-walk of move_capture's decision tree. Capture turns sources into
|
||||
/* A read-only pre-walk of courier_capture's decision tree. Capture turns sources into
|
||||
* T_MOVED as it goes, so an unmovable object midway would leave the graph broken beyond
|
||||
* repair; every "can not move" error is raised here, before anything is mutated. */
|
||||
static void
|
||||
@@ -2912,7 +2913,7 @@ move_preflight(VALUE obj, struct move_preflight_ctx *ctx)
|
||||
}
|
||||
|
||||
/* The walk also sizes the courier: one node per distinct unshareable object, one ref
|
||||
* per occurrence of a shareable one -- exactly what move_capture allocates, so the
|
||||
* per occurrence of a shareable one -- exactly what courier_capture allocates, so the
|
||||
* arrays never have to grow while the graph is being captured. */
|
||||
struct copy_support_ctx {
|
||||
st_table *seen;
|
||||
@@ -3006,8 +3007,8 @@ copy_courier_supported_p(VALUE obj, struct copy_support_ctx *ctx)
|
||||
|
||||
/* Build a courier holding a copy of obj's graph, leaving the sources untouched.
|
||||
* Returns NULL when the graph has a type only the on-heap snapshot path handles. */
|
||||
struct rb_ractor_move_courier *
|
||||
rb_ractor_copy_courier_build(VALUE obj, struct rb_ractor_move_courier **slot)
|
||||
struct rb_ractor_courier *
|
||||
rb_ractor_courier_build_copy(VALUE obj, struct rb_ractor_courier **slot)
|
||||
{
|
||||
struct copy_support_ctx scan = { st_init_numtable(), 0, 0, true };
|
||||
{
|
||||
@@ -3016,9 +3017,9 @@ rb_ractor_copy_courier_build(VALUE obj, struct rb_ractor_move_courier **slot)
|
||||
if (!ok) return NULL;
|
||||
}
|
||||
|
||||
struct rb_ractor_move_courier *c = ZALLOC(struct rb_ractor_move_courier);
|
||||
move_courier_reserve(c, scan.nodes, scan.refs);
|
||||
struct move_build b = { c, st_init_numtable(), true };
|
||||
struct rb_ractor_courier *c = ZALLOC(struct rb_ractor_courier);
|
||||
courier_reserve(c, scan.nodes, scan.refs);
|
||||
struct courier_build b = { c, st_init_numtable(), true };
|
||||
|
||||
/* Publish it into the caller's basket before capturing anything: from here the
|
||||
* shareable payloads it collects are rooted by the basket's holder. */
|
||||
@@ -3028,7 +3029,7 @@ rb_ractor_copy_courier_build(VALUE obj, struct rb_ractor_move_courier **slot)
|
||||
rb_execution_context_t *ec = GET_EC();
|
||||
EC_PUSH_TAG(ec);
|
||||
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
|
||||
c->root = move_capture(&b, obj);
|
||||
c->root = courier_capture(&b, obj);
|
||||
}
|
||||
EC_POP_TAG();
|
||||
st_free_table(b.seen);
|
||||
@@ -3037,10 +3038,10 @@ rb_ractor_copy_courier_build(VALUE obj, struct rb_ractor_move_courier **slot)
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Build a move courier from obj and turn every captured source into a
|
||||
* RactorMovedObject (move semantics). Returns the xmalloc'd courier. */
|
||||
struct rb_ractor_move_courier *
|
||||
rb_ractor_move_courier_build(VALUE obj, struct rb_ractor_move_courier **slot)
|
||||
/* Build a courier from obj and turn every captured source into a RactorMovedObject
|
||||
* (move semantics). Returns the xmalloc'd courier. */
|
||||
struct rb_ractor_courier *
|
||||
rb_ractor_courier_build_move(VALUE obj, struct rb_ractor_courier **slot)
|
||||
{
|
||||
/* Two phases, preflight then commit, so an unmovable object is raised from the
|
||||
* read-only walk while the graph is still intact. */
|
||||
@@ -3057,9 +3058,9 @@ rb_ractor_move_courier_build(VALUE obj, struct rb_ractor_move_courier **slot)
|
||||
if (state != TAG_NONE) EC_JUMP_TAG(ec, state);
|
||||
}
|
||||
|
||||
struct rb_ractor_move_courier *c = ZALLOC(struct rb_ractor_move_courier);
|
||||
move_courier_reserve(c, scan.nodes, scan.refs);
|
||||
struct move_build b = { c, st_init_numtable(), false };
|
||||
struct rb_ractor_courier *c = ZALLOC(struct rb_ractor_courier);
|
||||
courier_reserve(c, scan.nodes, scan.refs);
|
||||
struct courier_build b = { c, st_init_numtable(), false };
|
||||
|
||||
/* Publish it into the caller's basket before the sources become T_MOVED: from here
|
||||
* the basket's holder roots what the courier carries, and partial nodes are
|
||||
@@ -3070,14 +3071,14 @@ rb_ractor_move_courier_build(VALUE obj, struct rb_ractor_move_courier **slot)
|
||||
rb_execution_context_t *ec = GET_EC();
|
||||
EC_PUSH_TAG(ec);
|
||||
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
|
||||
c->root = move_capture(&b, obj);
|
||||
c->root = courier_capture(&b, obj);
|
||||
}
|
||||
EC_POP_TAG();
|
||||
st_free_table(b.seen);
|
||||
if (state != TAG_NONE) {
|
||||
/* move_capture raised (an unmovable type, an interrupt). The courier belongs to
|
||||
* the basket from the publish above, so leave it there and re-raise: the basket
|
||||
* frees it, once, on the way out. */
|
||||
/* courier_capture raised (an unmovable type, an interrupt). The courier belongs
|
||||
* to the basket from the publish above, so leave it there and re-raise: the
|
||||
* basket frees it, once, on the way out. */
|
||||
EC_JUMP_TAG(ec, state);
|
||||
}
|
||||
return c;
|
||||
@@ -3087,7 +3088,7 @@ rb_ractor_move_courier_build(VALUE obj, struct rb_ractor_move_courier **slot)
|
||||
* singleton class (classes are shareable; the reference is safe). A singleton's
|
||||
* attached object still points at the sender's source: re-attach it to the shell. */
|
||||
static void
|
||||
move_apply_moved_klass(VALUE shell, VALUE klass)
|
||||
courier_apply_klass(VALUE shell, VALUE klass)
|
||||
{
|
||||
if (klass != RBASIC_CLASS(shell)) {
|
||||
RBASIC_SET_CLASS(shell, klass);
|
||||
@@ -3100,105 +3101,105 @@ move_apply_moved_klass(VALUE shell, VALUE klass)
|
||||
/* Rebuild the courier's graph in the current Ractor's objspace and return its root.
|
||||
* Two passes (allocate shells, then fill) break reference cycles. */
|
||||
VALUE
|
||||
rb_ractor_move_courier_materialize(struct rb_ractor_move_courier *c)
|
||||
rb_ractor_courier_materialize(struct rb_ractor_courier *c)
|
||||
{
|
||||
/* A hidden Array roots every shell, keeping them alive while the allocations that
|
||||
* build the rest of the graph (which can start this Ractor's GC) run. */
|
||||
VALUE shells = rb_ary_hidden_new(c->count);
|
||||
|
||||
for (uint32_t i = 0; i < c->count; i++) {
|
||||
struct move_node *n = &c->nodes[i];
|
||||
struct courier_node *n = &c->nodes[i];
|
||||
VALUE shell;
|
||||
switch (n->kind) {
|
||||
case MOVE_KIND_REF:
|
||||
case COURIER_KIND_REF:
|
||||
shell = n->u.ref;
|
||||
break;
|
||||
case MOVE_KIND_STRING:
|
||||
case COURIER_KIND_STRING:
|
||||
/* Hand the courier's buffer to the String instead of copying it again: the
|
||||
* bytes were already copied (or taken from the source) when the node was
|
||||
* built. */
|
||||
shell = rb_str_new_owned(n->u.str.ptr, n->u.str.len, n->u.str.capa, n->u.str.encidx);
|
||||
n->u.str.ptr = NULL; /* consumed: the new String owns it now */
|
||||
move_apply_moved_klass(shell, n->u.str.klass);
|
||||
courier_apply_klass(shell, n->u.str.klass);
|
||||
break;
|
||||
case MOVE_KIND_ARRAY:
|
||||
case COURIER_KIND_ARRAY:
|
||||
shell = rb_ary_new_capa(n->u.ary.len);
|
||||
move_apply_moved_klass(shell, n->u.ary.klass);
|
||||
courier_apply_klass(shell, n->u.ary.klass);
|
||||
break;
|
||||
case MOVE_KIND_HASH:
|
||||
case COURIER_KIND_HASH:
|
||||
shell = n->u.hash.compare_by_id ? rb_ident_hash_new() : rb_hash_new();
|
||||
move_apply_moved_klass(shell, n->u.hash.klass);
|
||||
courier_apply_klass(shell, n->u.hash.klass);
|
||||
break;
|
||||
case MOVE_KIND_OBJECT:
|
||||
case COURIER_KIND_OBJECT:
|
||||
/* A singleton class cannot allocate, so make an instance of the real class
|
||||
* and re-attach it afterwards */
|
||||
shell = rb_obj_alloc(rb_class_real(n->u.obj.klass));
|
||||
move_apply_moved_klass(shell, n->u.obj.klass);
|
||||
courier_apply_klass(shell, n->u.obj.klass);
|
||||
break;
|
||||
case MOVE_KIND_STRUCT:
|
||||
case COURIER_KIND_STRUCT:
|
||||
shell = rb_obj_alloc(rb_class_real(n->u.strct.klass));
|
||||
move_apply_moved_klass(shell, n->u.strct.klass);
|
||||
courier_apply_klass(shell, n->u.strct.klass);
|
||||
break;
|
||||
case MOVE_KIND_MATCH:
|
||||
shell = rb_match_move_alloc(rb_class_real(n->u.match.klass), n->u.match.num_regs);
|
||||
move_apply_moved_klass(shell, n->u.match.klass);
|
||||
case COURIER_KIND_MATCH:
|
||||
shell = rb_match_blob_alloc(rb_class_real(n->u.match.klass), n->u.match.num_regs);
|
||||
courier_apply_klass(shell, n->u.match.klass);
|
||||
break;
|
||||
case MOVE_KIND_BACKTRACE:
|
||||
case COURIER_KIND_BACKTRACE:
|
||||
shell = rb_backtrace_blob_load(n->u.bt.blob, n->u.bt.size);
|
||||
break;
|
||||
case MOVE_KIND_IO:
|
||||
case COURIER_KIND_IO:
|
||||
shell = rb_obj_alloc(rb_class_real(n->u.io.klass));
|
||||
move_apply_moved_klass(shell, n->u.io.klass);
|
||||
courier_apply_klass(shell, n->u.io.klass);
|
||||
RFILE(shell)->fptr = n->u.io.fptr;
|
||||
n->u.io.fptr->self = shell;
|
||||
n->u.io.fptr = NULL; /* consumed: the new IO owns it now */
|
||||
break;
|
||||
default:
|
||||
rb_bug("rb_ractor_move_courier_materialize: bad node kind");
|
||||
rb_bug("rb_ractor_courier_materialize: bad node kind");
|
||||
}
|
||||
rb_ary_push(shells, shell);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < c->count; i++) {
|
||||
struct move_node *n = &c->nodes[i];
|
||||
struct courier_node *n = &c->nodes[i];
|
||||
VALUE shell = RARRAY_AREF(shells, i);
|
||||
switch (n->kind) {
|
||||
case MOVE_KIND_ARRAY: {
|
||||
case COURIER_KIND_ARRAY: {
|
||||
/* The length is known, so set it once and write the slots, rather than
|
||||
* pushing each element through the capacity check. */
|
||||
const long len = n->u.ary.len;
|
||||
if (len > 0) {
|
||||
rb_ary_resize(shell, len);
|
||||
for (long j = 0; j < len; j++) {
|
||||
RARRAY_ASET(shell, j, move_child(c, shells, n->u.ary.elems[j]));
|
||||
RARRAY_ASET(shell, j, courier_child(c, shells, n->u.ary.elems[j]));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MOVE_KIND_HASH:
|
||||
case COURIER_KIND_HASH:
|
||||
/* Entry insertion is deferred to a third pass: insertion calls the key's
|
||||
* #hash / #eql?, and a content-based #hash would collide on every key while
|
||||
* the graph is still empty, collapsing entries. */
|
||||
break;
|
||||
case MOVE_KIND_STRUCT:
|
||||
case COURIER_KIND_STRUCT:
|
||||
for (long j = 0; j < n->u.strct.len; j++) {
|
||||
RSTRUCT_SET(shell, (int)j, move_child(c, shells, n->u.strct.elems[j]));
|
||||
RSTRUCT_SET(shell, (int)j, courier_child(c, shells, n->u.strct.elems[j]));
|
||||
}
|
||||
break;
|
||||
case MOVE_KIND_MATCH:
|
||||
rb_match_move_load(shell, move_child(c, shells, n->u.match.regexp_id),
|
||||
move_child(c, shells, n->u.match.str_id),
|
||||
case COURIER_KIND_MATCH:
|
||||
rb_match_blob_load(shell, courier_child(c, shells, n->u.match.regexp_id),
|
||||
courier_child(c, shells, n->u.match.str_id),
|
||||
n->u.match.num_regs, n->u.match.regs);
|
||||
break;
|
||||
case MOVE_KIND_IO: {
|
||||
case COURIER_KIND_IO: {
|
||||
/* Write the rebuilt VALUE members back into fptr (capture detached them).
|
||||
* write_lock and wakeup_mutex stay nil; io.c recreates them lazily. */
|
||||
struct rb_io *fptr = RFILE(shell)->fptr;
|
||||
RB_OBJ_WRITE(shell, &fptr->pathv, move_child(c, shells, n->u.io.pathv_id));
|
||||
RB_OBJ_WRITE(shell, &fptr->encs.ecopts, move_child(c, shells, n->u.io.ecopts_id));
|
||||
RB_OBJ_WRITE(shell, &fptr->writeconv_pre_ecopts, move_child(c, shells, n->u.io.wc_pre_ecopts_id));
|
||||
RB_OBJ_WRITE(shell, &fptr->writeconv_asciicompat, move_child(c, shells, n->u.io.wc_asciicompat_id));
|
||||
RB_OBJ_WRITE(shell, &fptr->timeout, move_child(c, shells, n->u.io.timeout_id));
|
||||
RB_OBJ_WRITE(shell, &fptr->pathv, courier_child(c, shells, n->u.io.pathv_id));
|
||||
RB_OBJ_WRITE(shell, &fptr->encs.ecopts, courier_child(c, shells, n->u.io.ecopts_id));
|
||||
RB_OBJ_WRITE(shell, &fptr->writeconv_pre_ecopts, courier_child(c, shells, n->u.io.wc_pre_ecopts_id));
|
||||
RB_OBJ_WRITE(shell, &fptr->writeconv_asciicompat, courier_child(c, shells, n->u.io.wc_asciicompat_id));
|
||||
RB_OBJ_WRITE(shell, &fptr->timeout, courier_child(c, shells, n->u.io.timeout_id));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -3206,7 +3207,7 @@ rb_ractor_move_courier_materialize(struct rb_ractor_move_courier *c)
|
||||
}
|
||||
/* Restore instance and generic ivars (any non-REF node can have them) */
|
||||
for (uint32_t j = 0; j < n->niv; j++) {
|
||||
rb_ivar_set(shell, n->iv_ids[j], move_child(c, shells, n->iv_vals[j]));
|
||||
rb_ivar_set(shell, n->iv_ids[j], courier_child(c, shells, n->iv_vals[j]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3214,15 +3215,15 @@ rb_ractor_move_courier_materialize(struct rb_ractor_move_courier *c)
|
||||
* depth-first (children larger), so inserting in reverse settles nested hash keys
|
||||
* inside-out (a #hash cycling through itself is out of scope). */
|
||||
for (uint32_t i = c->count; i > 0; i--) {
|
||||
struct move_node *n = &c->nodes[i - 1];
|
||||
if (n->kind != MOVE_KIND_HASH) continue;
|
||||
struct courier_node *n = &c->nodes[i - 1];
|
||||
if (n->kind != COURIER_KIND_HASH) continue;
|
||||
VALUE shell = RARRAY_AREF(shells, i - 1);
|
||||
for (long j = 0; j < n->u.hash.size; j++) {
|
||||
rb_hash_aset(shell, move_child(c, shells, n->u.hash.kv[2 * j]),
|
||||
move_child(c, shells, n->u.hash.kv[2 * j + 1]));
|
||||
rb_hash_aset(shell, courier_child(c, shells, n->u.hash.kv[2 * j]),
|
||||
courier_child(c, shells, n->u.hash.kv[2 * j + 1]));
|
||||
}
|
||||
/* Restore the default value and default proc (before freezing) */
|
||||
VALUE ifnone = move_child(c, shells, n->u.hash.ifnone_id);
|
||||
VALUE ifnone = courier_child(c, shells, n->u.hash.ifnone_id);
|
||||
if (n->u.hash.proc_default) {
|
||||
rb_hash_set_default_proc(shell, ifnone);
|
||||
}
|
||||
@@ -3239,38 +3240,38 @@ rb_ractor_move_courier_materialize(struct rb_ractor_move_courier *c)
|
||||
}
|
||||
}
|
||||
|
||||
VALUE root = (c->count || c->refs_count) ? move_child(c, shells, c->root) : Qnil;
|
||||
VALUE root = (c->count || c->refs_count) ? courier_child(c, shells, c->root) : Qnil;
|
||||
RB_GC_GUARD(shells);
|
||||
return root;
|
||||
}
|
||||
|
||||
void
|
||||
rb_ractor_move_courier_free(struct rb_ractor_move_courier *c)
|
||||
rb_ractor_courier_free(struct rb_ractor_courier *c)
|
||||
{
|
||||
for (uint32_t i = 0; i < c->count; i++) {
|
||||
struct move_node *n = &c->nodes[i];
|
||||
struct courier_node *n = &c->nodes[i];
|
||||
ruby_xfree(n->iv_ids);
|
||||
ruby_xfree(n->iv_vals);
|
||||
switch (n->kind) {
|
||||
case MOVE_KIND_STRING:
|
||||
case COURIER_KIND_STRING:
|
||||
ruby_xfree(n->u.str.ptr);
|
||||
break;
|
||||
case MOVE_KIND_ARRAY:
|
||||
case COURIER_KIND_ARRAY:
|
||||
ruby_xfree(n->u.ary.elems);
|
||||
break;
|
||||
case MOVE_KIND_HASH:
|
||||
case COURIER_KIND_HASH:
|
||||
ruby_xfree(n->u.hash.kv);
|
||||
break;
|
||||
case MOVE_KIND_STRUCT:
|
||||
case COURIER_KIND_STRUCT:
|
||||
ruby_xfree(n->u.strct.elems);
|
||||
break;
|
||||
case MOVE_KIND_MATCH:
|
||||
rb_match_move_free(n->u.match.regs);
|
||||
case COURIER_KIND_MATCH:
|
||||
rb_match_blob_free(n->u.match.regs);
|
||||
break;
|
||||
case MOVE_KIND_BACKTRACE:
|
||||
case COURIER_KIND_BACKTRACE:
|
||||
ruby_xfree(n->u.bt.blob);
|
||||
break;
|
||||
case MOVE_KIND_IO:
|
||||
case COURIER_KIND_IO:
|
||||
/* A delivered IO left fptr == NULL (the rebuilt IO owns it). An
|
||||
* undelivered one still owns the fd and its source is already a
|
||||
* RactorMovedObject nobody can close: close it here, not leak it. */
|
||||
@@ -3292,39 +3293,39 @@ rb_ractor_move_courier_free(struct rb_ractor_move_courier *c)
|
||||
* classes of its objects. All of them are shareable, so marking cannot race, and the
|
||||
* global GC keeps them reachable through the courier. */
|
||||
void
|
||||
rb_ractor_move_courier_mark(struct rb_ractor_move_courier *c)
|
||||
rb_ractor_courier_mark(struct rb_ractor_courier *c)
|
||||
{
|
||||
if (!c) return;
|
||||
for (uint32_t i = 0; i < c->refs_count; i++) {
|
||||
rb_gc_mark(c->refs[i]);
|
||||
}
|
||||
for (uint32_t i = 0; i < c->count; i++) {
|
||||
struct move_node *n = &c->nodes[i];
|
||||
if (n->kind == MOVE_KIND_REF) {
|
||||
struct courier_node *n = &c->nodes[i];
|
||||
if (n->kind == COURIER_KIND_REF) {
|
||||
rb_gc_mark(n->u.ref);
|
||||
}
|
||||
else if (n->kind == MOVE_KIND_OBJECT) {
|
||||
else if (n->kind == COURIER_KIND_OBJECT) {
|
||||
rb_gc_mark(n->u.obj.klass);
|
||||
}
|
||||
else if (n->kind == MOVE_KIND_STRUCT) {
|
||||
else if (n->kind == COURIER_KIND_STRUCT) {
|
||||
rb_gc_mark(n->u.strct.klass);
|
||||
}
|
||||
else if (n->kind == MOVE_KIND_MATCH) {
|
||||
else if (n->kind == COURIER_KIND_MATCH) {
|
||||
rb_gc_mark(n->u.match.klass);
|
||||
}
|
||||
else if (n->kind == MOVE_KIND_IO) {
|
||||
else if (n->kind == COURIER_KIND_IO) {
|
||||
rb_gc_mark(n->u.io.klass);
|
||||
}
|
||||
else if (n->kind == MOVE_KIND_STRING) {
|
||||
else if (n->kind == COURIER_KIND_STRING) {
|
||||
rb_gc_mark(n->u.str.klass);
|
||||
}
|
||||
else if (n->kind == MOVE_KIND_BACKTRACE) {
|
||||
else if (n->kind == COURIER_KIND_BACKTRACE) {
|
||||
rb_backtrace_blob_mark(n->u.bt.blob, n->u.bt.size);
|
||||
}
|
||||
else if (n->kind == MOVE_KIND_ARRAY) {
|
||||
else if (n->kind == COURIER_KIND_ARRAY) {
|
||||
rb_gc_mark(n->u.ary.klass);
|
||||
}
|
||||
else if (n->kind == MOVE_KIND_HASH) {
|
||||
else if (n->kind == COURIER_KIND_HASH) {
|
||||
rb_gc_mark(n->u.hash.klass);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
#define RUBY_TYPED_FROZEN_SHAREABLE_NO_REC RUBY_FL_FINALIZE
|
||||
|
||||
/* An in-flight move payload, serialized off-heap (defined in ractor.c). */
|
||||
struct rb_ractor_move_courier;
|
||||
struct rb_ractor_courier;
|
||||
|
||||
struct rb_ractor_sync {
|
||||
// ractor lock
|
||||
|
||||
+28
-28
@@ -19,14 +19,14 @@ static struct ractor_basket *ractor_basket_new_ref(VALUE shareable);
|
||||
static void ractor_send_basket(rb_execution_context_t *ec, const struct ractor_port *rp, struct ractor_basket *b, bool raise_on_error);
|
||||
static void ractor_add_port(rb_ractor_t *r, st_data_t id);
|
||||
|
||||
// The off-heap courier used for moves. It is defined in ractor.c.
|
||||
struct rb_ractor_move_courier *rb_ractor_move_courier_build(VALUE obj, struct rb_ractor_move_courier **slot);
|
||||
VALUE rb_ractor_move_courier_materialize(struct rb_ractor_move_courier *c);
|
||||
void rb_ractor_move_courier_free(struct rb_ractor_move_courier *c);
|
||||
// The off-heap courier a copy or a move payload travels in. Defined in ractor.c.
|
||||
struct rb_ractor_courier *rb_ractor_courier_build_move(VALUE obj, struct rb_ractor_courier **slot);
|
||||
VALUE rb_ractor_courier_materialize(struct rb_ractor_courier *c);
|
||||
void rb_ractor_courier_free(struct rb_ractor_courier *c);
|
||||
static void ractor_off_queue_add(rb_ractor_t *cr, struct ractor_basket *b);
|
||||
static void ractor_off_queue_remove(struct ractor_basket *b);
|
||||
void rb_ractor_move_courier_mark(struct rb_ractor_move_courier *c);
|
||||
struct rb_ractor_move_courier *rb_ractor_copy_courier_build(VALUE obj, struct rb_ractor_move_courier **slot);
|
||||
void rb_ractor_courier_mark(struct rb_ractor_courier *c);
|
||||
struct rb_ractor_courier *rb_ractor_courier_build_copy(VALUE obj, struct rb_ractor_courier **slot);
|
||||
|
||||
static void
|
||||
ractor_port_mark(void *ptr)
|
||||
@@ -233,10 +233,10 @@ struct ractor_basket {
|
||||
* Marshal byte String. The receiver rebuilds it with Marshal.load instead
|
||||
* of walking it natively. */
|
||||
bool marshaled;
|
||||
/* The off-heap (xmalloc) courier of a basket_type_move. A move basket does
|
||||
* not use v. */
|
||||
struct rb_ractor_move_courier *move_courier;
|
||||
/* The marshaled bytes of a copy payload, off-heap like a move courier. When
|
||||
/* The off-heap (xmalloc) courier the payload graph was serialized into.
|
||||
* Copy and move both use it; when set, v is unused. */
|
||||
struct rb_ractor_courier *courier;
|
||||
/* The marshaled bytes of a copy payload, off-heap like the courier. When
|
||||
* set, v is unused: an in-flight payload that is not a GC object needs no
|
||||
* in-flight pin, so it never keeps a page of the sender's heap alive. */
|
||||
char *mbuf;
|
||||
@@ -264,12 +264,12 @@ ractor_basket_none_p(const struct ractor_basket *b)
|
||||
static void
|
||||
ractor_basket_mark(const struct ractor_basket *b)
|
||||
{
|
||||
if (b->p.move_courier != NULL) {
|
||||
if (b->p.courier != NULL) {
|
||||
/* The payload became this Ractor's to root the moment the message was enqueued
|
||||
* here: the sender's own roots stop at the send. Before and after the queue the
|
||||
* basket is on its holder's off_queue_baskets instead, so a courier is rooted
|
||||
* from the moment it is allocated to the moment it is freed. */
|
||||
rb_ractor_move_courier_mark(b->p.move_courier);
|
||||
rb_ractor_courier_mark(b->p.courier);
|
||||
}
|
||||
else if (b->p.mbuf == NULL) {
|
||||
/* Marshaled bytes are off-heap and hold nothing to mark. */
|
||||
@@ -284,10 +284,10 @@ ractor_basket_free(struct ractor_basket *b)
|
||||
ruby_xfree(b->p.mbuf);
|
||||
b->p.mbuf = NULL;
|
||||
b->p.mlen = 0;
|
||||
if (b->p.move_courier) {
|
||||
/* A move courier that was never consumed (a queue being torn down, say). */
|
||||
rb_ractor_move_courier_free(b->p.move_courier);
|
||||
b->p.move_courier = NULL;
|
||||
if (b->p.courier) {
|
||||
/* A courier that was never consumed (a queue being torn down, say). */
|
||||
rb_ractor_courier_free(b->p.courier);
|
||||
b->p.courier = NULL;
|
||||
}
|
||||
SIZED_FREE(b);
|
||||
}
|
||||
@@ -305,7 +305,7 @@ ractor_basket_alloc(void)
|
||||
b->p.v = Qnil;
|
||||
b->p.exception = false;
|
||||
b->p.marshaled = false;
|
||||
b->p.move_courier = NULL;
|
||||
b->p.courier = NULL;
|
||||
b->p.mbuf = NULL;
|
||||
b->p.mlen = 0;
|
||||
ccan_list_node_init(&b->off_queue_node);
|
||||
@@ -1013,7 +1013,7 @@ ractor_marshal_dump_rescue(VALUE obj, VALUE errinfo)
|
||||
|
||||
static VALUE
|
||||
ractor_prepare_payload(rb_execution_context_t *ec, VALUE obj, enum ractor_basket_type *ptype, bool *pmarshaled,
|
||||
struct rb_ractor_move_courier **pcourier)
|
||||
struct rb_ractor_courier **pcourier)
|
||||
{
|
||||
switch (*ptype) {
|
||||
case basket_type_ref:
|
||||
@@ -1031,7 +1031,7 @@ ractor_prepare_payload(rb_execution_context_t *ec, VALUE obj, enum ractor_basket
|
||||
* types; anything else is marshaled here, so its user hooks run on the
|
||||
* sender, and the dump travels as plain bytes. */
|
||||
*ptype = basket_type_copy;
|
||||
if (rb_ractor_copy_courier_build(obj, pcourier) != NULL) return Qundef;
|
||||
if (rb_ractor_courier_build_copy(obj, pcourier) != NULL) return Qundef;
|
||||
|
||||
*pmarshaled = true;
|
||||
return rb_rescue2(ractor_marshal_dump_body, obj,
|
||||
@@ -1064,10 +1064,10 @@ ractor_basket_new(rb_execution_context_t *ec, VALUE obj, enum ractor_basket_type
|
||||
* RactorMovedObject. While in flight there is no GC object left for the
|
||||
* sender's GC to mark, sweep or move. The build publishes the courier into
|
||||
* the basket as soon as it exists. */
|
||||
rb_ractor_move_courier_build(obj, &b->p.move_courier);
|
||||
rb_ractor_courier_build_move(obj, &b->p.courier);
|
||||
}
|
||||
else {
|
||||
v = ractor_prepare_payload(ec, obj, &type, &marshaled, &b->p.move_courier);
|
||||
v = ractor_prepare_payload(ec, obj, &type, &marshaled, &b->p.courier);
|
||||
if (type == basket_type_copy && marshaled) {
|
||||
/* Take the dump off-heap: the sender's copy of it is ordinary garbage
|
||||
* from here, so nothing of its heap is held while the message waits. */
|
||||
@@ -1103,7 +1103,7 @@ ractor_basket_value(struct ractor_basket *b)
|
||||
case basket_type_copy: {
|
||||
/* An off-heap copy courier rebuilds exactly like a move one; only the sources
|
||||
* differ (still alive here, already shells there). */
|
||||
if (b->p.move_courier != NULL) goto materialize_courier;
|
||||
if (b->p.courier != NULL) goto materialize_courier;
|
||||
/* The payload is the marshaled bytes. Marshal.load allocates through this
|
||||
* Ractor's normal newobj and write-barrier paths, and can raise (load hooks and
|
||||
* autoload run user code, an async interrupt can arrive anywhere), so it runs
|
||||
@@ -1151,7 +1151,7 @@ ractor_basket_value(struct ractor_basket *b)
|
||||
* #hash runs user code, and an async interrupt can arrive). On a raise the
|
||||
* courier is still owned by the basket, whose teardown frees it. */
|
||||
rb_execution_context_t *ec = rb_current_ec_noinline();
|
||||
struct rb_ractor_move_courier *courier = b->p.move_courier;
|
||||
struct rb_ractor_courier *courier = b->p.courier;
|
||||
/* Keep the materialized graph on the machine stack (result): it is the only
|
||||
* root until it reaches the caller. courier_free below runs a long loop, and
|
||||
* only the malloc'd basket's p.v holding it would give a concurrent global GC a
|
||||
@@ -1160,16 +1160,16 @@ ractor_basket_value(struct ractor_basket *b)
|
||||
enum ruby_tag_type state;
|
||||
EC_PUSH_TAG(ec);
|
||||
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
|
||||
result = rb_ractor_move_courier_materialize(courier);
|
||||
result = rb_ractor_courier_materialize(courier);
|
||||
}
|
||||
EC_POP_TAG();
|
||||
if (state != TAG_NONE) {
|
||||
/* An unconsumed courier stays in b->p.move_courier; basket_free frees it. */
|
||||
/* An unconsumed courier stays in b->p.courier; basket_free frees it. */
|
||||
ractor_basket_free(b);
|
||||
EC_JUMP_TAG(ec, state);
|
||||
}
|
||||
rb_ractor_move_courier_free(courier);
|
||||
b->p.move_courier = NULL;
|
||||
rb_ractor_courier_free(courier);
|
||||
b->p.courier = NULL;
|
||||
b->p.v = result;
|
||||
RB_GC_GUARD(result);
|
||||
break;
|
||||
@@ -1563,7 +1563,7 @@ ractor_basket_new_ref(VALUE shareable)
|
||||
b->p.v = shareable;
|
||||
b->p.exception = false;
|
||||
b->p.marshaled = false;
|
||||
b->p.move_courier = NULL;
|
||||
b->p.courier = NULL;
|
||||
b->p.mbuf = NULL;
|
||||
b->p.mlen = 0;
|
||||
|
||||
|
||||
@@ -1084,7 +1084,7 @@ match_set_regs(VALUE match, int num_regs, const OnigPosition *beg, const OnigPos
|
||||
* registers are written out to an onig-independent blob so the original malloc'd area can be
|
||||
* freed, leaving an empty shell behind, and rebuilt from the blob on the receiving side. */
|
||||
void *
|
||||
rb_match_move_dump(VALUE match, VALUE *regexp_out, VALUE *str_out, int *num_regs_out, bool release_source)
|
||||
rb_match_blob_dump(VALUE match, VALUE *regexp_out, VALUE *str_out, int *num_regs_out, bool release_source)
|
||||
{
|
||||
struct RMatch *rm = RMATCH(match);
|
||||
int n = rm->num_regs;
|
||||
@@ -1117,13 +1117,13 @@ rb_match_move_dump(VALUE match, VALUE *regexp_out, VALUE *str_out, int *num_regs
|
||||
}
|
||||
|
||||
VALUE
|
||||
rb_match_move_alloc(VALUE klass, int num_regs)
|
||||
rb_match_blob_alloc(VALUE klass, int num_regs)
|
||||
{
|
||||
return match_alloc_n(klass, num_regs);
|
||||
}
|
||||
|
||||
void
|
||||
rb_match_move_load(VALUE match, VALUE regexp, VALUE str, int num_regs, const void *blob_)
|
||||
rb_match_blob_load(VALUE match, VALUE regexp, VALUE str, int num_regs, const void *blob_)
|
||||
{
|
||||
const OnigPosition *blob = blob_;
|
||||
struct RMatch *rm = RMATCH(match);
|
||||
@@ -1142,7 +1142,7 @@ rb_match_move_load(VALUE match, VALUE regexp, VALUE str, int num_regs, const voi
|
||||
}
|
||||
|
||||
void
|
||||
rb_match_move_free(void *blob)
|
||||
rb_match_blob_free(void *blob)
|
||||
{
|
||||
ruby_xfree(blob);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user