farce 0.5.0-x86-linux-gnu → 0.6.0-x86-linux-gnu
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/docs/modes.md +1 -1
- data/docs/utilities.md +1 -0
- data/ext/farce/atom.c +23 -7
- data/ext/farce/containers.h +3 -0
- data/ext/farce/counter.c +4 -6
- data/ext/farce/depend +3 -1
- data/ext/farce/extconf.rb +19 -14
- data/ext/farce/farce.c +101 -3
- data/ext/farce/map.c +1 -4
- data/ext/farce/trie.c +140 -72
- data/ext/farce/trie_search.h +114 -0
- data/ext/farce/vector.c +50 -18
- data/ext/farce/weak_map.c +4 -8
- data/ext/farce/weak_references.h +23 -0
- data/ext/java/org/farce/FiberScheduler.java +8 -6
- data/lib/farce/abstract/atom.rb +5 -1
- data/lib/farce/abstract/counter.rb +5 -4
- data/lib/farce/abstract/molecule.rb +14 -14
- data/lib/farce/abstract/vector.rb +1 -1
- data/lib/farce/atom.rb +6 -0
- data/lib/farce/clock.rb +8 -7
- data/lib/farce/counter.rb +6 -0
- data/lib/farce/deduper.rb +12 -4
- data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
- data/lib/farce/engine/jvm/counter.rb +0 -2
- data/lib/farce/engine/ruby/3.4/farce.so +0 -0
- data/lib/farce/engine/ruby/4.0/farce.so +0 -0
- data/lib/farce/engine/ruby/shared/weak_cache_mirror.rb +56 -0
- data/lib/farce/engine/ruby.rb +5 -1
- data/lib/farce/engine/shared/trie/ignore_case.rb +136 -0
- data/lib/farce/engine/shared/trie.rb +14 -1
- data/lib/farce/engine/shared/trie_builder.rb +19 -8
- data/lib/farce/engine/shared/unshared_vector.rb +3 -1
- data/lib/farce/engine/shared/vector.rb +2 -0
- data/lib/farce/engine/shared.rb +2 -0
- data/lib/farce/engine/truffleruby/native/counter.rb +0 -2
- data/lib/farce/engine/truffleruby.rb +9 -0
- data/lib/farce/extension.rb +5 -3
- data/lib/farce/integrations/sorted_set.rb +1 -0
- data/lib/farce/internal/fake_ractor.rb +1 -1
- data/lib/farce/internal/freeze.rb +1 -5
- data/lib/farce/internal/map_value_modes.rb +8 -7
- data/lib/farce/internal.rb +11 -0
- data/lib/farce/local/counter.rb +4 -10
- data/lib/farce/map.rb +8 -1
- data/lib/farce/memoize.rb +10 -5
- data/lib/farce/shareable.rb +3 -0
- data/lib/farce/strict/weak_cache.rb +199 -0
- data/lib/farce/vector.rb +8 -7
- data/lib/farce/version.rb +1 -1
- data/lib/farce/walker/definitions.rb +18 -0
- data/lib/farce/walker/modification.rb +28 -14
- data/lib/farce/walker.rb +9 -1
- data/lib/farce/weak_key_map.rb +1 -1
- data/lib/farce/weak_map.rb +1 -1
- data/lib/farce/weak_value_map.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2af9b4b07ee5b0b788bccde9ad322417bad8b4de88700937135a0bb0e9a9bfe
|
|
4
|
+
data.tar.gz: d97b934939b47e43543f7e134c81b0de85bdf50cd9cdfa387f479150a768514f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7d98de686691f60681d1ae49f77adfb88a361e0e6ffa5225035fe326dc6092b8c16d600476c938b265479c3f31ea3305b554989e20aad36c29d6c1a8e65d94c
|
|
7
|
+
data.tar.gz: b0c0d0ee6feb2f52878526c57bfd94e7cce5b461e05782c5c02ab6766b50a5a004bec31a38f3480e5a69d0ab575637ed76791005425e9bfec72ba0192a6f96d2
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,7 @@ Farce follows [Semantic Versioning 2.0](http://semver.org/). Any public method o
|
|
|
6
6
|
|
|
7
7
|
Version | Date | Important Changes
|
|
8
8
|
--------------------------------|------------|--------------------------------------
|
|
9
|
+
[0.6.0](#farce-060-2026-11-04) | 2026-11-04 | Performance improvements, better counters, and `WeakCache`
|
|
9
10
|
[0.5.0](#farce-050-2026-10-21) | 2026-10-21 | Publishers, memoization, pool and queue resizing, and better concurrent-ruby compatibility
|
|
10
11
|
[0.4.1](#farce-041-2026-10-18) | 2026-10-18 | Include improved io_uring support in prebuilt Linux binaries
|
|
11
12
|
[0.4.0](#farce-040-2026-10-16) | 2026-10-16 | Introduce `Farce::Backports`
|
|
@@ -15,6 +16,23 @@ Farce follows [Semantic Versioning 2.0](http://semver.org/). Any public method o
|
|
|
15
16
|
|
|
16
17
|
## Release History
|
|
17
18
|
|
|
19
|
+
### Farce 0.6.0 (2026-11-04)
|
|
20
|
+
|
|
21
|
+
#### Breaking Changes
|
|
22
|
+
|
|
23
|
+
* `Counter` and `Local::Counter` arithmetic methods (`increment`, `add`, `decrement`, `subtract`, and `remove`) now return the integer produced by the atomic update instead of the counter. Use `number = counter.increment` to allocate sequence numbers without a separate read. Calls can no longer be chained.
|
|
24
|
+
|
|
25
|
+
#### New Features
|
|
26
|
+
|
|
27
|
+
* `Farce.dedup` can now deduplicate `Regexp`, `Range`, `Data`, and `SortedSet` instances.
|
|
28
|
+
* `Farce::Walker` (and thus `Farce.freeze_graph`) correctly walk `Struct` and `Range` instances now.
|
|
29
|
+
* Add `Farce::Strict::WeakCache`. It offers a subset of the API of `Farce::Strict::WeakValueMap`, but without its performance penalty on CRuby 3.x and 4.0 (on other Rubies, including CRuby 4.1, it's just a tiny wrapper around the well-performing `WeakValueMap`).
|
|
30
|
+
|
|
31
|
+
#### Performance improvements
|
|
32
|
+
|
|
33
|
+
* Faster construction of Map and Vector instances.
|
|
34
|
+
* Speed up atoms and memoization.
|
|
35
|
+
|
|
18
36
|
### Farce 0.5.0 (2026-10-21)
|
|
19
37
|
|
|
20
38
|
#### New Features
|
data/docs/modes.md
CHANGED
|
@@ -390,7 +390,7 @@ The same choices appear in several Farce APIs. Containers normally default to `:
|
|
|
390
390
|
| `Farce::Exchanger` | `new`, `exchange` | The value offered to a partner. |
|
|
391
391
|
| `Farce::WeakAtom` | `new`, `store`, `update`, and other replacement operations | Weakly held values. Supports only `:raise`, `:make_shareable`, and `:dedup`. Defaults to `:raise`. |
|
|
392
392
|
| `Farce::Atom` | `new`, `store`, `swap`, `update`, and other replacement operations | The stored value. |
|
|
393
|
-
| `Farce::Memoize` | `memoize`, `[]` | Memoized results with `scope: :global` (the default).
|
|
393
|
+
| `Farce::Memoize` | `memoize`, `[]` | Memoized results with `scope: :global` (the default). `:strict` caches shareable results directly, including with `weak: true`. Weak memoization also accepts `:raise` (the default), `:make_shareable`, and `:dedup`. Other scopes ignore the mode. |
|
|
394
394
|
| `Farce::Lazy`, `Farce::LazyRef` | `new` | The result computed once by the factory. |
|
|
395
395
|
| `Farce::Task` | `new`, `schedule`, `try_schedule`, `in_parallel`, `on_main`, `add_argument` | Arguments and the successful result. `add_argument` overrides the mode for one argument. Scheduling can select local mode automatically. |
|
|
396
396
|
| `Farce::Publisher`, `Farce::Unshared::Publisher`, `Farce::Local::Publisher` | `subscribe` with a block | Callback arguments and queued payloads. Defaults to `:smart` for `Publisher` and `:local` for its Unshared and Local variants. |
|
data/docs/utilities.md
CHANGED
|
@@ -59,6 +59,7 @@ Other classes Farce provides include:
|
|
|
59
59
|
* `ThreadExecutor`: A [task executor](scheduling.md#scheduling-code) creating a new thread for each unit of work. Used on TruffleRuby.
|
|
60
60
|
* `Walker`: A tool for walking a Ruby object tree.
|
|
61
61
|
* `WeakValue`: A value object version of [`WeakRef`](shims.md#weakref). Allows handling references more explicitly, without automatic method delegation.
|
|
62
|
+
* `Strict::WeakCache`: Ractor-shared weak maps are slow on CRuby before 4.1. `WeakCache` provides a faster alternative if entries aren't frequently garbage collected, but it does not allow deleting or replacing entries manually.
|
|
62
63
|
|
|
63
64
|
In addition, Farce includes a range of error classes not listed here. Check the [API documentation](https://rkh.github.io/farce/) or [code base](https://github.com/rkh/farce/blob/main/lib/farce/error.rb) for these.
|
|
64
65
|
|
data/ext/farce/atom.c
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "containers.h"
|
|
2
2
|
#include "native_wait.h"
|
|
3
3
|
#include "transaction.h"
|
|
4
|
+
#include "weak_references.h"
|
|
4
5
|
#include "ruby/fiber/scheduler.h"
|
|
5
6
|
|
|
6
7
|
#include <errno.h>
|
|
@@ -12,11 +13,6 @@ static VALUE cAtom;
|
|
|
12
13
|
#ifdef RC_HAVE_NATIVE_WEAK_REFERENCES
|
|
13
14
|
static VALUE cWeakAtom;
|
|
14
15
|
static VALUE cUnsharedWeakAtom;
|
|
15
|
-
|
|
16
|
-
/* These functions are exported by Rubies with the probed typed-data weak
|
|
17
|
-
* reference callback, but their declarations intentionally remain internal. */
|
|
18
|
-
void rb_gc_declare_weak_references(VALUE object);
|
|
19
|
-
bool rb_gc_handle_weak_references_alive_p(VALUE object);
|
|
20
16
|
#endif
|
|
21
17
|
|
|
22
18
|
typedef struct atom_waiter atom_waiter_t;
|
|
@@ -149,11 +145,11 @@ atom_compact(void *pointer)
|
|
|
149
145
|
/* The VM invokes this after marking while mutators are stopped. Taking the atom
|
|
150
146
|
* mutex here could deadlock if GC stopped a mutator while it owned the mutex. */
|
|
151
147
|
static void
|
|
152
|
-
atom_handle_weak_references
|
|
148
|
+
FARCE_WEAK_REFERENCES_CALLBACK(atom_handle_weak_references)
|
|
153
149
|
{
|
|
154
150
|
atom_t *atom = pointer;
|
|
155
151
|
if (!atom->weak || NIL_P(atom->value)) return;
|
|
156
|
-
if (
|
|
152
|
+
if (FARCE_WEAK_REFERENCE_ALIVE_P(atom->value)) return;
|
|
157
153
|
|
|
158
154
|
atom->value = Qnil;
|
|
159
155
|
atom_changed(atom);
|
|
@@ -500,6 +496,15 @@ static VALUE
|
|
|
500
496
|
atom_get(int argc, VALUE *argv, VALUE self)
|
|
501
497
|
{
|
|
502
498
|
atom_t *atom = get_atom(self);
|
|
499
|
+
if (argc == 0) {
|
|
500
|
+
pthread_mutex_lock(&atom->lock);
|
|
501
|
+
if (!atom->updating) {
|
|
502
|
+
VALUE value = atom->value;
|
|
503
|
+
pthread_mutex_unlock(&atom->lock);
|
|
504
|
+
return value;
|
|
505
|
+
}
|
|
506
|
+
pthread_mutex_unlock(&atom->lock);
|
|
507
|
+
}
|
|
503
508
|
atom_timeout_t timeout = atom_parse_timeout(atom_extract_timeout(argc, argv, "0:", NULL));
|
|
504
509
|
atom_execution_context_t execution = atom_current_execution_context();
|
|
505
510
|
|
|
@@ -607,6 +612,17 @@ static VALUE
|
|
|
607
612
|
atom_store_if_absent(int argc, VALUE *argv, VALUE self)
|
|
608
613
|
{
|
|
609
614
|
atom_t *atom = get_atom(self);
|
|
615
|
+
if (argc == 0) {
|
|
616
|
+
rb_check_frozen(self);
|
|
617
|
+
rb_need_block();
|
|
618
|
+
pthread_mutex_lock(&atom->lock);
|
|
619
|
+
if (!atom->updating && !NIL_P(atom->value)) {
|
|
620
|
+
VALUE value = atom->value;
|
|
621
|
+
pthread_mutex_unlock(&atom->lock);
|
|
622
|
+
return value;
|
|
623
|
+
}
|
|
624
|
+
pthread_mutex_unlock(&atom->lock);
|
|
625
|
+
}
|
|
610
626
|
atom_operation_t operation = {
|
|
611
627
|
.self = self,
|
|
612
628
|
.atom = atom,
|
data/ext/farce/containers.h
CHANGED
|
@@ -36,6 +36,9 @@ containers_check_typed_frozen(VALUE self)
|
|
|
36
36
|
|
|
37
37
|
void containers_check_shareable(VALUE value);
|
|
38
38
|
bool containers_strict_bool(VALUE value, const char *name);
|
|
39
|
+
VALUE containers_parse_constructor(
|
|
40
|
+
int argc, const VALUE *argv, const ID *keyword_ids, int keyword_count, VALUE *keyword_values
|
|
41
|
+
);
|
|
39
42
|
typedef void (*containers_native_reference_validator_t)(VALUE object);
|
|
40
43
|
VALUE containers_publish_native_reference_free(VALUE self);
|
|
41
44
|
VALUE containers_publish_native_with_references(
|
data/ext/farce/counter.c
CHANGED
|
@@ -338,8 +338,8 @@ counter_compare_and_set(VALUE self, VALUE expected_input, VALUE replacement_inpu
|
|
|
338
338
|
return exchanged ? Qtrue : Qfalse;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
/*
|
|
342
|
-
* add/subtract
|
|
341
|
+
/* Farce::Counter inherits these coercing operations directly. Internal
|
|
342
|
+
* add/subtract callers require strict Integer arguments. */
|
|
343
343
|
static VALUE
|
|
344
344
|
counter_increment(int argc, VALUE *argv, VALUE self)
|
|
345
345
|
{
|
|
@@ -352,8 +352,7 @@ counter_increment(int argc, VALUE *argv, VALUE self)
|
|
|
352
352
|
rb_check_frozen(self);
|
|
353
353
|
}
|
|
354
354
|
long long delta = NUM2LL(input);
|
|
355
|
-
|
|
356
|
-
return self;
|
|
355
|
+
return counter_change(counter, delta, false);
|
|
357
356
|
}
|
|
358
357
|
|
|
359
358
|
static VALUE
|
|
@@ -368,8 +367,7 @@ counter_decrement(int argc, VALUE *argv, VALUE self)
|
|
|
368
367
|
rb_check_frozen(self);
|
|
369
368
|
}
|
|
370
369
|
long long delta = NUM2LL(input);
|
|
371
|
-
|
|
372
|
-
return self;
|
|
370
|
+
return counter_change(counter, delta, true);
|
|
373
371
|
}
|
|
374
372
|
|
|
375
373
|
void
|
data/ext/farce/depend
CHANGED
|
@@ -7,11 +7,13 @@ reactor.o: reactor.c reactor.h futex.h
|
|
|
7
7
|
drivers.o: drivers.c reactor.h futex.h
|
|
8
8
|
io.o: io.c reactor.h futex.h
|
|
9
9
|
tree_map.o: tree_map.c containers.h native_wait.h futex.h
|
|
10
|
+
trie.o: trie.c containers.h trie_search.h
|
|
10
11
|
|
|
11
12
|
queue.o: queue.c containers.h unshared_wait.h
|
|
12
13
|
unshared_signal.o: unshared_signal.c containers.h unshared_wait.h unshared_io_pool.h
|
|
13
14
|
|
|
14
|
-
atom.o: atom.c containers.h transaction.h native_wait.h futex.h
|
|
15
|
+
atom.o: atom.c containers.h transaction.h native_wait.h futex.h weak_references.h
|
|
16
|
+
weak_map.o: weak_map.c containers.h weak_references.h
|
|
15
17
|
map.o: map.c containers.h transaction.h native_wait.h futex.h
|
|
16
18
|
vector.o: vector.c containers.h transaction.h native_wait.h futex.h
|
|
17
19
|
signal.o: signal.c containers.h native_wait.h futex.h
|
data/ext/farce/extconf.rb
CHANGED
|
@@ -103,19 +103,21 @@ if epoll || kqueue
|
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
106
|
+
# The internal callback gained an object-space argument during Ruby 4.1
|
|
107
|
+
# development. Check its exact type, since incompatible assignments may only warn.
|
|
108
|
+
weak_callback_arguments = ["void *, void *", "void *"].find do |arguments|
|
|
109
|
+
try_compile(<<~SOURCE)
|
|
110
|
+
#include "ruby.h"
|
|
111
|
+
|
|
112
|
+
_Static_assert(
|
|
113
|
+
_Generic(((rb_data_type_t *)0)->function.handle_weak_references,
|
|
114
|
+
void (*)(#{arguments}): 1, default: 0),
|
|
115
|
+
"unsupported weak-reference callback"
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
int main(void) { return 0; }
|
|
119
|
+
SOURCE
|
|
120
|
+
end
|
|
119
121
|
|
|
120
122
|
weak_declare = have_func("rb_gc_declare_weak_references")
|
|
121
123
|
weak_alive = have_func("rb_gc_handle_weak_references_alive_p")
|
|
@@ -123,7 +125,10 @@ weak_alive = have_func("rb_gc_handle_weak_references_alive_p")
|
|
|
123
125
|
# Kazlib deliberately puts full-tree integrity walks behind assert(3).
|
|
124
126
|
# Enable them only while debugging the priority queue.
|
|
125
127
|
$defs << "-DNDEBUG" unless ENV["FARCE_PRIORITY_QUEUE_ASSERTIONS"] == "1"
|
|
126
|
-
|
|
128
|
+
if weak_callback_arguments && weak_declare && weak_alive
|
|
129
|
+
$defs << "-DRC_HAVE_NATIVE_WEAK_REFERENCES=1"
|
|
130
|
+
$defs << "-DFARCE_WEAK_REFERENCES_HAVE_OBJSPACE=1" if weak_callback_arguments == "void *, void *"
|
|
131
|
+
end
|
|
127
132
|
$defs << "-DFARCE_TRIE_FAILURE_INJECTION" if ENV["FARCE_TRIE_FAILURE_INJECTION"] == "1"
|
|
128
133
|
$CFLAGS = "#{$CFLAGS} -std=c11 -fvisibility=hidden -Wall -Wextra -Wno-unused-parameter -Wno-unused-function"
|
|
129
134
|
$LDFLAGS = "#{$LDFLAGS} -pthread" unless /mswin|mingw/ =~ RUBY_PLATFORM
|
data/ext/farce/farce.c
CHANGED
|
@@ -43,6 +43,40 @@ containers_strict_bool(VALUE value, const char *name)
|
|
|
43
43
|
rb_raise(rb_eArgError, "%s must be true or false", name);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
VALUE
|
|
47
|
+
containers_parse_constructor(
|
|
48
|
+
int argc, const VALUE *argv, const ID *keyword_ids, int keyword_count, VALUE *keyword_values
|
|
49
|
+
)
|
|
50
|
+
{
|
|
51
|
+
VALUE keywords = Qnil;
|
|
52
|
+
int positional_count = argc;
|
|
53
|
+
if (argc > 0 && rb_keyword_given_p()) keywords = argv[--positional_count];
|
|
54
|
+
rb_check_arity(positional_count, 0, 1);
|
|
55
|
+
|
|
56
|
+
int found = 0;
|
|
57
|
+
for (int index = 0; index < keyword_count; index++) {
|
|
58
|
+
VALUE value = NIL_P(keywords)
|
|
59
|
+
? Qundef
|
|
60
|
+
: rb_hash_lookup2(keywords, ID2SYM(keyword_ids[index]), Qundef);
|
|
61
|
+
keyword_values[index] = value;
|
|
62
|
+
if (value != Qundef) found++;
|
|
63
|
+
}
|
|
64
|
+
if (!NIL_P(keywords) && RHASH_SIZE(keywords) != (size_t)found) {
|
|
65
|
+
/* Ruby's unknown-keyword error path deletes recognized keys, even
|
|
66
|
+
* when no output values are requested. Copy only for that error. */
|
|
67
|
+
rb_get_kwargs(rb_hash_dup(keywords), keyword_ids, 0, keyword_count, keyword_values);
|
|
68
|
+
}
|
|
69
|
+
return positional_count ? argv[0] : Qnil;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static int
|
|
73
|
+
containers_find_public_ivar(ID name, VALUE value, st_data_t found)
|
|
74
|
+
{
|
|
75
|
+
if (!rb_is_instance_id(name)) return ST_CONTINUE;
|
|
76
|
+
*(bool *)found = true;
|
|
77
|
+
return ST_STOP;
|
|
78
|
+
}
|
|
79
|
+
|
|
46
80
|
static void
|
|
47
81
|
containers_check_native_publication_target(VALUE self)
|
|
48
82
|
{
|
|
@@ -53,15 +87,15 @@ containers_check_native_publication_target(VALUE self)
|
|
|
53
87
|
rb_raise(rb_eTypeError, "native publication requires a shareable typed-data descriptor");
|
|
54
88
|
}
|
|
55
89
|
|
|
56
|
-
|
|
57
|
-
|
|
90
|
+
bool has_public_ivar = false;
|
|
91
|
+
rb_ivar_foreach(self, containers_find_public_ivar, (st_data_t)&has_public_ivar);
|
|
92
|
+
if (has_public_ivar) {
|
|
58
93
|
rb_raise(
|
|
59
94
|
rb_eTypeError,
|
|
60
95
|
"%s cannot be published with Ruby instance variables",
|
|
61
96
|
rb_obj_classname(self)
|
|
62
97
|
);
|
|
63
98
|
}
|
|
64
|
-
RB_GC_GUARD(instance_variables);
|
|
65
99
|
}
|
|
66
100
|
|
|
67
101
|
VALUE
|
|
@@ -327,6 +361,63 @@ farce_load_native_fiber_scheduler(VALUE namespace)
|
|
|
327
361
|
return Qnil;
|
|
328
362
|
}
|
|
329
363
|
|
|
364
|
+
#if RUBY_API_VERSION_CODE >= 40100
|
|
365
|
+
static rb_alloc_func_t farce_object_allocator;
|
|
366
|
+
static VALUE farce_shareable_inclusion;
|
|
367
|
+
|
|
368
|
+
static VALUE
|
|
369
|
+
farce_new_shareable(int argc, VALUE *argv, VALUE klass)
|
|
370
|
+
{
|
|
371
|
+
/* Ordinary ivar writes then include the hidden ID in the class's size
|
|
372
|
+
* hint, keeping later instances embedded instead of spilling at publication.
|
|
373
|
+
* Leave the allocator unchanged: Ruby's copy path requires a root shape. */
|
|
374
|
+
VALUE object = rb_obj_alloc(klass);
|
|
375
|
+
if (RB_TYPE_P(object, T_OBJECT)) rb_obj_id(object);
|
|
376
|
+
rb_obj_call_init_kw(object, argc, argv, RB_PASS_CALLED_KEYWORDS);
|
|
377
|
+
return object;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
static VALUE
|
|
381
|
+
farce_prepare_shareable_class(VALUE namespace, VALUE base)
|
|
382
|
+
{
|
|
383
|
+
if (RB_TYPE_P(base, T_CLASS) && !RB_FL_TEST_RAW(base, RUBY_FL_SINGLETON)) {
|
|
384
|
+
ID id_new = rb_intern("new");
|
|
385
|
+
if (rb_get_alloc_func(base) == farce_object_allocator &&
|
|
386
|
+
rb_method_basic_definition_p(rb_singleton_class(base), id_new)) {
|
|
387
|
+
VALUE constructor = rb_obj_method(base, ID2SYM(id_new));
|
|
388
|
+
if (rb_funcall(constructor, rb_intern("owner"), 0) == rb_cClass) {
|
|
389
|
+
rb_define_singleton_method(base, "new", farce_new_shareable, -1);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (RB_TYPE_P(base, T_MODULE)) {
|
|
394
|
+
rb_prepend_module(rb_singleton_class(base), farce_shareable_inclusion);
|
|
395
|
+
}
|
|
396
|
+
return base;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
static VALUE
|
|
400
|
+
farce_shareable_included(VALUE self, VALUE base)
|
|
401
|
+
{
|
|
402
|
+
VALUE result = rb_call_super(1, &base);
|
|
403
|
+
farce_prepare_shareable_class(Qnil, base);
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
#endif
|
|
407
|
+
|
|
408
|
+
static VALUE
|
|
409
|
+
farce_publish_shareable(VALUE namespace, VALUE object)
|
|
410
|
+
{
|
|
411
|
+
#if RUBY_API_VERSION_CODE >= 40100
|
|
412
|
+
/* A frozen facade can bypass Ruby's object-ID preallocation during
|
|
413
|
+
* make_shareable. Reserve the ID before freezing so a later object_id
|
|
414
|
+
* cannot resize shared ivar storage while another Ractor reads it. */
|
|
415
|
+
rb_obj_id(object);
|
|
416
|
+
#endif
|
|
417
|
+
rb_obj_freeze(object);
|
|
418
|
+
return rb_ractor_make_shareable(object);
|
|
419
|
+
}
|
|
420
|
+
|
|
330
421
|
#if RUBY_API_VERSION_CODE == 30400
|
|
331
422
|
static VALUE
|
|
332
423
|
farce_lock_native_thread(VALUE namespace)
|
|
@@ -345,6 +436,13 @@ Init_farce(void)
|
|
|
345
436
|
VALUE singleton = rb_singleton_class(mInternal);
|
|
346
437
|
eIsolationError = rb_const_get(rb_cRactor, rb_intern("IsolationError"));
|
|
347
438
|
rb_define_singleton_method(mInternal, "value_changed?", farce_value_changed, 3);
|
|
439
|
+
#if RUBY_API_VERSION_CODE >= 40100
|
|
440
|
+
farce_object_allocator = rb_get_alloc_func(rb_cObject);
|
|
441
|
+
farce_shareable_inclusion = rb_define_module_under(mInternal, "ShareableConstruction");
|
|
442
|
+
rb_define_method(farce_shareable_inclusion, "included", farce_shareable_included, 1);
|
|
443
|
+
rb_define_singleton_method(mInternal, "prepare_shareable_class", farce_prepare_shareable_class, 1);
|
|
444
|
+
#endif
|
|
445
|
+
rb_define_singleton_method(mInternal, "publish_shareable", farce_publish_shareable, 1);
|
|
348
446
|
#if RUBY_API_VERSION_CODE == 30400
|
|
349
447
|
rb_define_singleton_method(mInternal, "lock_native_thread", farce_lock_native_thread, 0);
|
|
350
448
|
#endif
|
data/ext/farce/map.c
CHANGED
|
@@ -850,8 +850,6 @@ map_initialize_entry(VALUE key, VALUE value, VALUE opaque)
|
|
|
850
850
|
static VALUE
|
|
851
851
|
map_initialize(int argc, VALUE *argv, VALUE self)
|
|
852
852
|
{
|
|
853
|
-
VALUE mapping = Qnil;
|
|
854
|
-
VALUE keywords = Qnil;
|
|
855
853
|
ID keyword_ids[] = {
|
|
856
854
|
rb_intern("compare_by_identity"),
|
|
857
855
|
rb_intern("compare_keys_by_identity"),
|
|
@@ -860,8 +858,7 @@ map_initialize(int argc, VALUE *argv, VALUE self)
|
|
|
860
858
|
VALUE keyword_values[3] = {Qundef, Qundef, Qundef};
|
|
861
859
|
map_t *map;
|
|
862
860
|
|
|
863
|
-
|
|
864
|
-
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 3, keyword_values);
|
|
861
|
+
VALUE mapping = containers_parse_constructor(argc, argv, keyword_ids, 3, keyword_values);
|
|
865
862
|
TypedData_Get_Struct(self, map_t, &map_type, map);
|
|
866
863
|
if (map->initialized) rb_raise(rb_eRuntimeError, "Map is already initialized");
|
|
867
864
|
|