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
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(void *pointer)
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 (rb_gc_handle_weak_references_alive_p(atom->value)) return;
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,
@@ -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
- /* The chainable operations are inherited directly by Farce::Counter. Keep
342
- * add/subtract as strict, value-returning primitives for internal callers. */
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
- (void)counter_change(counter, delta, false);
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
- (void)counter_change(counter, delta, true);
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
- weak_callback = try_compile(<<~SOURCE)
107
- #include "ruby.h"
108
-
109
- static void handle_weak_references(void *pointer) { (void)pointer; }
110
- static const rb_data_type_t probe_type = {
111
- .wrap_struct_name = "weak-reference-probe",
112
- .function = {
113
- .handle_weak_references = handle_weak_references,
114
- },
115
- };
116
-
117
- int main(void) { return probe_type.function.handle_weak_references == 0; }
118
- SOURCE
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
- $defs << "-DRC_HAVE_NATIVE_WEAK_REFERENCES=1" if weak_callback && weak_declare && weak_alive
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
- VALUE instance_variables = rb_obj_instance_variables(self);
57
- if (RARRAY_LEN(instance_variables) != 0) {
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
@@ -306,6 +340,14 @@ containers_raise_key_error(VALUE receiver, VALUE key)
306
340
  rb_exc_raise(exception);
307
341
  }
308
342
 
343
+ static VALUE
344
+ farce_value_changed(VALUE namespace, VALUE current, VALUE expected, VALUE identity)
345
+ {
346
+ if (current == expected) return Qfalse;
347
+ if (RTEST(identity)) return identity;
348
+ return RTEST(rb_equal(current, expected)) ? Qfalse : Qtrue;
349
+ }
350
+
309
351
  static VALUE
310
352
  farce_futex_supported(VALUE namespace)
311
353
  {
@@ -319,6 +361,63 @@ farce_load_native_fiber_scheduler(VALUE namespace)
319
361
  return Qnil;
320
362
  }
321
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
+
322
421
  #if RUBY_API_VERSION_CODE == 30400
323
422
  static VALUE
324
423
  farce_lock_native_thread(VALUE namespace)
@@ -336,6 +435,14 @@ Init_farce(void)
336
435
  VALUE mInternal = rb_const_get(mFarce, rb_intern("Internal"));
337
436
  VALUE singleton = rb_singleton_class(mInternal);
338
437
  eIsolationError = rb_const_get(rb_cRactor, rb_intern("IsolationError"));
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);
339
446
  #if RUBY_API_VERSION_CODE == 30400
340
447
  rb_define_singleton_method(mInternal, "lock_native_thread", farce_lock_native_thread, 0);
341
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
- rb_scan_args(argc, argv, "01:", &mapping, &keywords);
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
 
@@ -1417,9 +1414,15 @@ map_modify_body(VALUE opaque)
1417
1414
  }
1418
1415
 
1419
1416
  static VALUE
1420
- map_modify(VALUE self, VALUE key)
1417
+ map_modify(int argc, VALUE *argv, VALUE self)
1421
1418
  {
1422
1419
  rb_check_frozen(self);
1420
+ VALUE key;
1421
+ VALUE keywords = Qnil;
1422
+ VALUE keyword_values[1] = {Qundef};
1423
+ ID keyword_ids[] = {rb_intern("timeout")};
1424
+ rb_scan_args(argc, argv, "1:", &key, &keywords);
1425
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1423
1426
  rb_need_block();
1424
1427
  map_t *map = get_map(self);
1425
1428
  key = map_prepare_key(map, key);
@@ -1432,7 +1435,12 @@ map_modify(VALUE self, VALUE key)
1432
1435
  };
1433
1436
  operation.hash = map_key_hash(map, key);
1434
1437
  rb_check_frozen(self);
1435
- map_lock_for_key(map, key, operation.hash, &operation.execution);
1438
+ map_timeout_t timeout = map_parse_timeout(
1439
+ keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1440
+ );
1441
+ if (!map_lock_for_key_with_timeout(
1442
+ map, key, operation.hash, &timeout, &operation.execution
1443
+ )) return Qfalse;
1436
1444
  bool found;
1437
1445
  size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
1438
1446
  if (RB_OBJ_FROZEN(self)) {
@@ -2151,7 +2159,7 @@ map_define_methods(VALUE klass)
2151
2159
  rb_define_method(klass, "store", map_store_with_timeout, -1);
2152
2160
  rb_define_method(klass, "swap", map_swap, -1);
2153
2161
  rb_define_method(klass, "update", map_update, -1);
2154
- rb_define_method(klass, "modify", map_modify, 1);
2162
+ rb_define_method(klass, "modify", map_modify, -1);
2155
2163
  rb_define_method(klass, "wait_until_changed", map_wait_until_changed, -1);
2156
2164
  rb_define_method(klass, "wait_until_non_nil", map_wait_until_non_nil, -1);
2157
2165
  rb_define_method(klass, "store_if_absent", map_store_if_absent, -1);
@@ -1964,11 +1964,55 @@ priority_queue_oldest_age(VALUE self)
1964
1964
  return priority_queue_call(self, queue, priority_queue_oldest_age_body, (VALUE)&operation);
1965
1965
  }
1966
1966
 
1967
+ static VALUE
1968
+ priority_queue_capacity_body(VALUE opaque)
1969
+ {
1970
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1971
+ return operation->queue->bounded ? SIZET2NUM(operation->queue->capacity) : Qnil;
1972
+ }
1973
+
1967
1974
  static VALUE
1968
1975
  priority_queue_capacity(VALUE self)
1969
1976
  {
1970
1977
  priority_queue_t *queue = priority_queue_get(self);
1971
- return queue->bounded ? SIZET2NUM(queue->capacity) : Qnil;
1978
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1979
+ if (priority_queue_can_run_direct(queue)) return priority_queue_capacity_body((VALUE)&operation);
1980
+ return priority_queue_call(self, queue, priority_queue_capacity_body, (VALUE)&operation);
1981
+ }
1982
+
1983
+ typedef struct {
1984
+ priority_queue_t *queue;
1985
+ VALUE value;
1986
+ size_t capacity;
1987
+ bool bounded;
1988
+ } priority_queue_resize_t;
1989
+
1990
+ static VALUE
1991
+ priority_queue_set_capacity_body(VALUE opaque)
1992
+ {
1993
+ priority_queue_resize_t *resize = (priority_queue_resize_t *)opaque;
1994
+ priority_queue_notify_before_commit(resize->queue);
1995
+ resize->queue->capacity = resize->capacity;
1996
+ resize->queue->bounded = resize->bounded;
1997
+ return resize->value;
1998
+ }
1999
+
2000
+ static VALUE
2001
+ priority_queue_set_capacity(VALUE self, VALUE value)
2002
+ {
2003
+ priority_queue_t *queue = priority_queue_get(self);
2004
+ priority_queue_resize_t resize = {.queue = queue, .value = value, .capacity = 0, .bounded = !NIL_P(value)};
2005
+ if (resize.bounded) {
2006
+ resize.value = rb_to_int(value);
2007
+ long long capacity = NUM2LL(resize.value);
2008
+ if (capacity <= 0) rb_raise(rb_eArgError, "capacity must be positive or nil");
2009
+ if ((unsigned long long)capacity > (unsigned long long)SIZE_MAX) {
2010
+ rb_raise(rb_eArgError, "capacity is too large");
2011
+ }
2012
+ resize.capacity = (size_t)capacity;
2013
+ }
2014
+ if (priority_queue_can_run_direct(queue)) return priority_queue_set_capacity_body((VALUE)&resize);
2015
+ return priority_queue_call(self, queue, priority_queue_set_capacity_body, (VALUE)&resize);
1972
2016
  }
1973
2017
 
1974
2018
  static void
@@ -1999,6 +2043,7 @@ priority_queue_define_methods(VALUE klass)
1999
2043
  rb_define_method(klass, "oldest_enqueued_at", priority_queue_oldest_enqueued_at, 0);
2000
2044
  rb_define_method(klass, "oldest_age", priority_queue_oldest_age, 0);
2001
2045
  rb_define_method(klass, "capacity", priority_queue_capacity, 0);
2046
+ rb_define_method(klass, "capacity=", priority_queue_set_capacity, 1);
2002
2047
  rb_define_method(klass, "freeze", priority_queue_freeze, 0);
2003
2048
  rb_define_method(klass, "frozen?", priority_queue_frozen_p, 0);
2004
2049
  }