farce 0.4.0-x86-linux-gnu → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +85 -1
- 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/LIBURING-LICENSE +20 -0
- data/ext/farce/atom.c +23 -66
- data/ext/farce/counter.c +1 -1
- data/ext/farce/depend +15 -7
- data/ext/farce/drivers.c +44 -2
- data/ext/farce/exchanger.c +1 -1
- data/ext/farce/extconf.rb +45 -2
- data/ext/farce/farce.c +17 -0
- data/ext/farce/flag.c +1 -1
- data/ext/farce/futex.c +76 -0
- data/ext/farce/futex.h +31 -0
- data/ext/farce/lock.c +16 -62
- data/ext/farce/map.c +24 -57
- data/ext/farce/native_wait.c +139 -0
- data/ext/farce/native_wait.h +37 -0
- data/ext/farce/priority_queue.c +62 -59
- data/ext/farce/queue.c +142 -8
- data/ext/farce/reactor.c +100 -11
- data/ext/farce/reactor.h +6 -2
- data/ext/farce/signal.c +17 -64
- data/ext/farce/tree_map.c +15 -54
- data/ext/farce/unshareable.c +1 -1
- data/ext/farce/unsupported.c +13 -1
- data/ext/farce/vector.c +16 -63
- data/ext/farce/weak_map.c +13 -4
- 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/3.4/farce.so +0 -0
- data/lib/farce/engine/ruby/4.0/farce.so +0 -0
- data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
- data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
- data/lib/farce/engine/ruby/shared/weak_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 +23 -1
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
|
|
|
@@ -224,7 +240,7 @@ queue_memsize(const void *pointer)
|
|
|
224
240
|
}
|
|
225
241
|
|
|
226
242
|
static const rb_data_type_t queue_type = {
|
|
227
|
-
.wrap_struct_name = "
|
|
243
|
+
.wrap_struct_name = "Farce::Internal::Queue",
|
|
228
244
|
.function = {
|
|
229
245
|
.dmark = queue_mark,
|
|
230
246
|
.dfree = queue_free,
|
|
@@ -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/reactor.c
CHANGED
|
@@ -345,6 +345,9 @@ void fs_watch_io(fs_state *s, fs_wait *w, int fd, int events, int group, long in
|
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
// Cancel the outstanding futex operation before its caller releases the word.
|
|
349
|
+
static void fs_cancel_futex(VALUE self, fs_wait *w);
|
|
350
|
+
|
|
348
351
|
// Finish a wait and remove its watches and ready-queue entry.
|
|
349
352
|
// A pending receive must stop using the buffer before cleanup can continue.
|
|
350
353
|
// Clear Ruby references and keep the fiber's slot available for its next wait.
|
|
@@ -352,6 +355,12 @@ VALUE fs_retire(VALUE self, VALUE token) {
|
|
|
352
355
|
fs_state *s = fs_get(self); fs_wait *w = fs_lookup(s, token);
|
|
353
356
|
if (!w) return Qnil;
|
|
354
357
|
if (w->receive_pending) fs_cancel_receive(self, w);
|
|
358
|
+
if (w->futex_pending) {
|
|
359
|
+
// Logical cancellation must not discard the lookup required by the CQE.
|
|
360
|
+
// Suppress resumption first, then consume the original completion.
|
|
361
|
+
w->status = 2;
|
|
362
|
+
fs_cancel_futex(self, w);
|
|
363
|
+
}
|
|
355
364
|
ready_remove(s, w);
|
|
356
365
|
fs_watch *watch = w->watches;
|
|
357
366
|
while (watch) {
|
|
@@ -381,6 +390,56 @@ VALUE fs_retire(VALUE self, VALUE token) {
|
|
|
381
390
|
return Qnil;
|
|
382
391
|
}
|
|
383
392
|
|
|
393
|
+
// Select asynchronous futex parking only for an installed, live native ring.
|
|
394
|
+
// External schedulers require a later integration instead of a duck-typed hook.
|
|
395
|
+
bool containers_futex_scheduler_supported(VALUE scheduler) {
|
|
396
|
+
#ifdef FARCE_HAVE_URING_FUTEX
|
|
397
|
+
if (NIL_P(scheduler) || !rb_typeddata_is_kind_of(scheduler, &fs_type)) return false;
|
|
398
|
+
fs_state *s = fs_get(scheduler);
|
|
399
|
+
return s->driver == FS_URING_DRIVER && s->ring_live && s->futex_supported &&
|
|
400
|
+
!s->closed && !s->stopping && s->owner == rb_thread_current() &&
|
|
401
|
+
s->active == rb_fiber_current() && rb_fiber_scheduler_get() == scheduler &&
|
|
402
|
+
containers_futex_available();
|
|
403
|
+
#else
|
|
404
|
+
return false;
|
|
405
|
+
#endif
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
typedef struct {
|
|
409
|
+
VALUE self, token, timeout;
|
|
410
|
+
containers_futex_t *word;
|
|
411
|
+
} fs_futex_wait;
|
|
412
|
+
|
|
413
|
+
// Submit and park using one suspension. The surrounding ensure retains the word
|
|
414
|
+
// until the kernel request has completed, even if timer setup or parking raises.
|
|
415
|
+
static VALUE futex_wait_body(VALUE data) {
|
|
416
|
+
fs_futex_wait *wait = (fs_futex_wait *)data;
|
|
417
|
+
fs_state *s = fs_get(wait->self);
|
|
418
|
+
fs_wait *w = fs_lookup(s, wait->token);
|
|
419
|
+
int error = fs_driver_futex(s, w, wait->word);
|
|
420
|
+
if (error) rb_syserr_fail(error, "scheduler futex submission");
|
|
421
|
+
return rb_funcall(wait->self, rb_intern("wait_with_timeout"), 3,
|
|
422
|
+
wait->token, wait->timeout, Qfalse);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static VALUE futex_wait_cleanup(VALUE data) {
|
|
426
|
+
fs_futex_wait *wait = (fs_futex_wait *)data;
|
|
427
|
+
return fs_retire(wait->self, wait->token);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
bool containers_futex_scheduler_wait(VALUE scheduler, containers_futex_t *word, VALUE timeout) {
|
|
431
|
+
if (!containers_futex_scheduler_supported(scheduler)) return true;
|
|
432
|
+
fs_futex_wait wait = {scheduler, fs_arm(scheduler), timeout, word};
|
|
433
|
+
return RTEST(rb_ensure(futex_wait_body, (VALUE)&wait, futex_wait_cleanup, (VALUE)&wait));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Expose the backend capability privately so native-path validation cannot pass
|
|
437
|
+
// by silently exercising a descriptor fallback.
|
|
438
|
+
static VALUE futex_supported(VALUE self) {
|
|
439
|
+
fs_state *s = fs_get(self);
|
|
440
|
+
return !s->closed && !s->stopping && s->futex_supported && containers_futex_available() ? Qtrue : Qfalse;
|
|
441
|
+
}
|
|
442
|
+
|
|
384
443
|
// Switch from the root to a ready fiber.
|
|
385
444
|
// If a newly started child finishes, return control to its waiting parent
|
|
386
445
|
// before returning to the root dispatcher.
|
|
@@ -600,6 +659,27 @@ static int snapshot_fallback(st_data_t key, st_data_t value, st_data_t arg) {
|
|
|
600
659
|
// Wake-pipe events ask the dispatcher to return to Ruby policy code.
|
|
601
660
|
static void accept_event(VALUE self, fs_event *e) {
|
|
602
661
|
fs_state *s = fs_get(self);
|
|
662
|
+
if (e->generation & FS_FUTEX) {
|
|
663
|
+
fs_wait *w = fs_lookup(s, ULL2NUM(e->generation & ~FS_OPERATION));
|
|
664
|
+
if (!w) return;
|
|
665
|
+
w->futex_pending = 0;
|
|
666
|
+
int error = e->error < 0 ? -e->error : 0;
|
|
667
|
+
if (error == ENOSYS || error == EOPNOTSUPP) s->futex_supported = 0;
|
|
668
|
+
if (!w->status) {
|
|
669
|
+
if (error == ENOSYS || error == EOPNOTSUPP) {
|
|
670
|
+
// Retry with the now-disabled capability, rather than reporting
|
|
671
|
+
// a timeout to consumers that have not reached their deadline.
|
|
672
|
+
fs_resume(self, w->token, Qtrue, Qnil);
|
|
673
|
+
} else {
|
|
674
|
+
// Early notification is reported as EAGAIN. Other interruptions
|
|
675
|
+
// also return to the container's condition/deadline check.
|
|
676
|
+
if (error != EAGAIN && error != EINTR && error != ECANCELED)
|
|
677
|
+
w->native_error = error;
|
|
678
|
+
fs_resume(self, w->token, Qtrue, Qnil);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
603
683
|
if (e->generation & FS_RECEIVE) {
|
|
604
684
|
fs_wait *w = fs_lookup(s, ULL2NUM(e->generation & ~FS_RECEIVE));
|
|
605
685
|
if (!w) return;
|
|
@@ -660,36 +740,44 @@ static void poll_ready(VALUE self) {
|
|
|
660
740
|
if (s->poll_error || s->selected_pending) s->policy_pending = 1;
|
|
661
741
|
}
|
|
662
742
|
|
|
663
|
-
// Cancel an
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
void
|
|
743
|
+
// Cancel an operation and consume its original CQE before releasing storage.
|
|
744
|
+
// Cancellation acknowledgements use zero userdata, so a delayed acknowledgement
|
|
745
|
+
// cannot refer to an operation record or a subsequently reused suspension.
|
|
746
|
+
static void cancel_operation(VALUE self, fs_wait *w, uint64_t kind, int *pending) {
|
|
667
747
|
#ifdef FARCE_URING
|
|
668
748
|
fs_state *s = fs_get(self);
|
|
669
749
|
struct io_uring_sqe *entry = io_uring_get_sqe(&s->ring);
|
|
670
750
|
if (!entry) {
|
|
671
751
|
int result;
|
|
672
752
|
do { result = io_uring_submit(&s->ring); } while (result == -EINTR);
|
|
673
|
-
if (result < 0) rb_bug("cannot submit pending scheduler
|
|
753
|
+
if (result < 0) rb_bug("cannot submit pending scheduler operation");
|
|
674
754
|
entry = io_uring_get_sqe(&s->ring);
|
|
675
755
|
if (!entry) rb_bug("cannot reserve scheduler cancellation");
|
|
676
756
|
}
|
|
677
|
-
io_uring_prep_cancel64(entry,
|
|
757
|
+
io_uring_prep_cancel64(entry, kind | NUM2ULL(w->token), 0);
|
|
678
758
|
entry->user_data = 0;
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
while (
|
|
759
|
+
// Do not run fibers here. The caller's buffer or futex word stays alive
|
|
760
|
+
// until the original completion, rather than just the cancellation reply.
|
|
761
|
+
while (*pending) {
|
|
682
762
|
int result = io_uring_submit_and_wait(&s->ring, 1);
|
|
683
|
-
if (result < 0 && result != -EINTR) rb_bug("cannot acknowledge scheduler
|
|
763
|
+
if (result < 0 && result != -EINTR) rb_bug("cannot acknowledge scheduler cancellation");
|
|
684
764
|
s->timeout = 0;
|
|
685
765
|
fs_driver_poll(s);
|
|
686
|
-
if (s->poll_error && s->poll_error != EINTR) rb_bug("cannot drain scheduler
|
|
766
|
+
if (s->poll_error && s->poll_error != EINTR) rb_bug("cannot drain scheduler cancellation");
|
|
687
767
|
for (int i = 0; i < s->event_count; i++) accept_event(self, &s->events[i]);
|
|
688
768
|
rearm_events(s);
|
|
689
769
|
}
|
|
690
770
|
#endif
|
|
691
771
|
}
|
|
692
772
|
|
|
773
|
+
void fs_cancel_receive(VALUE self, fs_wait *w) {
|
|
774
|
+
cancel_operation(self, w, FS_RECEIVE, &w->receive_pending);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
static void fs_cancel_futex(VALUE self, fs_wait *w) {
|
|
778
|
+
cancel_operation(self, w, FS_FUTEX, &w->futex_pending);
|
|
779
|
+
}
|
|
780
|
+
|
|
693
781
|
// Call a helper for every active wait, skipping empty slots and idle fibers.
|
|
694
782
|
static void each_wait(fs_state *s, int (*callback)(st_data_t, st_data_t, st_data_t), st_data_t arg) {
|
|
695
783
|
for (uint32_t i = 0; i < s->slot_count; i++) {
|
|
@@ -836,6 +924,7 @@ void farce_init_fiber_scheduler(VALUE internal) {
|
|
|
836
924
|
rb_define_private_method(klass, "retire_wait", fs_retire, 1);
|
|
837
925
|
rb_define_private_method(klass, "register_select", register_select, 3);
|
|
838
926
|
rb_define_private_method(klass, "ready?", ready, 0);
|
|
927
|
+
rb_define_private_method(klass, "futex_supported?", futex_supported, 0);
|
|
839
928
|
rb_define_private_method(klass, "pending?", pending, 0);
|
|
840
929
|
rb_define_private_method(klass, "pending_count", pending_count, 0);
|
|
841
930
|
rb_define_private_method(klass, "wakeup", wakeup, 0);
|
data/ext/farce/reactor.h
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#ifndef FARCE_FIBER_REACTOR_H
|
|
2
2
|
#define FARCE_FIBER_REACTOR_H
|
|
3
3
|
#include "containers.h"
|
|
4
|
+
#include "futex.h"
|
|
4
5
|
#include <ruby/io.h>
|
|
5
6
|
#include <ruby/io/buffer.h>
|
|
6
7
|
#include <ruby/thread.h>
|
|
@@ -21,6 +22,8 @@
|
|
|
21
22
|
#define FS_WRITE 4
|
|
22
23
|
#define FS_EVENTS 1024
|
|
23
24
|
#define FS_RECEIVE (UINT64_C(1) << 63)
|
|
25
|
+
#define FS_FUTEX (UINT64_C(1) << 62)
|
|
26
|
+
#define FS_OPERATION (FS_RECEIVE | FS_FUTEX)
|
|
24
27
|
#define FS_SLOT_BITS 20
|
|
25
28
|
#define FS_SLOT_MASK ((1U << FS_SLOT_BITS) - 1)
|
|
26
29
|
|
|
@@ -45,7 +48,7 @@ struct fs_reg {
|
|
|
45
48
|
struct fs_wait {
|
|
46
49
|
VALUE token, fiber, value, error, groups, ios;
|
|
47
50
|
uint32_t slot;
|
|
48
|
-
int status, events, selected, queued, native_error, receive_pending;
|
|
51
|
+
int status, events, selected, queued, native_error, receive_pending, futex_pending;
|
|
49
52
|
fs_wait *ready_next, *ready_prev;
|
|
50
53
|
fs_watch *watches;
|
|
51
54
|
fs_watch first_watch;
|
|
@@ -62,7 +65,7 @@ typedef struct {
|
|
|
62
65
|
size_t pending;
|
|
63
66
|
uint64_t serial, generation, suspensions;
|
|
64
67
|
unsigned immediate;
|
|
65
|
-
int selected_pending, receive_disabled;
|
|
68
|
+
int selected_pending, receive_disabled, futex_supported;
|
|
66
69
|
int driver, descriptor, wake[2], closed;
|
|
67
70
|
pthread_mutex_t wake_mutex;
|
|
68
71
|
#ifdef FARCE_URING
|
|
@@ -85,6 +88,7 @@ static inline fs_state *fs_get(VALUE self) {
|
|
|
85
88
|
int fs_driver_init(fs_state *s, int driver);
|
|
86
89
|
int fs_driver_update(fs_state *s, fs_reg *reg, int events);
|
|
87
90
|
int fs_driver_receive(fs_state *s, fs_wait *w, int fd, void *buffer, size_t size);
|
|
91
|
+
int fs_driver_futex(fs_state *s, fs_wait *w, containers_futex_t *word);
|
|
88
92
|
void fs_cancel_receive(VALUE self, fs_wait *w);
|
|
89
93
|
void fs_driver_close(fs_state *s);
|
|
90
94
|
void *fs_driver_poll(void *data);
|
data/ext/farce/signal.c
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
#include "containers.h"
|
|
2
|
-
#include "
|
|
2
|
+
#include "native_wait.h"
|
|
3
3
|
|
|
4
|
-
#include <errno.h>
|
|
5
|
-
#include <fcntl.h>
|
|
6
4
|
#include <math.h>
|
|
7
5
|
#include <stdint.h>
|
|
8
6
|
#include <time.h>
|
|
9
|
-
#include <unistd.h>
|
|
10
7
|
|
|
11
8
|
static VALUE cSignal;
|
|
12
9
|
|
|
13
10
|
typedef struct containers_signal_waiter containers_signal_waiter_t;
|
|
14
11
|
|
|
15
12
|
struct containers_signal_waiter {
|
|
16
|
-
|
|
17
|
-
int write_fd;
|
|
18
|
-
bool notified;
|
|
13
|
+
containers_waiter_t notification;
|
|
19
14
|
containers_signal_waiter_t *next;
|
|
20
15
|
};
|
|
21
16
|
|
|
@@ -66,34 +61,13 @@ containers_signal_parse_timeout(VALUE value)
|
|
|
66
61
|
return timeout;
|
|
67
62
|
}
|
|
68
63
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
#ifdef _WIN32
|
|
73
|
-
(void)fd;
|
|
74
|
-
#else
|
|
75
|
-
int flags = fcntl(fd, F_GETFL, 0);
|
|
76
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
77
|
-
flags = fcntl(fd, F_GETFD, 0);
|
|
78
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
79
|
-
#endif
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/* Called with signal->lock held. Each waiter has a separate descriptor, so a
|
|
83
|
-
* single broadcast makes every scheduled Fiber or native thread runnable. */
|
|
64
|
+
/* Called with signal->lock held. The notification remains latched while
|
|
65
|
+
* the waiting caller selects its parking backend outside the state mutex. */
|
|
84
66
|
static void
|
|
85
67
|
containers_signal_notify_waiters_locked(containers_signal_t *signal)
|
|
86
68
|
{
|
|
87
|
-
unsigned char byte = 1;
|
|
88
69
|
for (containers_signal_waiter_t *waiter = signal->waiters; waiter; waiter = waiter->next) {
|
|
89
|
-
|
|
90
|
-
ssize_t result;
|
|
91
|
-
do {
|
|
92
|
-
result = write(waiter->write_fd, &byte, 1);
|
|
93
|
-
} while (result < 0 && errno == EINTR);
|
|
94
|
-
if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
95
|
-
waiter->notified = true;
|
|
96
|
-
}
|
|
70
|
+
(void)containers_waiter_notify(&waiter->notification);
|
|
97
71
|
}
|
|
98
72
|
}
|
|
99
73
|
|
|
@@ -112,7 +86,7 @@ containers_signal_memsize(const void *pointer)
|
|
|
112
86
|
}
|
|
113
87
|
|
|
114
88
|
static const rb_data_type_t containers_signal_type = {
|
|
115
|
-
.wrap_struct_name = "
|
|
89
|
+
.wrap_struct_name = "Farce::Internal::Signal",
|
|
116
90
|
.function = {
|
|
117
91
|
.dmark = NULL,
|
|
118
92
|
.dfree = containers_signal_free,
|
|
@@ -184,26 +158,14 @@ containers_signal_new(void)
|
|
|
184
158
|
return containers_signal_initialize(signal);
|
|
185
159
|
}
|
|
186
160
|
|
|
187
|
-
static bool
|
|
188
|
-
containers_signal_wait_for_descriptor(int fd, containers_signal_timeout_t *timeout)
|
|
189
|
-
{
|
|
190
|
-
VALUE wait_timeout = Qnil;
|
|
191
|
-
if (timeout->finite) {
|
|
192
|
-
double remaining = timeout->deadline - containers_signal_monotonic_now();
|
|
193
|
-
if (remaining <= 0) return false;
|
|
194
|
-
wait_timeout = DBL2NUM(remaining);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return containers_wait_for_readable(fd, wait_timeout);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
161
|
static VALUE
|
|
201
162
|
containers_signal_wait_body(VALUE opaque)
|
|
202
163
|
{
|
|
203
164
|
containers_signal_wait_context_t *context = (containers_signal_wait_context_t *)opaque;
|
|
204
|
-
return
|
|
205
|
-
|
|
206
|
-
|
|
165
|
+
return containers_waiter_wait(
|
|
166
|
+
&context->waiter.notification, &context->signal->lock,
|
|
167
|
+
context->timeout->finite, context->timeout->deadline, NULL
|
|
168
|
+
) ? Qtrue : Qfalse;
|
|
207
169
|
}
|
|
208
170
|
|
|
209
171
|
static VALUE
|
|
@@ -220,8 +182,7 @@ containers_signal_wait_cleanup(VALUE opaque)
|
|
|
220
182
|
}
|
|
221
183
|
}
|
|
222
184
|
pthread_mutex_unlock(&context->signal->lock);
|
|
223
|
-
|
|
224
|
-
close(context->waiter.write_fd);
|
|
185
|
+
containers_waiter_close(&context->waiter.notification);
|
|
225
186
|
return Qnil;
|
|
226
187
|
}
|
|
227
188
|
|
|
@@ -232,24 +193,12 @@ containers_signal_wait_once(
|
|
|
232
193
|
containers_signal_timeout_t *timeout
|
|
233
194
|
)
|
|
234
195
|
{
|
|
235
|
-
int descriptors[2];
|
|
236
|
-
if (pipe(descriptors) != 0) {
|
|
237
|
-
pthread_mutex_unlock(&signal->lock);
|
|
238
|
-
rb_sys_fail("pipe");
|
|
239
|
-
}
|
|
240
|
-
containers_signal_set_fd_flags(descriptors[0]);
|
|
241
|
-
containers_signal_set_fd_flags(descriptors[1]);
|
|
242
|
-
|
|
243
196
|
containers_signal_wait_context_t context = {
|
|
244
197
|
.signal = signal,
|
|
245
|
-
.waiter = {
|
|
246
|
-
.read_fd = descriptors[0],
|
|
247
|
-
.write_fd = descriptors[1],
|
|
248
|
-
.notified = false,
|
|
249
|
-
.next = signal->waiters,
|
|
250
|
-
},
|
|
198
|
+
.waiter = {.next = signal->waiters},
|
|
251
199
|
.timeout = timeout,
|
|
252
200
|
};
|
|
201
|
+
containers_waiter_init(&context.waiter.notification);
|
|
253
202
|
signal->waiters = &context.waiter;
|
|
254
203
|
pthread_mutex_unlock(&signal->lock);
|
|
255
204
|
return RTEST(rb_ensure(
|
|
@@ -330,6 +279,10 @@ containers_signal_wait(int argc, VALUE *argv, VALUE self)
|
|
|
330
279
|
pthread_mutex_unlock(&signal->lock);
|
|
331
280
|
return ULL2NUM(generation);
|
|
332
281
|
}
|
|
282
|
+
if (timeout.finite && timeout.deadline <= containers_signal_monotonic_now()) {
|
|
283
|
+
pthread_mutex_unlock(&signal->lock);
|
|
284
|
+
return containers_signal_timeout_result();
|
|
285
|
+
}
|
|
333
286
|
bool awakened = containers_signal_wait_once(signal, &timeout);
|
|
334
287
|
pthread_mutex_lock(&signal->lock);
|
|
335
288
|
if (!awakened && signal->generation == observed) {
|