farce 0.4.1-x86_64-linux-gnu → 0.6.0-x86_64-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 +98 -2
- data/README.md +3 -1
- data/docs/benchmarks.md +37 -21
- data/docs/compatibility.md +13 -0
- data/docs/data-structures.md +37 -0
- data/docs/integrations.md +1 -0
- data/docs/introduction.md +19 -0
- data/docs/modes.md +2 -0
- data/docs/scheduling.md +35 -3
- data/docs/scopes.md +3 -1
- data/docs/shims.md +53 -0
- data/docs/utilities.md +32 -0
- data/docs/variants.md +9 -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 +110 -3
- data/ext/farce/map.c +15 -7
- data/ext/farce/priority_queue.c +46 -1
- data/ext/farce/queue.c +141 -7
- data/ext/farce/trie.c +140 -72
- data/ext/farce/trie_search.h +114 -0
- data/ext/farce/vector.c +53 -21
- data/ext/farce/weak_map.c +16 -11
- data/ext/farce/weak_references.h +23 -0
- data/ext/java/org/farce/FiberScheduler.java +8 -6
- data/ext/java/org/farce/PriorityQueue.java +18 -2
- data/lib/farce/abstract/atom.rb +5 -1
- data/lib/farce/abstract/concurrent_map.rb +122 -0
- data/lib/farce/abstract/counter.rb +36 -1
- data/lib/farce/abstract/executor.rb +30 -7
- data/lib/farce/abstract/flag.rb +12 -0
- data/lib/farce/abstract/molecule.rb +14 -14
- data/lib/farce/abstract/pool.rb +158 -47
- data/lib/farce/abstract/priority_queue.rb +1 -1
- data/lib/farce/abstract/publisher.rb +189 -0
- data/lib/farce/abstract/queue.rb +29 -3
- data/lib/farce/abstract/scheduler.rb +41 -14
- data/lib/farce/abstract/timer_queue.rb +3 -3
- data/lib/farce/abstract/vector.rb +3 -1
- data/lib/farce/atom.rb +15 -2
- data/lib/farce/clock.rb +20 -7
- data/lib/farce/concurrent/count_down_latch.rb +37 -0
- data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
- data/lib/farce/concurrent/event.rb +60 -0
- data/lib/farce/concurrent/fiber_local_var.rb +31 -0
- data/lib/farce/concurrent/local_var.rb +65 -0
- data/lib/farce/concurrent/thread_local_var.rb +28 -0
- data/lib/farce/concurrent/tuple.rb +74 -0
- data/lib/farce/concurrent.rb +50 -0
- data/lib/farce/config.rb +112 -2
- data/lib/farce/counter.rb +5 -15
- data/lib/farce/deduper.rb +12 -4
- data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
- data/lib/farce/engine/jruby/map.rb +8 -6
- data/lib/farce/engine/jruby/signal.rb +2 -2
- data/lib/farce/engine/jvm/counter.rb +0 -2
- data/lib/farce/engine/jvm/farce.jar +0 -0
- data/lib/farce/engine/jvm/priority_queue.rb +14 -4
- 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/parallel_scheduler.rb +9 -1
- data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
- data/lib/farce/engine/ruby/shared/weak_cache_mirror.rb +56 -0
- data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
- data/lib/farce/engine/ruby.rb +5 -1
- data/lib/farce/engine/shared/exchanger.rb +2 -2
- data/lib/farce/engine/shared/queue.rb +43 -5
- data/lib/farce/engine/shared/ractor_selector.rb +1 -1
- data/lib/farce/engine/shared/strict_map.rb +5 -0
- 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_atom.rb +1 -1
- data/lib/farce/engine/shared/unshared_vector.rb +11 -8
- data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
- data/lib/farce/engine/shared/vector.rb +2 -0
- data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
- data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
- data/lib/farce/engine/shared.rb +2 -0
- data/lib/farce/engine/truffleruby/map.rb +8 -6
- data/lib/farce/engine/truffleruby/native/counter.rb +0 -2
- data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
- data/lib/farce/engine/truffleruby/signal.rb +1 -1
- data/lib/farce/engine/truffleruby.rb +9 -0
- data/lib/farce/extension.rb +5 -3
- data/lib/farce/integrations/concurrent.rb +16 -3
- data/lib/farce/integrations/sorted_set.rb +1 -0
- data/lib/farce/internal/blocking_priority_queue.rb +4 -9
- data/lib/farce/internal/fake_ractor.rb +1 -1
- data/lib/farce/internal/flag_waiting.rb +1 -1
- data/lib/farce/internal/freeze.rb +1 -5
- data/lib/farce/internal/lease_initialization.rb +1 -1
- data/lib/farce/internal/lease_pool_state.rb +1 -1
- data/lib/farce/internal/lease_state.rb +2 -2
- data/lib/farce/internal/local_executor_methods.rb +3 -3
- data/lib/farce/internal/map_value_modes.rb +17 -9
- data/lib/farce/internal/pool_settings.rb +9 -0
- data/lib/farce/internal/pool_worker.rb +34 -21
- data/lib/farce/internal/reservation_waiting.rb +1 -1
- data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
- data/lib/farce/internal/weak_map_value_modes.rb +5 -0
- data/lib/farce/internal.rb +19 -8
- data/lib/farce/lazy.rb +3 -0
- data/lib/farce/lazy_ref.rb +3 -0
- data/lib/farce/local/counter.rb +4 -10
- data/lib/farce/local/lease.rb +2 -9
- data/lib/farce/local/lease_map.rb +2 -9
- data/lib/farce/local/pool.rb +38 -5
- data/lib/farce/local/publisher.rb +30 -0
- data/lib/farce/local/scheduler.rb +18 -5
- data/lib/farce/local/vector.rb +3 -2
- data/lib/farce/map.rb +8 -1
- data/lib/farce/memoize.rb +319 -0
- data/lib/farce/pool.rb +5 -0
- data/lib/farce/priority_queue.rb +1 -1
- data/lib/farce/publisher.rb +57 -0
- data/lib/farce/shareable.rb +3 -0
- data/lib/farce/signal.rb +1 -1
- data/lib/farce/strict/publisher.rb +31 -0
- data/lib/farce/strict/vector.rb +3 -2
- data/lib/farce/strict/weak_cache.rb +199 -0
- data/lib/farce/subscription/definitions.rb +27 -0
- data/lib/farce/subscription.rb +121 -0
- data/lib/farce/transaction/map.rb +35 -1
- data/lib/farce/unshared/pool.rb +1 -1
- data/lib/farce/unshared/publisher.rb +25 -0
- data/lib/farce/unshared/scheduler.rb +3 -3
- data/lib/farce/unshared/vector.rb +3 -2
- data/lib/farce/vector.rb +20 -16
- data/lib/farce/version.rb +1 -1
- data/lib/farce/walker/definitions.rb +31 -2
- data/lib/farce/walker/modification.rb +28 -14
- data/lib/farce/walker.rb +9 -1
- data/lib/farce/weak_atom.rb +5 -2
- 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
- data/lib/farce.rb +3 -1
- metadata +23 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* Included twice to specialize static matching without a mode branch in the
|
|
2
|
+
* traversal loop. Keep both modes on the same capture and backtracking logic. */
|
|
3
|
+
|
|
4
|
+
static VALUE
|
|
5
|
+
TRIE_SEARCH_BODY(VALUE opaque)
|
|
6
|
+
{
|
|
7
|
+
trie_search_t *search = (trie_search_t *)(uintptr_t)opaque;
|
|
8
|
+
#if TRIE_IGNORE_CASE
|
|
9
|
+
trie_case_mapping_t mapping = { .encoding = rb_enc_from_index(search->input_encoding) };
|
|
10
|
+
mapping.ascii_compatible = rb_enc_asciicompat(mapping.encoding);
|
|
11
|
+
trie_search_push_frame_ignore_case(search, search->trie->root, 0, 0, 0, &mapping);
|
|
12
|
+
#else
|
|
13
|
+
trie_search_push_frame(search, search->trie->root, 0, 0);
|
|
14
|
+
#endif
|
|
15
|
+
while (search->frame_count) {
|
|
16
|
+
trie_frame_t *frame = &search->frames[search->frame_count - 1];
|
|
17
|
+
trie_search_work_checkpoint(search);
|
|
18
|
+
if (frame->phase == 0) {
|
|
19
|
+
bool descended = false;
|
|
20
|
+
if (frame->node->static_index) {
|
|
21
|
+
while (frame->static_index) {
|
|
22
|
+
trie_static_edge_t *edge = &frame->node->static_edges[frame->static_index - 1];
|
|
23
|
+
#if TRIE_IGNORE_CASE
|
|
24
|
+
long end_cursor;
|
|
25
|
+
unsigned end_offset;
|
|
26
|
+
#else
|
|
27
|
+
long label_length = RSTRING_LEN(edge->label);
|
|
28
|
+
#endif
|
|
29
|
+
frame->static_index = edge->next_same_byte;
|
|
30
|
+
#if TRIE_IGNORE_CASE
|
|
31
|
+
if (!trie_static_match_ignore_case(search, frame, edge, &mapping,
|
|
32
|
+
&end_cursor, &end_offset)) continue;
|
|
33
|
+
trie_search_push_frame_ignore_case(search, edge->child, end_cursor,
|
|
34
|
+
search->event_count, end_offset, &mapping);
|
|
35
|
+
#else
|
|
36
|
+
if (!trie_static_match(search->input, frame->cursor, edge,
|
|
37
|
+
label_length, search->input_encoding)) continue;
|
|
38
|
+
trie_search_push_frame(search, edge->child, frame->cursor + label_length,
|
|
39
|
+
search->event_count);
|
|
40
|
+
#endif
|
|
41
|
+
descended = true;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
while (frame->static_index < frame->node->static_count) {
|
|
47
|
+
trie_static_edge_t *edge = &frame->node->static_edges[frame->static_index++];
|
|
48
|
+
#if TRIE_IGNORE_CASE
|
|
49
|
+
long end_cursor;
|
|
50
|
+
unsigned end_offset;
|
|
51
|
+
#else
|
|
52
|
+
long label_length = RSTRING_LEN(edge->label);
|
|
53
|
+
#endif
|
|
54
|
+
#if TRIE_IGNORE_CASE
|
|
55
|
+
if (!trie_static_match_ignore_case(search, frame, edge, &mapping,
|
|
56
|
+
&end_cursor, &end_offset)) continue;
|
|
57
|
+
trie_search_push_frame_ignore_case(search, edge->child, end_cursor,
|
|
58
|
+
search->event_count, end_offset, &mapping);
|
|
59
|
+
#else
|
|
60
|
+
if (!trie_static_match(search->input, frame->cursor, edge,
|
|
61
|
+
label_length, search->input_encoding)) continue;
|
|
62
|
+
trie_search_push_frame(search, edge->child, frame->cursor + label_length,
|
|
63
|
+
search->event_count);
|
|
64
|
+
#endif
|
|
65
|
+
descended = true;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (descended) continue;
|
|
70
|
+
frame->phase = 1;
|
|
71
|
+
}
|
|
72
|
+
if (frame->phase == 1) {
|
|
73
|
+
bool descended = false;
|
|
74
|
+
while (frame->regexp_index < frame->node->regexp_count) {
|
|
75
|
+
#if TRIE_IGNORE_CASE
|
|
76
|
+
if (frame->lowercase_offset) break;
|
|
77
|
+
#endif
|
|
78
|
+
trie_regexp_edge_t *edge = &frame->node->regexp_edges[frame->regexp_index++];
|
|
79
|
+
size_t restore = search->event_count;
|
|
80
|
+
long end_cursor;
|
|
81
|
+
if (!trie_regexp_match(edge, search, frame->cursor, &end_cursor)) {
|
|
82
|
+
search->event_count = restore;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
#if TRIE_IGNORE_CASE
|
|
86
|
+
trie_search_push_frame_ignore_case(search, edge->child, end_cursor, restore, 0, &mapping);
|
|
87
|
+
#else
|
|
88
|
+
trie_search_push_frame(search, edge->child, end_cursor, restore);
|
|
89
|
+
#endif
|
|
90
|
+
descended = true;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
if (descended) continue;
|
|
94
|
+
frame->phase = 2;
|
|
95
|
+
}
|
|
96
|
+
if (frame->phase == 2) {
|
|
97
|
+
frame->phase = 3;
|
|
98
|
+
if (
|
|
99
|
+
#if TRIE_IGNORE_CASE
|
|
100
|
+
!frame->lowercase_offset &&
|
|
101
|
+
#endif
|
|
102
|
+
(search->peek || frame->cursor == search->input_length) &&
|
|
103
|
+
trie_emit_terminal(search, frame)) break;
|
|
104
|
+
}
|
|
105
|
+
if (frame->phase == 3) {
|
|
106
|
+
size_t restore = frame->restore_events;
|
|
107
|
+
search->frame_count--;
|
|
108
|
+
search->event_count = restore;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (search->stream) return search->self;
|
|
112
|
+
return search->first_only ? search->first : search->results;
|
|
113
|
+
}
|
|
114
|
+
|
data/ext/farce/vector.c
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include "ruby/fiber/scheduler.h"
|
|
5
5
|
|
|
6
6
|
#include <errno.h>
|
|
7
|
+
#include <limits.h>
|
|
7
8
|
#include <math.h>
|
|
8
9
|
#include <stdint.h>
|
|
9
10
|
#include <stdlib.h>
|
|
@@ -380,22 +381,26 @@ vector_assignment_index(vector_t *vector, long long raw)
|
|
|
380
381
|
return (size_t)raw;
|
|
381
382
|
}
|
|
382
383
|
|
|
384
|
+
static size_t
|
|
385
|
+
vector_initial_size(VALUE source)
|
|
386
|
+
{
|
|
387
|
+
long size = NUM2LONG(source);
|
|
388
|
+
if (size < 0) rb_raise(rb_eArgError, "negative array size");
|
|
389
|
+
if (size > LONG_MAX / (long)sizeof(VALUE)) rb_raise(rb_eArgError, "array size too big");
|
|
390
|
+
return (size_t)size;
|
|
391
|
+
}
|
|
392
|
+
|
|
383
393
|
static VALUE
|
|
384
|
-
|
|
394
|
+
vector_normalize_source(VALUE klass, VALUE source)
|
|
385
395
|
{
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
ID keyword_ids[] = {rb_intern("compare_by_identity")};
|
|
390
|
-
VALUE keyword_values[1];
|
|
391
|
-
vector_t *vector;
|
|
396
|
+
if (RB_INTEGER_TYPE_P(source)) vector_initial_size(source);
|
|
397
|
+
return source;
|
|
398
|
+
}
|
|
392
399
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
identity = keyword_values[0];
|
|
398
|
-
}
|
|
400
|
+
static VALUE
|
|
401
|
+
vector_initialize_with_identity(VALUE self, VALUE source, VALUE identity)
|
|
402
|
+
{
|
|
403
|
+
vector_t *vector;
|
|
399
404
|
TypedData_Get_Struct(self, vector_t, &vector_type, vector);
|
|
400
405
|
if (vector->initialized) rb_raise(rb_eRuntimeError, "Vector is already initialized");
|
|
401
406
|
|
|
@@ -403,8 +408,12 @@ vector_initialize(int argc, VALUE *argv, VALUE self)
|
|
|
403
408
|
? false
|
|
404
409
|
: containers_strict_bool(identity, "compare_by_identity");
|
|
405
410
|
|
|
411
|
+
bool sized = RB_INTEGER_TYPE_P(source);
|
|
406
412
|
size_t size = 0;
|
|
407
|
-
if (
|
|
413
|
+
if (sized) {
|
|
414
|
+
size = vector_initial_size(source);
|
|
415
|
+
}
|
|
416
|
+
else if (!NIL_P(source)) {
|
|
408
417
|
Check_Type(source, T_ARRAY);
|
|
409
418
|
size = (size_t)RARRAY_LEN(source);
|
|
410
419
|
}
|
|
@@ -413,16 +422,37 @@ vector_initialize(int argc, VALUE *argv, VALUE self)
|
|
|
413
422
|
if (!vector->values) rb_memerror();
|
|
414
423
|
vector->capacity = capacity;
|
|
415
424
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
425
|
+
if (!sized) {
|
|
426
|
+
for (size_t index = 0; index < size; index++) {
|
|
427
|
+
VALUE value = rb_ary_entry(source, (long)index);
|
|
428
|
+
containers_check_shareable(value);
|
|
429
|
+
RB_OBJ_WRITE(self, &vector->values[index], value);
|
|
430
|
+
}
|
|
420
431
|
}
|
|
421
432
|
vector->size = size;
|
|
422
433
|
vector->initialized = true;
|
|
434
|
+
if (sized) return containers_publish_native_reference_free(self);
|
|
423
435
|
return containers_publish_native_with_references(self, vector_validate_references);
|
|
424
436
|
}
|
|
425
437
|
|
|
438
|
+
static VALUE
|
|
439
|
+
vector_initialize(int argc, VALUE *argv, VALUE self)
|
|
440
|
+
{
|
|
441
|
+
ID keyword_ids[] = {rb_intern("compare_by_identity")};
|
|
442
|
+
VALUE keyword_values[1];
|
|
443
|
+
|
|
444
|
+
rb_check_frozen(self);
|
|
445
|
+
VALUE source = containers_parse_constructor(argc, argv, keyword_ids, 1, keyword_values);
|
|
446
|
+
return vector_initialize_with_identity(self, source, keyword_values[0]);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
static VALUE
|
|
450
|
+
vector_new_for_modes(VALUE klass, VALUE source)
|
|
451
|
+
{
|
|
452
|
+
VALUE self = rb_obj_alloc(klass);
|
|
453
|
+
return vector_initialize_with_identity(self, source, Qtrue);
|
|
454
|
+
}
|
|
455
|
+
|
|
426
456
|
static VALUE
|
|
427
457
|
vector_snapshot_locked(VALUE self)
|
|
428
458
|
{
|
|
@@ -1002,7 +1032,7 @@ vector_wait_until_changed(int argc, VALUE *argv, VALUE self)
|
|
|
1002
1032
|
uint64_t generation;
|
|
1003
1033
|
size_t index;
|
|
1004
1034
|
pthread_mutex_lock(&vector->lock);
|
|
1005
|
-
if (!vector_wait_for_reservation_locked(vector, &timeout)) return Qnil;
|
|
1035
|
+
if (!vector_wait_for_reservation_locked(vector, &timeout)) return rb_block_given_p() ? rb_yield(Qnil) : Qnil;
|
|
1006
1036
|
VALUE current = vector_lookup_index(vector, raw, &index) ? vector->values[index] : Qnil;
|
|
1007
1037
|
generation = vector->generation;
|
|
1008
1038
|
pthread_mutex_unlock(&vector->lock);
|
|
@@ -1010,12 +1040,12 @@ vector_wait_until_changed(int argc, VALUE *argv, VALUE self)
|
|
|
1010
1040
|
if (!vector_values_equal(vector, current, expected)) return current;
|
|
1011
1041
|
|
|
1012
1042
|
pthread_mutex_lock(&vector->lock);
|
|
1013
|
-
if (!vector_wait_for_reservation_locked(vector, &timeout)) return Qnil;
|
|
1043
|
+
if (!vector_wait_for_reservation_locked(vector, &timeout)) return rb_block_given_p() ? rb_yield(Qnil) : Qnil;
|
|
1014
1044
|
if (vector->generation != generation) {
|
|
1015
1045
|
pthread_mutex_unlock(&vector->lock);
|
|
1016
1046
|
continue;
|
|
1017
1047
|
}
|
|
1018
|
-
if (!vector_wait_once(vector, &timeout)) return Qnil;
|
|
1048
|
+
if (!vector_wait_once(vector, &timeout)) return rb_block_given_p() ? rb_yield(Qnil) : Qnil;
|
|
1019
1049
|
}
|
|
1020
1050
|
}
|
|
1021
1051
|
|
|
@@ -1185,6 +1215,8 @@ containers_init_vector(VALUE namespace)
|
|
|
1185
1215
|
|
|
1186
1216
|
cVector = rb_define_class_under(namespace, "Vector", rb_cObject);
|
|
1187
1217
|
rb_define_alloc_func(cVector, vector_allocate);
|
|
1218
|
+
rb_define_singleton_method(cVector, "normalize_source", vector_normalize_source, 1);
|
|
1219
|
+
rb_define_singleton_method(cVector, "new_for_modes", vector_new_for_modes, 1);
|
|
1188
1220
|
rb_define_method(cVector, "freeze", vector_freeze, 0);
|
|
1189
1221
|
rb_define_method(cVector, "initialize", vector_initialize, -1);
|
|
1190
1222
|
rb_define_method(cVector, "size", vector_size, 0);
|
data/ext/farce/weak_map.c
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "containers.h"
|
|
2
|
+
#include "weak_references.h"
|
|
2
3
|
|
|
3
4
|
#ifdef RC_HAVE_NATIVE_WEAK_REFERENCES
|
|
4
5
|
|
|
@@ -15,11 +16,6 @@
|
|
|
15
16
|
|
|
16
17
|
#define WEAK_MAP_INITIAL_CAPACITY 16
|
|
17
18
|
|
|
18
|
-
/* These functions are exported by Rubies with the probed typed-data weak
|
|
19
|
-
* reference callback, but their declarations intentionally remain internal. */
|
|
20
|
-
void rb_gc_declare_weak_references(VALUE object);
|
|
21
|
-
bool rb_gc_handle_weak_references_alive_p(VALUE object);
|
|
22
|
-
|
|
23
19
|
static VALUE cWeakKeyMap;
|
|
24
20
|
static VALUE weak_map_keep;
|
|
25
21
|
static VALUE weak_map_delete_token;
|
|
@@ -209,7 +205,7 @@ weak_map_compact(void *pointer)
|
|
|
209
205
|
* mutex here could deadlock if GC stopped a mutator at a safepoint while it
|
|
210
206
|
* owned the mutex, so cleanup deliberately runs lock-free. */
|
|
211
207
|
static void
|
|
212
|
-
weak_map_handle_weak_references
|
|
208
|
+
FARCE_WEAK_REFERENCES_CALLBACK(weak_map_handle_weak_references)
|
|
213
209
|
{
|
|
214
210
|
weak_map_t *map = pointer;
|
|
215
211
|
if (!map->slots) return;
|
|
@@ -219,8 +215,8 @@ weak_map_handle_weak_references(void *pointer)
|
|
|
219
215
|
weak_map_slot_t *slot = &map->slots[index];
|
|
220
216
|
if (slot->state != WEAK_MAP_OCCUPIED) continue;
|
|
221
217
|
|
|
222
|
-
bool key_alive = !map->weak_keys ||
|
|
223
|
-
bool value_alive = !map->weak_values ||
|
|
218
|
+
bool key_alive = !map->weak_keys || FARCE_WEAK_REFERENCE_ALIVE_P(slot->key);
|
|
219
|
+
bool value_alive = !map->weak_values || FARCE_WEAK_REFERENCE_ALIVE_P(slot->value);
|
|
224
220
|
if (key_alive && value_alive) continue;
|
|
225
221
|
|
|
226
222
|
slot->key = Qnil;
|
|
@@ -1173,9 +1169,15 @@ weak_map_modify_body(VALUE opaque)
|
|
|
1173
1169
|
}
|
|
1174
1170
|
|
|
1175
1171
|
static VALUE
|
|
1176
|
-
weak_map_modify(VALUE
|
|
1172
|
+
weak_map_modify(int argc, VALUE *argv, VALUE self)
|
|
1177
1173
|
{
|
|
1178
1174
|
rb_check_frozen(self);
|
|
1175
|
+
VALUE key;
|
|
1176
|
+
VALUE keywords = Qnil;
|
|
1177
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1178
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1179
|
+
rb_scan_args(argc, argv, "1:", &key, &keywords);
|
|
1180
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1179
1181
|
rb_need_block();
|
|
1180
1182
|
weak_map_t *map = get_weak_map(self);
|
|
1181
1183
|
key = weak_map_prepare_key(map, key);
|
|
@@ -1187,8 +1189,11 @@ weak_map_modify(VALUE self, VALUE key)
|
|
|
1187
1189
|
};
|
|
1188
1190
|
operation.hash = weak_map_key_hash(map, key);
|
|
1189
1191
|
rb_check_frozen(self);
|
|
1192
|
+
weak_map_timeout_t timeout = weak_map_parse_timeout(
|
|
1193
|
+
keyword_values[0] == Qundef ? Qnil : keyword_values[0]
|
|
1194
|
+
);
|
|
1190
1195
|
weak_map_execution_context_t execution = {.self = self};
|
|
1191
|
-
|
|
1196
|
+
if (!weak_map_lock_for_key_with_timeout(map, key, operation.hash, &timeout, &execution)) return Qfalse;
|
|
1192
1197
|
bool found;
|
|
1193
1198
|
size_t index = weak_map_find_slot(map, key, operation.hash, &found, &execution);
|
|
1194
1199
|
if (RB_OBJ_FROZEN(self)) {
|
|
@@ -1675,7 +1680,7 @@ define_weak_map_methods(VALUE klass)
|
|
|
1675
1680
|
rb_define_method(klass, "store", weak_map_store_with_timeout, -1);
|
|
1676
1681
|
rb_define_method(klass, "swap", weak_map_swap, -1);
|
|
1677
1682
|
rb_define_method(klass, "update", weak_map_update, -1);
|
|
1678
|
-
rb_define_method(klass, "modify", weak_map_modify, 1);
|
|
1683
|
+
rb_define_method(klass, "modify", weak_map_modify, -1);
|
|
1679
1684
|
rb_define_method(klass, "wait_until_changed", weak_map_wait_until_changed, -1);
|
|
1680
1685
|
rb_define_method(klass, "wait_until_non_nil", weak_map_wait_until_non_nil, -1);
|
|
1681
1686
|
rb_define_method(klass, "store_if_absent", weak_map_store_if_absent, -1);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#ifndef FARCE_WEAK_REFERENCES_H
|
|
2
|
+
#define FARCE_WEAK_REFERENCES_H
|
|
3
|
+
|
|
4
|
+
#include "ruby.h"
|
|
5
|
+
#include <stdbool.h>
|
|
6
|
+
|
|
7
|
+
#ifdef RC_HAVE_NATIVE_WEAK_REFERENCES
|
|
8
|
+
/* These symbols are exported, but their declarations remain internal. The
|
|
9
|
+
* callback and liveness function gained the same opaque GC context together. */
|
|
10
|
+
void rb_gc_declare_weak_references(VALUE object);
|
|
11
|
+
|
|
12
|
+
#ifdef FARCE_WEAK_REFERENCES_HAVE_OBJSPACE
|
|
13
|
+
bool rb_gc_handle_weak_references_alive_p(void *objspace, VALUE object);
|
|
14
|
+
# define FARCE_WEAK_REFERENCES_CALLBACK(name) name(void *pointer, void *objspace)
|
|
15
|
+
# define FARCE_WEAK_REFERENCE_ALIVE_P(object) rb_gc_handle_weak_references_alive_p(objspace, object)
|
|
16
|
+
#else
|
|
17
|
+
bool rb_gc_handle_weak_references_alive_p(VALUE object);
|
|
18
|
+
# define FARCE_WEAK_REFERENCES_CALLBACK(name) name(void *pointer)
|
|
19
|
+
# define FARCE_WEAK_REFERENCE_ALIVE_P(object) rb_gc_handle_weak_references_alive_p(object)
|
|
20
|
+
#endif
|
|
21
|
+
#endif
|
|
22
|
+
|
|
23
|
+
#endif
|
|
@@ -17,6 +17,7 @@ import org.jruby.RubyModule;
|
|
|
17
17
|
import org.jruby.RubyObject;
|
|
18
18
|
import org.jruby.RubyThread;
|
|
19
19
|
import org.jruby.anno.JRubyMethod;
|
|
20
|
+
import org.jruby.api.Access;
|
|
20
21
|
import org.jruby.runtime.ThreadContext;
|
|
21
22
|
import org.jruby.runtime.builtin.IRubyObject;
|
|
22
23
|
import org.jruby.util.io.SelectExecutor;
|
|
@@ -30,9 +31,10 @@ public final class FiberScheduler extends RubyObject {
|
|
|
30
31
|
public FiberScheduler(Ruby runtime, RubyClass klass) { super(runtime, klass); }
|
|
31
32
|
|
|
32
33
|
public static void load(Ruby runtime) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
klass.
|
|
34
|
+
ThreadContext context = runtime.getCurrentContext();
|
|
35
|
+
RubyModule internal = (RubyModule) Access.getModule(context, "Farce").getConstantAt(context, "Internal");
|
|
36
|
+
RubyClass klass = internal.defineClassUnder(context, "FiberScheduler", runtime.getObject(), FiberScheduler::new);
|
|
37
|
+
klass.defineMethods(context, FiberScheduler.class);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
@JRubyMethod(name = "nio_initialize")
|
|
@@ -59,14 +61,14 @@ public final class FiberScheduler extends RubyObject {
|
|
|
59
61
|
checkOwner(context);
|
|
60
62
|
if (selector == null) throw context.runtime.newIOError("scheduler is closed");
|
|
61
63
|
RubyArray groups = (RubyArray) groupValue;
|
|
62
|
-
Long milliseconds = timeout.isNil() ? null : Math.max(0, (long)Math.ceil(timeout.convertToFloat().
|
|
64
|
+
Long milliseconds = timeout.isNil() ? null : Math.max(0, (long)Math.ceil(timeout.convertToFloat().asDouble(context) * 1000));
|
|
63
65
|
IRubyObject immediate = snapshot(context, groups, 0L);
|
|
64
66
|
if (!immediate.isNil() || (milliseconds != null && milliseconds == 0)) return immediate;
|
|
65
67
|
HashMap<SelectableChannel, Integer> interests = new HashMap<>();
|
|
66
68
|
boolean compatible = ((RubyArray)groups.entry(2)).isEmpty();
|
|
67
69
|
for (int i = 0; i < 2; i++) {
|
|
68
70
|
RubyArray list = (RubyArray)groups.entry(i);
|
|
69
|
-
for (Object object : list.toJavaArray()) {
|
|
71
|
+
for (Object object : list.toJavaArray(context)) {
|
|
70
72
|
RubyIO io = RubyIO.convertToIO(context, (IRubyObject)object);
|
|
71
73
|
SelectableChannel channel = io.getOpenFileChecked().fd().chSelect;
|
|
72
74
|
if (channel == null || channel.provider() != selector.provider()) { compatible = false; continue; }
|
|
@@ -123,7 +125,7 @@ public final class FiberScheduler extends RubyObject {
|
|
|
123
125
|
public IRubyObject hookBuffer(ThreadContext context, IRubyObject value) {
|
|
124
126
|
RubyIOBuffer buffer = (RubyIOBuffer)value;
|
|
125
127
|
if (!buffer.locked_p(context).isTrue()) return value;
|
|
126
|
-
if (buffer.size(context).convertToInteger().
|
|
128
|
+
if (buffer.size(context).convertToInteger().asLong(context) != 0) return value;
|
|
127
129
|
try {
|
|
128
130
|
Field field = RubyIOBuffer.class.getDeclaredField("base");
|
|
129
131
|
if (!field.trySetAccessible()) throw context.runtime.newRuntimeError("Farce cannot access the JRuby hook buffer layout");
|
|
@@ -54,7 +54,8 @@ public final class PriorityQueue {
|
|
|
54
54
|
private final Consumer<Runnable> committer;
|
|
55
55
|
private final QueueSignal signal;
|
|
56
56
|
private final BooleanSupplier scheduled;
|
|
57
|
-
private
|
|
57
|
+
private long capacity;
|
|
58
|
+
private Object rubyCapacity;
|
|
58
59
|
private final boolean trackAge;
|
|
59
60
|
private long size;
|
|
60
61
|
private long generation;
|
|
@@ -70,7 +71,7 @@ public final class PriorityQueue {
|
|
|
70
71
|
BiPredicate<Object, Object> identical,
|
|
71
72
|
BiPredicate<Object, Object> match,
|
|
72
73
|
Consumer<Runnable> committer,
|
|
73
|
-
BooleanSupplier scheduled, long capacity, QueueSignal signal,
|
|
74
|
+
BooleanSupplier scheduled, long capacity, Object rubyCapacity, QueueSignal signal,
|
|
74
75
|
boolean trackAge) {
|
|
75
76
|
this.tree = new TreeMap<>(comparator);
|
|
76
77
|
this.snapshot = snapshot;
|
|
@@ -82,6 +83,7 @@ public final class PriorityQueue {
|
|
|
82
83
|
this.signal = signal;
|
|
83
84
|
this.scheduled = scheduled;
|
|
84
85
|
this.capacity = capacity;
|
|
86
|
+
this.rubyCapacity = rubyCapacity;
|
|
85
87
|
this.trackAge = trackAge;
|
|
86
88
|
}
|
|
87
89
|
|
|
@@ -302,6 +304,20 @@ public final class PriorityQueue {
|
|
|
302
304
|
}
|
|
303
305
|
}
|
|
304
306
|
|
|
307
|
+
public Object getCapacity() {
|
|
308
|
+
enter();
|
|
309
|
+
try { return rubyCapacity; } finally { lock.unlock(); }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
public void setCapacity(long capacity, Object rubyCapacity) {
|
|
313
|
+
enter();
|
|
314
|
+
try { commit(() -> {
|
|
315
|
+
this.capacity = capacity;
|
|
316
|
+
this.rubyCapacity = rubyCapacity;
|
|
317
|
+
}); }
|
|
318
|
+
finally { lock.unlock(); }
|
|
319
|
+
}
|
|
320
|
+
|
|
305
321
|
public long size() {
|
|
306
322
|
enter();
|
|
307
323
|
try { return size; } finally { lock.unlock(); }
|
data/lib/farce/abstract/atom.rb
CHANGED
|
@@ -59,7 +59,11 @@ module Farce
|
|
|
59
59
|
# @yield computes the value to store when the current value is nil
|
|
60
60
|
# @yieldreturn [BasicObject, nil] the value to store
|
|
61
61
|
# @return [BasicObject, nil] the current or newly stored value, or nil when the timeout expires
|
|
62
|
-
def store_if_absent(timeout: nil, &)
|
|
62
|
+
def store_if_absent(timeout: nil, &)
|
|
63
|
+
return internal_atom.store_if_absent(&) if timeout.nil?
|
|
64
|
+
|
|
65
|
+
internal_atom.store_if_absent(timeout:, &)
|
|
66
|
+
end
|
|
63
67
|
|
|
64
68
|
# Atomically replace the current value if it matches the expected value.
|
|
65
69
|
# @param expected [BasicObject, nil] the value to compare with the current value
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
module Farce
|
|
6
6
|
module Abstract
|
|
7
7
|
# @abstract Super class for {Map maps} with added concurrency features.
|
|
8
|
+
#
|
|
9
|
+
# Maps implement most of the interface provided by Hash,
|
|
10
|
+
# as well as `Concurrent::Map` from the `concurrent-ruby` gem.
|
|
8
11
|
class ConcurrentMap < Map
|
|
9
12
|
include DuplicableMap
|
|
10
13
|
|
|
@@ -34,6 +37,24 @@ module Farce
|
|
|
34
37
|
end
|
|
35
38
|
end
|
|
36
39
|
|
|
40
|
+
# Return an existing value or store a default for an absent key.
|
|
41
|
+
# A block takes precedence over the default and receives the original key.
|
|
42
|
+
# The lookup and store are separate operations. Concurrent writes can be overwritten.
|
|
43
|
+
# @param key [BasicObject] The key to look up or store.
|
|
44
|
+
# @param defaults [Array<BasicObject>] At most one default value to store when the key is absent.
|
|
45
|
+
# @see #store For supported transfer and timeout options.
|
|
46
|
+
# @yieldparam key [BasicObject] The original key.
|
|
47
|
+
# @yieldreturn [BasicObject] The value to store.
|
|
48
|
+
# @return [BasicObject, nil] The existing or newly stored value.
|
|
49
|
+
# @raise [KeyError] If the key is absent and neither a default nor a block is provided.
|
|
50
|
+
def fetch_or_store(key, *defaults, **)
|
|
51
|
+
if defaults.size > 1
|
|
52
|
+
raise ArgumentError, "wrong number of arguments (given #{defaults.size + 1}, expected 1..2)"
|
|
53
|
+
end
|
|
54
|
+
return fetch(key) if defaults.empty? && !block_given?
|
|
55
|
+
fetch(key) { store(key, block_given? ? yield(key) : defaults.first, **) }
|
|
56
|
+
end
|
|
57
|
+
|
|
37
58
|
# Read the value associated with a key, waiting for atomic-update access if necessary.
|
|
38
59
|
# @param key [BasicObject] The key to look up.
|
|
39
60
|
# @param timeout [Numeric, nil] The maximum number of seconds to wait for access.
|
|
@@ -43,6 +64,12 @@ module Farce
|
|
|
43
64
|
# The associated value, nil if the key is absent, or the fallback result if the operation times out.
|
|
44
65
|
def get(key, timeout: nil, &) = internal_map.get(key, timeout:, &)
|
|
45
66
|
|
|
67
|
+
# Sets a value and returns the previous value.
|
|
68
|
+
# @param key [BasicObject] The key to update.
|
|
69
|
+
# @param value [BasicObject] The value to store.
|
|
70
|
+
# @return [BasicObject, nil] The previous value, or nil if the key was absent.
|
|
71
|
+
def get_and_set(...) = swap(...)
|
|
72
|
+
|
|
46
73
|
# Associate a value with a key, waiting for atomic-update access if necessary.
|
|
47
74
|
# @param key [BasicObject] The key to store.
|
|
48
75
|
# @param value [BasicObject] The value to store.
|
|
@@ -52,6 +79,9 @@ module Farce
|
|
|
52
79
|
# @return [BasicObject, nil] `value` on success, or the fallback result or nil on timeout.
|
|
53
80
|
def store(key, value, timeout: nil, &) = internal_map.store(key, value, timeout:, &)
|
|
54
81
|
|
|
82
|
+
# (see #store)
|
|
83
|
+
def put(...) = store(...)
|
|
84
|
+
|
|
55
85
|
# Replace the value associated with a key and return its previous value.
|
|
56
86
|
# @param key [BasicObject] The key to update.
|
|
57
87
|
# @param replacement [BasicObject] The value to store.
|
|
@@ -71,6 +101,69 @@ module Farce
|
|
|
71
101
|
# @raise [LocalJumpError] If no block is given.
|
|
72
102
|
def store_if_absent(key, timeout: nil, &) = internal_map.store_if_absent(key, timeout:, &)
|
|
73
103
|
|
|
104
|
+
# (see #store_if_absent)
|
|
105
|
+
def compute_if_absent(...) = store_if_absent(...)
|
|
106
|
+
|
|
107
|
+
# Atomically store a value only when the key is absent.
|
|
108
|
+
# Existing nil and false values are preserved.
|
|
109
|
+
# @param key [BasicObject] The key to look up or store.
|
|
110
|
+
# @param value [BasicObject] The value to store when the key is absent.
|
|
111
|
+
# @see #store_if_absent For supported transfer and timeout options.
|
|
112
|
+
# @return [BasicObject, nil] The existing value, or nil when a value was inserted or access timed out.
|
|
113
|
+
def put_if_absent(key, value, **)
|
|
114
|
+
inserted = false
|
|
115
|
+
previous = store_if_absent(key, **) do
|
|
116
|
+
inserted = true
|
|
117
|
+
value
|
|
118
|
+
end
|
|
119
|
+
inserted ? nil : previous
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Atomically replace an existing value and return its previous value.
|
|
123
|
+
# An absent key is left unchanged. Existing nil and false values can be replaced.
|
|
124
|
+
# @param key [BasicObject] The key to update.
|
|
125
|
+
# @param replacement [BasicObject] The value to store.
|
|
126
|
+
# @param timeout [Numeric, nil] The maximum number of seconds to wait for access.
|
|
127
|
+
# @see #store For supported transfer options.
|
|
128
|
+
# @return [BasicObject, nil] The previous value, or nil when the key is absent or access timed out.
|
|
129
|
+
def replace_if_exists(key, replacement, timeout: nil, **)
|
|
130
|
+
map = internal_map
|
|
131
|
+
previous = nil
|
|
132
|
+
changed = map.modify(key, timeout:) do |present, value|
|
|
133
|
+
next Internal::MAP_KEEP unless present
|
|
134
|
+
previous = value
|
|
135
|
+
map.check_mutation
|
|
136
|
+
wrap_value(replacement, **)
|
|
137
|
+
end
|
|
138
|
+
unwrap_value(previous) if changed
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Atomically replace the value associated with a key if it is present.
|
|
142
|
+
# The block receives the current value and should return the new value to store.
|
|
143
|
+
# The block is not called for an absent key. A stored nil is passed to the block.
|
|
144
|
+
# @param key [BasicObject] The key to update.
|
|
145
|
+
# @param timeout [Numeric, nil] The maximum number of seconds to wait for access.
|
|
146
|
+
# @yield [value] Called with the current value if the key is present.
|
|
147
|
+
# @yieldparam value [BasicObject] The current value.
|
|
148
|
+
# @yieldreturn [BasicObject] The new value to store.
|
|
149
|
+
# @return [BasicObject, nil] The newly stored value, or nil if the key was absent or the operation timed out.
|
|
150
|
+
# @raise [LocalJumpError] If no block is given.
|
|
151
|
+
def store_if_present(key, timeout: nil)
|
|
152
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
153
|
+
map = internal_map
|
|
154
|
+
result = nil
|
|
155
|
+
changed = map.modify(key, timeout:) do |present, value|
|
|
156
|
+
next Internal::MAP_KEEP unless present
|
|
157
|
+
replacement = yield(unwrap_value(value))
|
|
158
|
+
map.check_mutation
|
|
159
|
+
result = wrap_value(replacement)
|
|
160
|
+
end
|
|
161
|
+
unwrap_value(result) if changed
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# (see #store_if_present)
|
|
165
|
+
def compute_if_present(...) = store_if_present(...)
|
|
166
|
+
|
|
74
167
|
# Atomically replace a value if the key is present and its current value matches `expected`.
|
|
75
168
|
# The configured value-comparison mode determines whether matching uses equality or identity.
|
|
76
169
|
# @param key [BasicObject] The key to update.
|
|
@@ -82,6 +175,23 @@ module Farce
|
|
|
82
175
|
internal_map.compare_and_set(key, expected, replacement, timeout:)
|
|
83
176
|
end
|
|
84
177
|
|
|
178
|
+
# (see #compare_and_set)
|
|
179
|
+
def replace_pair(...) = compare_and_set(...)
|
|
180
|
+
|
|
181
|
+
# Atomically remove a key if its current value matches `expected`.
|
|
182
|
+
# The configured value-comparison mode determines whether matching uses equality or identity.
|
|
183
|
+
# Missing keys and mismatched values are left unchanged.
|
|
184
|
+
# @param key [BasicObject] The key to remove.
|
|
185
|
+
# @param expected [BasicObject] The expected current value.
|
|
186
|
+
# @param timeout [Numeric, nil] The maximum number of seconds to wait for access.
|
|
187
|
+
# @return [Boolean] Whether the entry was removed. Returns false on timeout.
|
|
188
|
+
def delete_pair(key, expected, timeout: nil)
|
|
189
|
+
expected = wrap_comparison(expected)
|
|
190
|
+
internal_map.modify(key, timeout:) do |present, value|
|
|
191
|
+
present && values_equal?(value, expected) ? Internal::MAP_DELETE : Internal::MAP_KEEP
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
85
195
|
# Atomically replace the value associated with a key with the block result.
|
|
86
196
|
# The block receives nil when the key is absent or its current value is nil.
|
|
87
197
|
# @param key [BasicObject] The key to update.
|
|
@@ -93,6 +203,9 @@ module Farce
|
|
|
93
203
|
# @raise [LocalJumpError] If no block is given.
|
|
94
204
|
def update(key, timeout: nil, &) = internal_map.update(key, timeout:, &)
|
|
95
205
|
|
|
206
|
+
# (see #update)
|
|
207
|
+
def compute(...) = update(...)
|
|
208
|
+
|
|
96
209
|
# Atomically insert `initial` for an absent key or replace an existing value with the block result.
|
|
97
210
|
# The block is not called when the key is absent.
|
|
98
211
|
# @param key [BasicObject] The key to insert or update.
|
|
@@ -105,6 +218,9 @@ module Farce
|
|
|
105
218
|
# @raise [LocalJumpError] If no block is given.
|
|
106
219
|
def upsert(key, initial, timeout: nil, &) = internal_map.upsert(key, initial, timeout:, &)
|
|
107
220
|
|
|
221
|
+
# (see #upsert)
|
|
222
|
+
def merge_pair(...) = upsert(...)
|
|
223
|
+
|
|
108
224
|
# Wait until a block condition matches the current value.
|
|
109
225
|
# One timeout budget covers all checks and waits. The block is not interrupted.
|
|
110
226
|
# @yieldparam value [BasicObject, nil] the current value
|
|
@@ -344,6 +460,12 @@ module Farce
|
|
|
344
460
|
|
|
345
461
|
private
|
|
346
462
|
|
|
463
|
+
def wrap_comparison(value) = value
|
|
464
|
+
|
|
465
|
+
def values_equal?(left, right)
|
|
466
|
+
compare_values_by_identity? ? left.equal?(right) : left == right
|
|
467
|
+
end
|
|
468
|
+
|
|
347
469
|
def modify_entry(key, canonical: false)
|
|
348
470
|
map = internal_map
|
|
349
471
|
map = map.instance_variable_get(:@map) if canonical && map.is_a?(Internal::KeyNormalizer::ConcurrentMap)
|