farce 0.4.1-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.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -2
  3. data/README.md +3 -1
  4. data/docs/benchmarks.md +37 -21
  5. data/docs/compatibility.md +13 -0
  6. data/docs/data-structures.md +37 -0
  7. data/docs/integrations.md +1 -0
  8. data/docs/introduction.md +19 -0
  9. data/docs/modes.md +2 -0
  10. data/docs/scheduling.md +35 -3
  11. data/docs/scopes.md +3 -1
  12. data/docs/shims.md +53 -0
  13. data/docs/utilities.md +32 -0
  14. data/docs/variants.md +9 -0
  15. data/ext/farce/atom.c +23 -7
  16. data/ext/farce/containers.h +3 -0
  17. data/ext/farce/counter.c +4 -6
  18. data/ext/farce/depend +3 -1
  19. data/ext/farce/extconf.rb +19 -14
  20. data/ext/farce/farce.c +110 -3
  21. data/ext/farce/map.c +15 -7
  22. data/ext/farce/priority_queue.c +46 -1
  23. data/ext/farce/queue.c +141 -7
  24. data/ext/farce/trie.c +140 -72
  25. data/ext/farce/trie_search.h +114 -0
  26. data/ext/farce/vector.c +53 -21
  27. data/ext/farce/weak_map.c +16 -11
  28. data/ext/farce/weak_references.h +23 -0
  29. data/ext/java/org/farce/FiberScheduler.java +8 -6
  30. data/ext/java/org/farce/PriorityQueue.java +18 -2
  31. data/lib/farce/abstract/atom.rb +5 -1
  32. data/lib/farce/abstract/concurrent_map.rb +122 -0
  33. data/lib/farce/abstract/counter.rb +36 -1
  34. data/lib/farce/abstract/executor.rb +30 -7
  35. data/lib/farce/abstract/flag.rb +12 -0
  36. data/lib/farce/abstract/molecule.rb +14 -14
  37. data/lib/farce/abstract/pool.rb +158 -47
  38. data/lib/farce/abstract/priority_queue.rb +1 -1
  39. data/lib/farce/abstract/publisher.rb +189 -0
  40. data/lib/farce/abstract/queue.rb +29 -3
  41. data/lib/farce/abstract/scheduler.rb +41 -14
  42. data/lib/farce/abstract/timer_queue.rb +3 -3
  43. data/lib/farce/abstract/vector.rb +3 -1
  44. data/lib/farce/atom.rb +15 -2
  45. data/lib/farce/clock.rb +20 -7
  46. data/lib/farce/concurrent/count_down_latch.rb +37 -0
  47. data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
  48. data/lib/farce/concurrent/event.rb +60 -0
  49. data/lib/farce/concurrent/fiber_local_var.rb +31 -0
  50. data/lib/farce/concurrent/local_var.rb +65 -0
  51. data/lib/farce/concurrent/thread_local_var.rb +28 -0
  52. data/lib/farce/concurrent/tuple.rb +74 -0
  53. data/lib/farce/concurrent.rb +50 -0
  54. data/lib/farce/config.rb +112 -2
  55. data/lib/farce/counter.rb +5 -15
  56. data/lib/farce/deduper.rb +12 -4
  57. data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
  58. data/lib/farce/engine/jruby/map.rb +8 -6
  59. data/lib/farce/engine/jruby/signal.rb +2 -2
  60. data/lib/farce/engine/jvm/counter.rb +0 -2
  61. data/lib/farce/engine/jvm/farce.jar +0 -0
  62. data/lib/farce/engine/jvm/priority_queue.rb +14 -4
  63. data/lib/farce/engine/ruby/3.4/farce.so +0 -0
  64. data/lib/farce/engine/ruby/4.0/farce.so +0 -0
  65. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
  66. data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
  67. data/lib/farce/engine/ruby/shared/weak_cache_mirror.rb +56 -0
  68. data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
  69. data/lib/farce/engine/ruby.rb +5 -1
  70. data/lib/farce/engine/shared/exchanger.rb +2 -2
  71. data/lib/farce/engine/shared/queue.rb +43 -5
  72. data/lib/farce/engine/shared/ractor_selector.rb +1 -1
  73. data/lib/farce/engine/shared/strict_map.rb +5 -0
  74. data/lib/farce/engine/shared/trie/ignore_case.rb +136 -0
  75. data/lib/farce/engine/shared/trie.rb +14 -1
  76. data/lib/farce/engine/shared/trie_builder.rb +19 -8
  77. data/lib/farce/engine/shared/unshared_atom.rb +1 -1
  78. data/lib/farce/engine/shared/unshared_vector.rb +11 -8
  79. data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
  80. data/lib/farce/engine/shared/vector.rb +2 -0
  81. data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
  82. data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
  83. data/lib/farce/engine/shared.rb +2 -0
  84. data/lib/farce/engine/truffleruby/map.rb +8 -6
  85. data/lib/farce/engine/truffleruby/native/counter.rb +0 -2
  86. data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
  87. data/lib/farce/engine/truffleruby/signal.rb +1 -1
  88. data/lib/farce/engine/truffleruby.rb +9 -0
  89. data/lib/farce/extension.rb +5 -3
  90. data/lib/farce/integrations/concurrent.rb +16 -3
  91. data/lib/farce/integrations/sorted_set.rb +1 -0
  92. data/lib/farce/internal/blocking_priority_queue.rb +4 -9
  93. data/lib/farce/internal/fake_ractor.rb +1 -1
  94. data/lib/farce/internal/flag_waiting.rb +1 -1
  95. data/lib/farce/internal/freeze.rb +1 -5
  96. data/lib/farce/internal/lease_initialization.rb +1 -1
  97. data/lib/farce/internal/lease_pool_state.rb +1 -1
  98. data/lib/farce/internal/lease_state.rb +2 -2
  99. data/lib/farce/internal/local_executor_methods.rb +3 -3
  100. data/lib/farce/internal/map_value_modes.rb +17 -9
  101. data/lib/farce/internal/pool_settings.rb +9 -0
  102. data/lib/farce/internal/pool_worker.rb +34 -21
  103. data/lib/farce/internal/reservation_waiting.rb +1 -1
  104. data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
  105. data/lib/farce/internal/weak_map_value_modes.rb +5 -0
  106. data/lib/farce/internal.rb +19 -8
  107. data/lib/farce/lazy.rb +3 -0
  108. data/lib/farce/lazy_ref.rb +3 -0
  109. data/lib/farce/local/counter.rb +4 -10
  110. data/lib/farce/local/lease.rb +2 -9
  111. data/lib/farce/local/lease_map.rb +2 -9
  112. data/lib/farce/local/pool.rb +38 -5
  113. data/lib/farce/local/publisher.rb +30 -0
  114. data/lib/farce/local/scheduler.rb +18 -5
  115. data/lib/farce/local/vector.rb +3 -2
  116. data/lib/farce/map.rb +8 -1
  117. data/lib/farce/memoize.rb +319 -0
  118. data/lib/farce/pool.rb +5 -0
  119. data/lib/farce/priority_queue.rb +1 -1
  120. data/lib/farce/publisher.rb +57 -0
  121. data/lib/farce/shareable.rb +3 -0
  122. data/lib/farce/signal.rb +1 -1
  123. data/lib/farce/strict/publisher.rb +31 -0
  124. data/lib/farce/strict/vector.rb +3 -2
  125. data/lib/farce/strict/weak_cache.rb +199 -0
  126. data/lib/farce/subscription/definitions.rb +27 -0
  127. data/lib/farce/subscription.rb +121 -0
  128. data/lib/farce/transaction/map.rb +35 -1
  129. data/lib/farce/unshared/pool.rb +1 -1
  130. data/lib/farce/unshared/publisher.rb +25 -0
  131. data/lib/farce/unshared/scheduler.rb +3 -3
  132. data/lib/farce/unshared/vector.rb +3 -2
  133. data/lib/farce/vector.rb +20 -16
  134. data/lib/farce/version.rb +1 -1
  135. data/lib/farce/walker/definitions.rb +31 -2
  136. data/lib/farce/walker/modification.rb +28 -14
  137. data/lib/farce/walker.rb +9 -1
  138. data/lib/farce/weak_atom.rb +5 -2
  139. data/lib/farce/weak_key_map.rb +1 -1
  140. data/lib/farce/weak_map.rb +1 -1
  141. data/lib/farce/weak_value_map.rb +1 -1
  142. data/lib/farce.rb +3 -1
  143. 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
- vector_initialize(int argc, VALUE *argv, VALUE self)
394
+ vector_normalize_source(VALUE klass, VALUE source)
385
395
  {
386
- VALUE source = Qnil;
387
- VALUE keywords = Qnil;
388
- VALUE identity = Qundef;
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
- rb_check_frozen(self);
394
- rb_scan_args(argc, argv, "01:", &source, &keywords);
395
- if (!NIL_P(keywords)) {
396
- rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
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 (!NIL_P(source)) {
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
- for (size_t index = 0; index < size; index++) {
417
- VALUE value = rb_ary_entry(source, (long)index);
418
- containers_check_shareable(value);
419
- RB_OBJ_WRITE(self, &vector->values[index], value);
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(void *pointer)
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 || rb_gc_handle_weak_references_alive_p(slot->key);
223
- bool value_alive = !map->weak_values || rb_gc_handle_weak_references_alive_p(slot->value);
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 self, VALUE key)
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
- weak_map_lock_for_key(map, key, operation.hash, &execution);
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
- RubyModule internal = (RubyModule) runtime.getModule("Farce").getConstantAt("Internal");
34
- RubyClass klass = internal.defineClassUnder("FiberScheduler", runtime.getObject(), FiberScheduler::new);
35
- klass.defineAnnotatedMethods(FiberScheduler.class);
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().getDoubleValue() * 1000));
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().getLongValue() != 0) return value;
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 final long capacity;
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(); }
@@ -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, &) = internal_atom.store_if_absent(timeout:, &)
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)