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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +80 -2
- data/README.md +3 -1
- data/docs/benchmarks.md +37 -21
- data/docs/compatibility.md +13 -0
- data/docs/data-structures.md +37 -0
- data/docs/integrations.md +1 -0
- data/docs/introduction.md +19 -0
- data/docs/modes.md +2 -0
- data/docs/scheduling.md +35 -3
- data/docs/scopes.md +3 -1
- data/docs/shims.md +53 -0
- data/docs/utilities.md +31 -0
- data/docs/variants.md +9 -0
- data/ext/farce/farce.c +9 -0
- data/ext/farce/map.c +14 -3
- data/ext/farce/priority_queue.c +46 -1
- data/ext/farce/queue.c +141 -7
- data/ext/farce/vector.c +3 -3
- data/ext/farce/weak_map.c +12 -3
- data/ext/java/org/farce/PriorityQueue.java +18 -2
- data/lib/farce/abstract/concurrent_map.rb +122 -0
- data/lib/farce/abstract/counter.rb +35 -1
- data/lib/farce/abstract/executor.rb +30 -7
- data/lib/farce/abstract/flag.rb +12 -0
- data/lib/farce/abstract/pool.rb +158 -47
- data/lib/farce/abstract/priority_queue.rb +1 -1
- data/lib/farce/abstract/publisher.rb +189 -0
- data/lib/farce/abstract/queue.rb +29 -3
- data/lib/farce/abstract/scheduler.rb +41 -14
- data/lib/farce/abstract/timer_queue.rb +3 -3
- data/lib/farce/abstract/vector.rb +3 -1
- data/lib/farce/atom.rb +9 -2
- data/lib/farce/clock.rb +12 -0
- data/lib/farce/concurrent/count_down_latch.rb +37 -0
- data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
- data/lib/farce/concurrent/event.rb +60 -0
- data/lib/farce/concurrent/fiber_local_var.rb +31 -0
- data/lib/farce/concurrent/local_var.rb +65 -0
- data/lib/farce/concurrent/thread_local_var.rb +28 -0
- data/lib/farce/concurrent/tuple.rb +74 -0
- data/lib/farce/concurrent.rb +50 -0
- data/lib/farce/config.rb +112 -2
- data/lib/farce/counter.rb +0 -16
- data/lib/farce/engine/jruby/map.rb +8 -6
- data/lib/farce/engine/jruby/signal.rb +2 -2
- data/lib/farce/engine/jvm/farce.jar +0 -0
- data/lib/farce/engine/jvm/priority_queue.rb +14 -4
- data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
- data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
- data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
- data/lib/farce/engine/shared/exchanger.rb +2 -2
- data/lib/farce/engine/shared/queue.rb +43 -5
- data/lib/farce/engine/shared/ractor_selector.rb +1 -1
- data/lib/farce/engine/shared/strict_map.rb +5 -0
- data/lib/farce/engine/shared/unshared_atom.rb +1 -1
- data/lib/farce/engine/shared/unshared_vector.rb +8 -7
- data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
- data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
- data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
- data/lib/farce/engine/truffleruby/map.rb +8 -6
- data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
- data/lib/farce/engine/truffleruby/signal.rb +1 -1
- data/lib/farce/integrations/concurrent.rb +16 -3
- data/lib/farce/internal/blocking_priority_queue.rb +4 -9
- data/lib/farce/internal/flag_waiting.rb +1 -1
- data/lib/farce/internal/lease_initialization.rb +1 -1
- data/lib/farce/internal/lease_pool_state.rb +1 -1
- data/lib/farce/internal/lease_state.rb +2 -2
- data/lib/farce/internal/local_executor_methods.rb +3 -3
- data/lib/farce/internal/map_value_modes.rb +9 -2
- data/lib/farce/internal/pool_settings.rb +9 -0
- data/lib/farce/internal/pool_worker.rb +34 -21
- data/lib/farce/internal/reservation_waiting.rb +1 -1
- data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
- data/lib/farce/internal/weak_map_value_modes.rb +5 -0
- data/lib/farce/internal.rb +8 -8
- data/lib/farce/lazy.rb +3 -0
- data/lib/farce/lazy_ref.rb +3 -0
- data/lib/farce/local/lease.rb +2 -9
- data/lib/farce/local/lease_map.rb +2 -9
- data/lib/farce/local/pool.rb +38 -5
- data/lib/farce/local/publisher.rb +30 -0
- data/lib/farce/local/scheduler.rb +18 -5
- data/lib/farce/local/vector.rb +3 -2
- data/lib/farce/memoize.rb +314 -0
- data/lib/farce/pool.rb +5 -0
- data/lib/farce/priority_queue.rb +1 -1
- data/lib/farce/publisher.rb +57 -0
- data/lib/farce/signal.rb +1 -1
- data/lib/farce/strict/publisher.rb +31 -0
- data/lib/farce/strict/vector.rb +3 -2
- data/lib/farce/subscription/definitions.rb +27 -0
- data/lib/farce/subscription.rb +121 -0
- data/lib/farce/transaction/map.rb +35 -1
- data/lib/farce/unshared/pool.rb +1 -1
- data/lib/farce/unshared/publisher.rb +25 -0
- data/lib/farce/unshared/scheduler.rb +3 -3
- data/lib/farce/unshared/vector.rb +3 -2
- data/lib/farce/vector.rb +12 -9
- data/lib/farce/version.rb +1 -1
- data/lib/farce/walker/definitions.rb +13 -2
- data/lib/farce/weak_atom.rb +5 -2
- data/lib/farce.rb +3 -1
- metadata +18 -1
data/ext/farce/priority_queue.c
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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)
|