farce 0.4.1 → 0.5.0

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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -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 +31 -0
  14. data/docs/variants.md +9 -0
  15. data/ext/farce/farce.c +9 -0
  16. data/ext/farce/map.c +14 -3
  17. data/ext/farce/priority_queue.c +46 -1
  18. data/ext/farce/queue.c +141 -7
  19. data/ext/farce/vector.c +3 -3
  20. data/ext/farce/weak_map.c +12 -3
  21. data/ext/java/org/farce/PriorityQueue.java +18 -2
  22. data/lib/farce/abstract/concurrent_map.rb +122 -0
  23. data/lib/farce/abstract/counter.rb +35 -1
  24. data/lib/farce/abstract/executor.rb +30 -7
  25. data/lib/farce/abstract/flag.rb +12 -0
  26. data/lib/farce/abstract/pool.rb +158 -47
  27. data/lib/farce/abstract/priority_queue.rb +1 -1
  28. data/lib/farce/abstract/publisher.rb +189 -0
  29. data/lib/farce/abstract/queue.rb +29 -3
  30. data/lib/farce/abstract/scheduler.rb +41 -14
  31. data/lib/farce/abstract/timer_queue.rb +3 -3
  32. data/lib/farce/abstract/vector.rb +3 -1
  33. data/lib/farce/atom.rb +9 -2
  34. data/lib/farce/clock.rb +12 -0
  35. data/lib/farce/concurrent/count_down_latch.rb +37 -0
  36. data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
  37. data/lib/farce/concurrent/event.rb +60 -0
  38. data/lib/farce/concurrent/fiber_local_var.rb +31 -0
  39. data/lib/farce/concurrent/local_var.rb +65 -0
  40. data/lib/farce/concurrent/thread_local_var.rb +28 -0
  41. data/lib/farce/concurrent/tuple.rb +74 -0
  42. data/lib/farce/concurrent.rb +50 -0
  43. data/lib/farce/config.rb +112 -2
  44. data/lib/farce/counter.rb +0 -16
  45. data/lib/farce/engine/jruby/map.rb +8 -6
  46. data/lib/farce/engine/jruby/signal.rb +2 -2
  47. data/lib/farce/engine/jvm/farce.jar +0 -0
  48. data/lib/farce/engine/jvm/priority_queue.rb +14 -4
  49. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
  50. data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
  51. data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
  52. data/lib/farce/engine/shared/exchanger.rb +2 -2
  53. data/lib/farce/engine/shared/queue.rb +43 -5
  54. data/lib/farce/engine/shared/ractor_selector.rb +1 -1
  55. data/lib/farce/engine/shared/strict_map.rb +5 -0
  56. data/lib/farce/engine/shared/unshared_atom.rb +1 -1
  57. data/lib/farce/engine/shared/unshared_vector.rb +8 -7
  58. data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
  59. data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
  60. data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
  61. data/lib/farce/engine/truffleruby/map.rb +8 -6
  62. data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
  63. data/lib/farce/engine/truffleruby/signal.rb +1 -1
  64. data/lib/farce/integrations/concurrent.rb +16 -3
  65. data/lib/farce/internal/blocking_priority_queue.rb +4 -9
  66. data/lib/farce/internal/flag_waiting.rb +1 -1
  67. data/lib/farce/internal/lease_initialization.rb +1 -1
  68. data/lib/farce/internal/lease_pool_state.rb +1 -1
  69. data/lib/farce/internal/lease_state.rb +2 -2
  70. data/lib/farce/internal/local_executor_methods.rb +3 -3
  71. data/lib/farce/internal/map_value_modes.rb +9 -2
  72. data/lib/farce/internal/pool_settings.rb +9 -0
  73. data/lib/farce/internal/pool_worker.rb +34 -21
  74. data/lib/farce/internal/reservation_waiting.rb +1 -1
  75. data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
  76. data/lib/farce/internal/weak_map_value_modes.rb +5 -0
  77. data/lib/farce/internal.rb +8 -8
  78. data/lib/farce/lazy.rb +3 -0
  79. data/lib/farce/lazy_ref.rb +3 -0
  80. data/lib/farce/local/lease.rb +2 -9
  81. data/lib/farce/local/lease_map.rb +2 -9
  82. data/lib/farce/local/pool.rb +38 -5
  83. data/lib/farce/local/publisher.rb +30 -0
  84. data/lib/farce/local/scheduler.rb +18 -5
  85. data/lib/farce/local/vector.rb +3 -2
  86. data/lib/farce/memoize.rb +314 -0
  87. data/lib/farce/pool.rb +5 -0
  88. data/lib/farce/priority_queue.rb +1 -1
  89. data/lib/farce/publisher.rb +57 -0
  90. data/lib/farce/signal.rb +1 -1
  91. data/lib/farce/strict/publisher.rb +31 -0
  92. data/lib/farce/strict/vector.rb +3 -2
  93. data/lib/farce/subscription/definitions.rb +27 -0
  94. data/lib/farce/subscription.rb +121 -0
  95. data/lib/farce/transaction/map.rb +35 -1
  96. data/lib/farce/unshared/pool.rb +1 -1
  97. data/lib/farce/unshared/publisher.rb +25 -0
  98. data/lib/farce/unshared/scheduler.rb +3 -3
  99. data/lib/farce/unshared/vector.rb +3 -2
  100. data/lib/farce/vector.rb +12 -9
  101. data/lib/farce/version.rb +1 -1
  102. data/lib/farce/walker/definitions.rb +13 -2
  103. data/lib/farce/weak_atom.rb +5 -2
  104. data/lib/farce.rb +3 -1
  105. metadata +18 -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
  }
data/ext/farce/queue.c CHANGED
@@ -33,6 +33,8 @@ typedef struct {
33
33
  #endif
34
34
  } readiness_signal_t;
35
35
 
36
+ typedef struct queue_wait_context queue_wait_context_t;
37
+
36
38
  typedef struct {
37
39
  pthread_mutex_t lock;
38
40
  VALUE *values;
@@ -44,8 +46,14 @@ typedef struct {
44
46
  size_t tail;
45
47
  size_t pop_waiters;
46
48
  size_t push_waiters;
49
+ VALUE interrupt_token;
50
+ uint64_t interrupt_generation;
51
+ size_t interrupt_pending;
52
+ queue_wait_context_t *interrupt_waiters;
47
53
  readiness_signal_t can_pop;
48
54
  readiness_signal_t can_push;
55
+ readiness_signal_t interrupts_drained;
56
+ size_t interrupt_drain_waiters;
49
57
  farce_unshared_wait_list_t unshared_pop_waiters;
50
58
  farce_unshared_wait_list_t unshared_push_waiters;
51
59
  bool bounded;
@@ -160,20 +168,25 @@ queue_update_readiness(queue_t *queue)
160
168
  }
161
169
  readiness_set(
162
170
  &queue->can_pop,
163
- queue->pop_waiters > 0 && (queue->closed || queue->size > 0)
171
+ queue->pop_waiters > 0 && (queue->closed || queue->size > 0 || queue->interrupt_pending > 0)
164
172
  );
165
173
  readiness_set(
166
174
  &queue->can_push,
167
175
  queue->push_waiters > 0 &&
168
176
  (queue->sealed || !queue->bounded || queue->size < queue->limit)
169
177
  );
178
+ readiness_set(
179
+ &queue->interrupts_drained,
180
+ queue->interrupt_drain_waiters > 0 && (queue->closed || queue->interrupt_pending == 0)
181
+ );
170
182
  if (!queue->shared) {
171
183
  /* Keep the slow path until both waiter mechanisms and every pending
172
184
  * readiness byte have drained. Interrupted direct waits may leave the
173
185
  * flag set, which is harmless and corrected by the next mutation. */
174
186
  queue->unshared_notifications_needed = queue->pop_waiters || queue->push_waiters ||
175
187
  queue->unshared_pop_waiters.count || queue->unshared_push_waiters.count ||
176
- queue->can_pop.set || queue->can_push.set;
188
+ queue->can_pop.set || queue->can_push.set ||
189
+ queue->interrupt_drain_waiters || queue->interrupts_drained.set;
177
190
  }
178
191
  }
179
192
 
@@ -181,6 +194,7 @@ static void
181
194
  queue_mark(void *pointer)
182
195
  {
183
196
  queue_t *queue = pointer;
197
+ rb_gc_mark_movable(queue->interrupt_token);
184
198
  farce_unshared_wait_mark(&queue->unshared_pop_waiters);
185
199
  farce_unshared_wait_mark(&queue->unshared_push_waiters);
186
200
  if (!queue->values) return;
@@ -195,6 +209,7 @@ static void
195
209
  queue_compact(void *pointer)
196
210
  {
197
211
  queue_t *queue = pointer;
212
+ containers_gc_update(&queue->interrupt_token);
198
213
  if (!queue->values) return;
199
214
  size_t index = queue->head;
200
215
  for (size_t remaining = queue->size; remaining > 0; remaining--) {
@@ -212,6 +227,7 @@ queue_free(void *pointer)
212
227
  free(queue->enqueued_at);
213
228
  readiness_close(&queue->can_pop);
214
229
  readiness_close(&queue->can_push);
230
+ readiness_close(&queue->interrupts_drained);
215
231
  ruby_xfree(queue);
216
232
  }
217
233
 
@@ -277,8 +293,14 @@ queue_allocate_type(VALUE klass, bool shared)
277
293
  queue->tail = 0;
278
294
  queue->pop_waiters = 0;
279
295
  queue->push_waiters = 0;
296
+ queue->interrupt_token = Qnil;
297
+ queue->interrupt_generation = 0;
298
+ queue->interrupt_pending = 0;
299
+ queue->interrupt_waiters = NULL;
280
300
  queue->can_pop = (readiness_signal_t){.read_fd = -1, .write_fd = -1, .set = false};
281
301
  queue->can_push = (readiness_signal_t){.read_fd = -1, .write_fd = -1, .set = false};
302
+ queue->interrupts_drained = (readiness_signal_t){.read_fd = -1, .write_fd = -1, .set = false};
303
+ queue->interrupt_drain_waiters = 0;
282
304
  queue->unshared_pop_waiters = (farce_unshared_wait_list_t){0};
283
305
  queue->unshared_push_waiters = (farce_unshared_wait_list_t){0};
284
306
  queue->bounded = true;
@@ -384,6 +406,8 @@ typedef struct {
384
406
  bool finite;
385
407
  bool collected;
386
408
  double deadline;
409
+ bool interruptible;
410
+ VALUE token;
387
411
  } queue_timeout_t;
388
412
 
389
413
  static double
@@ -449,13 +473,44 @@ queue_wait_for_descriptor(int fd, queue_timeout_t *timeout)
449
473
  return containers_wait_for_readable(fd, wait_timeout);
450
474
  }
451
475
 
452
- typedef struct {
476
+ struct queue_wait_context {
453
477
  queue_t *queue;
454
478
  readiness_signal_t *signal;
455
479
  size_t *waiter_count;
456
480
  queue_timeout_t *timeout;
457
481
  int wait_fd;
458
- } queue_wait_context_t;
482
+ bool linked;
483
+ uint64_t generation;
484
+ queue_wait_context_t *previous;
485
+ queue_wait_context_t *next;
486
+ };
487
+
488
+ /* Keep a wakeup readable until every interrupted descriptor waiter has left.
489
+ * Registration and removal happen under the same queue lock as publication. */
490
+ static void
491
+ queue_register_interrupt_waiter(queue_wait_context_t *context)
492
+ {
493
+ if (!context->timeout->interruptible || context->signal != &context->queue->can_pop) return;
494
+ queue_t *queue = context->queue;
495
+ context->generation = queue->interrupt_generation;
496
+ context->previous = NULL;
497
+ context->next = queue->interrupt_waiters;
498
+ if (context->next) context->next->previous = context;
499
+ queue->interrupt_waiters = context;
500
+ context->linked = true;
501
+ }
502
+
503
+ static void
504
+ queue_remove_interrupt_waiter(queue_wait_context_t *context)
505
+ {
506
+ if (!context->linked) return;
507
+ queue_t *queue = context->queue;
508
+ if (context->previous) context->previous->next = context->next;
509
+ else queue->interrupt_waiters = context->next;
510
+ if (context->next) context->next->previous = context->previous;
511
+ if (context->generation != queue->interrupt_generation) queue->interrupt_pending--;
512
+ context->linked = false;
513
+ }
459
514
 
460
515
  static VALUE
461
516
  queue_wait_body(VALUE opaque)
@@ -527,6 +582,7 @@ queue_wait_cleanup(VALUE opaque)
527
582
  queue_wait_context_t *context = (queue_wait_context_t *)opaque;
528
583
  if (context->wait_fd >= 0) close(context->wait_fd);
529
584
  queue_lock(context->queue);
585
+ queue_remove_interrupt_waiter(context);
530
586
  (*context->waiter_count)--;
531
587
  queue_update_readiness(context->queue);
532
588
  if (context->queue->closed && *context->waiter_count == 0) {
@@ -588,8 +644,16 @@ queue_wait(VALUE self, queue_t *queue, readiness_signal_t *signal, size_t *waite
588
644
  queue_unlock(queue);
589
645
  return false;
590
646
  }
647
+ /* A fresh wait must not repeatedly consume readiness reserved for older
648
+ * interrupted fibers on the same scheduler. Wait for their acknowledgments
649
+ * on a separate signal, without joining the interrupted generation. */
650
+ if (signal == &queue->can_pop && queue->interrupt_pending > 0) {
651
+ signal = &queue->interrupts_drained;
652
+ waiter_count = &queue->interrupt_drain_waiters;
653
+ }
591
654
  if (!queue->shared) queue->unshared_notifications_needed = true;
592
- if (!queue->shared && (!queue->unshared_fiber_io || NIL_P(rb_fiber_scheduler_current()))) {
655
+ if (signal != &queue->interrupts_drained && !queue->shared &&
656
+ (!queue->unshared_fiber_io || NIL_P(rb_fiber_scheduler_current()))) {
593
657
  farce_unshared_wait_list_t *list = signal == &queue->can_pop ?
594
658
  &queue->unshared_pop_waiters : &queue->unshared_push_waiters;
595
659
  return farce_unshared_wait(list, self, timeout->finite, timeout->deadline);
@@ -611,6 +675,7 @@ queue_wait(VALUE self, queue_t *queue, readiness_signal_t *signal, size_t *waite
611
675
  .timeout = timeout,
612
676
  .wait_fd = -1,
613
677
  };
678
+ queue_register_interrupt_waiter(&context);
614
679
  (*waiter_count)++;
615
680
  queue_update_readiness(queue);
616
681
  queue_unlock(queue);
@@ -632,6 +697,7 @@ queue_wait(VALUE self, queue_t *queue, readiness_signal_t *signal, size_t *waite
632
697
  .timeout = timeout,
633
698
  .wait_fd = wait_fd,
634
699
  };
700
+ queue_register_interrupt_waiter(&context);
635
701
  (*waiter_count)++;
636
702
  queue_update_readiness(queue);
637
703
  queue_unlock(queue);
@@ -664,7 +730,54 @@ static VALUE
664
730
  queue_capacity(VALUE self)
665
731
  {
666
732
  queue_t *queue = get_queue(self);
667
- return queue->bounded ? SIZET2NUM(queue->limit) : Qnil;
733
+ queue_lock(queue);
734
+ bool bounded = queue->bounded;
735
+ size_t limit = queue->limit;
736
+ queue_unlock(queue);
737
+ return bounded ? SIZET2NUM(limit) : Qnil;
738
+ }
739
+
740
+ static VALUE
741
+ queue_set_capacity(VALUE self, VALUE value)
742
+ {
743
+ size_t limit = 0;
744
+ if (!NIL_P(value)) {
745
+ long long converted = NUM2LL(rb_to_int(value));
746
+ if (converted <= 0) rb_raise(rb_eArgError, "capacity must be positive or nil");
747
+ if ((unsigned long long)converted > SIZE_MAX / sizeof(VALUE)) rb_raise(rb_eArgError, "capacity is too large");
748
+ limit = (size_t)converted;
749
+ }
750
+ queue_t *queue = get_queue(self);
751
+ queue_lock(queue);
752
+ queue->bounded = !NIL_P(value);
753
+ queue->limit = limit;
754
+ queue_update_readiness(queue);
755
+ queue_unlock(queue);
756
+ return value;
757
+ }
758
+
759
+ static VALUE
760
+ queue_interrupt(VALUE self, VALUE token, VALUE generation_value)
761
+ {
762
+ queue_t *queue = get_queue(self);
763
+ if (queue->shared) containers_check_shareable(token);
764
+ uint64_t generation = NUM2ULL(generation_value);
765
+ queue_lock(queue);
766
+ /* Concurrent writers can finish their notifications out of order. */
767
+ if (!NIL_P(queue->interrupt_token) && generation <= queue->interrupt_generation) {
768
+ queue_unlock(queue);
769
+ return self;
770
+ }
771
+ RB_OBJ_WRITE(self, &queue->interrupt_token, token);
772
+ queue->interrupt_generation = generation;
773
+ queue->interrupt_pending = 0;
774
+ for (queue_wait_context_t *waiter = queue->interrupt_waiters; waiter; waiter = waiter->next) {
775
+ if (waiter->generation != generation) queue->interrupt_pending++;
776
+ }
777
+ if (!queue->shared) farce_unshared_wait_notify_all(&queue->unshared_pop_waiters);
778
+ queue_update_readiness(queue);
779
+ queue_unlock(queue);
780
+ return self;
668
781
  }
669
782
 
670
783
  /* Called with queue->lock held. The queue is unchanged if allocation fails.
@@ -719,7 +832,7 @@ queue_num_waiting(VALUE self)
719
832
  queue_t *queue = get_queue(self);
720
833
  size_t num_waiting;
721
834
  queue_lock(queue);
722
- num_waiting = queue->pop_waiters + queue->push_waiters +
835
+ num_waiting = queue->pop_waiters + queue->push_waiters + queue->interrupt_drain_waiters +
723
836
  queue->unshared_pop_waiters.count + queue->unshared_push_waiters.count;
724
837
  queue_unlock(queue);
725
838
  return SIZET2NUM(num_waiting);
@@ -848,6 +961,7 @@ queue_close(VALUE self)
848
961
  queue_update_readiness(queue);
849
962
  if (queue->pop_waiters == 0) readiness_close(&queue->can_pop);
850
963
  if (queue->push_waiters == 0) readiness_close(&queue->can_push);
964
+ if (queue->interrupt_drain_waiters == 0) readiness_close(&queue->interrupts_drained);
851
965
  }
852
966
  queue_unlock(queue);
853
967
  return self;
@@ -862,6 +976,7 @@ queue_release_wait_descriptors(VALUE self)
862
976
  queue_lock(queue);
863
977
  if (queue->pop_waiters == 0) readiness_close(&queue->can_pop);
864
978
  if (queue->push_waiters == 0) readiness_close(&queue->can_push);
979
+ if (queue->interrupt_drain_waiters == 0) readiness_close(&queue->interrupts_drained);
865
980
  queue_unlock(queue);
866
981
  return self;
867
982
  }
@@ -871,6 +986,10 @@ queue_pop_with_timeout(VALUE self, queue_t *queue, queue_timeout_t timeout)
871
986
  {
872
987
  for (;;) {
873
988
  queue_lock(queue);
989
+ if (timeout.interruptible && timeout.token != queue->interrupt_token) {
990
+ queue_unlock(queue);
991
+ return Qfalse;
992
+ }
874
993
  if (queue->closed) {
875
994
  queue_unlock(queue);
876
995
  raise_queue_closed();
@@ -902,6 +1021,18 @@ queue_pop(int argc, VALUE *argv, VALUE self)
902
1021
  return queue_pop_with_timeout(self, queue, timeout);
903
1022
  }
904
1023
 
1024
+ /* Pool dispatch uses positional arguments to avoid another keyword hash. */
1025
+ static VALUE
1026
+ queue_pop_interruptible(VALUE self, VALUE token, VALUE timeout_value)
1027
+ {
1028
+ queue_timeout_t timeout = parse_timeout(timeout_value);
1029
+ timeout.interruptible = true;
1030
+ timeout.token = token;
1031
+ VALUE result = queue_pop_with_timeout(self, get_queue(self), timeout);
1032
+ RB_GC_GUARD(token);
1033
+ return result;
1034
+ }
1035
+
905
1036
  static VALUE
906
1037
  queue_try_pop(VALUE self)
907
1038
  {
@@ -1042,6 +1173,9 @@ containers_init_queue(VALUE namespace)
1042
1173
  rb_define_method(unshared, "fiber_wait", unshared_queue_fiber_wait, 0);
1043
1174
  rb_define_method(cQueue, "initialize", queue_initialize, -1);
1044
1175
  rb_define_method(cQueue, "capacity", queue_capacity, 0);
1176
+ rb_define_method(cQueue, "capacity=", queue_set_capacity, 1);
1177
+ rb_define_method(cQueue, "interrupt", queue_interrupt, 2);
1178
+ rb_define_method(cQueue, "pop_interruptible", queue_pop_interruptible, 2);
1045
1179
  rb_define_method(cQueue, "size", queue_size, 0);
1046
1180
  rb_define_method(cQueue, "num_waiting", queue_num_waiting, 0);
1047
1181
  rb_define_method(cQueue, "clear", queue_clear, 0);
data/ext/farce/vector.c CHANGED
@@ -1002,7 +1002,7 @@ vector_wait_until_changed(int argc, VALUE *argv, VALUE self)
1002
1002
  uint64_t generation;
1003
1003
  size_t index;
1004
1004
  pthread_mutex_lock(&vector->lock);
1005
- if (!vector_wait_for_reservation_locked(vector, &timeout)) return Qnil;
1005
+ if (!vector_wait_for_reservation_locked(vector, &timeout)) return rb_block_given_p() ? rb_yield(Qnil) : Qnil;
1006
1006
  VALUE current = vector_lookup_index(vector, raw, &index) ? vector->values[index] : Qnil;
1007
1007
  generation = vector->generation;
1008
1008
  pthread_mutex_unlock(&vector->lock);
@@ -1010,12 +1010,12 @@ vector_wait_until_changed(int argc, VALUE *argv, VALUE self)
1010
1010
  if (!vector_values_equal(vector, current, expected)) return current;
1011
1011
 
1012
1012
  pthread_mutex_lock(&vector->lock);
1013
- if (!vector_wait_for_reservation_locked(vector, &timeout)) return Qnil;
1013
+ if (!vector_wait_for_reservation_locked(vector, &timeout)) return rb_block_given_p() ? rb_yield(Qnil) : Qnil;
1014
1014
  if (vector->generation != generation) {
1015
1015
  pthread_mutex_unlock(&vector->lock);
1016
1016
  continue;
1017
1017
  }
1018
- if (!vector_wait_once(vector, &timeout)) return Qnil;
1018
+ if (!vector_wait_once(vector, &timeout)) return rb_block_given_p() ? rb_yield(Qnil) : Qnil;
1019
1019
  }
1020
1020
  }
1021
1021
 
data/ext/farce/weak_map.c CHANGED
@@ -1173,9 +1173,15 @@ weak_map_modify_body(VALUE opaque)
1173
1173
  }
1174
1174
 
1175
1175
  static VALUE
1176
- weak_map_modify(VALUE self, VALUE key)
1176
+ weak_map_modify(int argc, VALUE *argv, VALUE self)
1177
1177
  {
1178
1178
  rb_check_frozen(self);
1179
+ VALUE key;
1180
+ VALUE keywords = Qnil;
1181
+ VALUE keyword_values[1] = {Qundef};
1182
+ ID keyword_ids[] = {rb_intern("timeout")};
1183
+ rb_scan_args(argc, argv, "1:", &key, &keywords);
1184
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1179
1185
  rb_need_block();
1180
1186
  weak_map_t *map = get_weak_map(self);
1181
1187
  key = weak_map_prepare_key(map, key);
@@ -1187,8 +1193,11 @@ weak_map_modify(VALUE self, VALUE key)
1187
1193
  };
1188
1194
  operation.hash = weak_map_key_hash(map, key);
1189
1195
  rb_check_frozen(self);
1196
+ weak_map_timeout_t timeout = weak_map_parse_timeout(
1197
+ keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1198
+ );
1190
1199
  weak_map_execution_context_t execution = {.self = self};
1191
- weak_map_lock_for_key(map, key, operation.hash, &execution);
1200
+ if (!weak_map_lock_for_key_with_timeout(map, key, operation.hash, &timeout, &execution)) return Qfalse;
1192
1201
  bool found;
1193
1202
  size_t index = weak_map_find_slot(map, key, operation.hash, &found, &execution);
1194
1203
  if (RB_OBJ_FROZEN(self)) {
@@ -1675,7 +1684,7 @@ define_weak_map_methods(VALUE klass)
1675
1684
  rb_define_method(klass, "store", weak_map_store_with_timeout, -1);
1676
1685
  rb_define_method(klass, "swap", weak_map_swap, -1);
1677
1686
  rb_define_method(klass, "update", weak_map_update, -1);
1678
- rb_define_method(klass, "modify", weak_map_modify, 1);
1687
+ rb_define_method(klass, "modify", weak_map_modify, -1);
1679
1688
  rb_define_method(klass, "wait_until_changed", weak_map_wait_until_changed, -1);
1680
1689
  rb_define_method(klass, "wait_until_non_nil", weak_map_wait_until_non_nil, -1);
1681
1690
  rb_define_method(klass, "store_if_absent", weak_map_store_if_absent, -1);
@@ -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(); }
@@ -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)