farce 0.4.1-aarch64-linux-gnu → 0.6.0-aarch64-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 +98 -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 +32 -0
- data/docs/variants.md +9 -0
- data/ext/farce/atom.c +23 -7
- data/ext/farce/containers.h +3 -0
- data/ext/farce/counter.c +4 -6
- data/ext/farce/depend +3 -1
- data/ext/farce/extconf.rb +19 -14
- data/ext/farce/farce.c +110 -3
- data/ext/farce/map.c +15 -7
- data/ext/farce/priority_queue.c +46 -1
- data/ext/farce/queue.c +141 -7
- data/ext/farce/trie.c +140 -72
- data/ext/farce/trie_search.h +114 -0
- data/ext/farce/vector.c +53 -21
- data/ext/farce/weak_map.c +16 -11
- data/ext/farce/weak_references.h +23 -0
- data/ext/java/org/farce/FiberScheduler.java +8 -6
- data/ext/java/org/farce/PriorityQueue.java +18 -2
- data/lib/farce/abstract/atom.rb +5 -1
- data/lib/farce/abstract/concurrent_map.rb +122 -0
- data/lib/farce/abstract/counter.rb +36 -1
- data/lib/farce/abstract/executor.rb +30 -7
- data/lib/farce/abstract/flag.rb +12 -0
- data/lib/farce/abstract/molecule.rb +14 -14
- 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 +15 -2
- data/lib/farce/clock.rb +20 -7
- 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 +5 -15
- data/lib/farce/deduper.rb +12 -4
- data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
- data/lib/farce/engine/jruby/map.rb +8 -6
- data/lib/farce/engine/jruby/signal.rb +2 -2
- data/lib/farce/engine/jvm/counter.rb +0 -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_cache_mirror.rb +56 -0
- data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
- data/lib/farce/engine/ruby.rb +5 -1
- 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/trie/ignore_case.rb +136 -0
- data/lib/farce/engine/shared/trie.rb +14 -1
- data/lib/farce/engine/shared/trie_builder.rb +19 -8
- data/lib/farce/engine/shared/unshared_atom.rb +1 -1
- data/lib/farce/engine/shared/unshared_vector.rb +11 -8
- data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
- data/lib/farce/engine/shared/vector.rb +2 -0
- 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/shared.rb +2 -0
- data/lib/farce/engine/truffleruby/map.rb +8 -6
- data/lib/farce/engine/truffleruby/native/counter.rb +0 -2
- data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
- data/lib/farce/engine/truffleruby/signal.rb +1 -1
- data/lib/farce/engine/truffleruby.rb +9 -0
- data/lib/farce/extension.rb +5 -3
- data/lib/farce/integrations/concurrent.rb +16 -3
- data/lib/farce/integrations/sorted_set.rb +1 -0
- data/lib/farce/internal/blocking_priority_queue.rb +4 -9
- data/lib/farce/internal/fake_ractor.rb +1 -1
- data/lib/farce/internal/flag_waiting.rb +1 -1
- data/lib/farce/internal/freeze.rb +1 -5
- 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 +17 -9
- 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 +19 -8
- data/lib/farce/lazy.rb +3 -0
- data/lib/farce/lazy_ref.rb +3 -0
- data/lib/farce/local/counter.rb +4 -10
- 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/map.rb +8 -1
- data/lib/farce/memoize.rb +319 -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/shareable.rb +3 -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/strict/weak_cache.rb +199 -0
- 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 +20 -16
- data/lib/farce/version.rb +1 -1
- data/lib/farce/walker/definitions.rb +31 -2
- data/lib/farce/walker/modification.rb +28 -14
- data/lib/farce/walker.rb +9 -1
- data/lib/farce/weak_atom.rb +5 -2
- data/lib/farce/weak_key_map.rb +1 -1
- data/lib/farce/weak_map.rb +1 -1
- data/lib/farce/weak_value_map.rb +1 -1
- 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
|
|
|
@@ -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/trie.c
CHANGED
|
@@ -111,6 +111,7 @@ typedef struct {
|
|
|
111
111
|
size_t static_index_count;
|
|
112
112
|
size_t static_index_bytes;
|
|
113
113
|
size_t regexp_edge_count;
|
|
114
|
+
VALUE (*search_body)(VALUE);
|
|
114
115
|
bool initialized;
|
|
115
116
|
} trie_t;
|
|
116
117
|
|
|
@@ -131,6 +132,7 @@ typedef struct {
|
|
|
131
132
|
size_t static_index;
|
|
132
133
|
size_t regexp_index;
|
|
133
134
|
unsigned phase;
|
|
135
|
+
unsigned lowercase_offset;
|
|
134
136
|
} trie_frame_t;
|
|
135
137
|
|
|
136
138
|
/* A suspended Enumerator or user Fiber can be collected without unwinding the
|
|
@@ -191,8 +193,12 @@ static VALUE cTrie;
|
|
|
191
193
|
static ID id_captures;
|
|
192
194
|
static ID id_named_captures;
|
|
193
195
|
static ID id_timeout;
|
|
196
|
+
static ID id_downcase;
|
|
194
197
|
static bool regexp_timeout_supported;
|
|
195
198
|
|
|
199
|
+
static VALUE trie_search_body(VALUE opaque);
|
|
200
|
+
static VALUE trie_search_body_ignore_case(VALUE opaque);
|
|
201
|
+
|
|
196
202
|
#ifdef FARCE_TRIE_FAILURE_INJECTION
|
|
197
203
|
static _Thread_local long trie_failure_countdown = -1;
|
|
198
204
|
static VALUE trie_interrupt_queue = Qnil;
|
|
@@ -1356,6 +1362,14 @@ trie_insert_entry(VALUE self, trie_t *trie, VALUE parts, uint32_t token)
|
|
|
1356
1362
|
if (rb_enc_str_coderange(stable_part) == ENC_CODERANGE_BROKEN) {
|
|
1357
1363
|
rb_raise(rb_eArgError, "trie literal has invalid encoding");
|
|
1358
1364
|
}
|
|
1365
|
+
if (trie->search_body == trie_search_body_ignore_case) {
|
|
1366
|
+
VALUE lowercase = rb_funcall(stable_part, id_downcase, 0);
|
|
1367
|
+
Check_Type(lowercase, T_STRING);
|
|
1368
|
+
stable_part = rb_str_new_frozen(lowercase);
|
|
1369
|
+
if (rb_enc_str_coderange(stable_part) == ENC_CODERANGE_BROKEN) {
|
|
1370
|
+
rb_raise(rb_eArgError, "trie literal has invalid encoding");
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1359
1373
|
node = trie_insert_string(self, trie, node, stable_part);
|
|
1360
1374
|
RB_GC_GUARD(stable_part);
|
|
1361
1375
|
}
|
|
@@ -1407,16 +1421,25 @@ trie_validate_references(VALUE self)
|
|
|
1407
1421
|
}
|
|
1408
1422
|
|
|
1409
1423
|
static VALUE
|
|
1410
|
-
trie_initialize(
|
|
1424
|
+
trie_initialize(int argc, VALUE *argv, VALUE self)
|
|
1411
1425
|
{
|
|
1412
1426
|
trie_t *trie;
|
|
1427
|
+
VALUE entries, tokens, options;
|
|
1428
|
+
VALUE ignore_case = Qfalse;
|
|
1413
1429
|
VALUE token_copy;
|
|
1414
1430
|
VALUE token_identities;
|
|
1415
1431
|
long index;
|
|
1432
|
+
rb_scan_args(argc, argv, "2:", &entries, &tokens, &options);
|
|
1433
|
+
if (!NIL_P(options)) {
|
|
1434
|
+
ID keyword = rb_intern("ignore_case");
|
|
1435
|
+
rb_get_kwargs(options, &keyword, 0, 1, &ignore_case);
|
|
1436
|
+
if (ignore_case == Qundef) ignore_case = Qfalse;
|
|
1437
|
+
}
|
|
1416
1438
|
TypedData_Get_Struct(self, trie_t, &trie_type, trie);
|
|
1417
1439
|
if (trie->initialized || trie->node_count || !NIL_P(trie->tokens)) {
|
|
1418
1440
|
rb_raise(rb_eRuntimeError, "trie initialization already attempted");
|
|
1419
1441
|
}
|
|
1442
|
+
trie->search_body = RTEST(ignore_case) ? trie_search_body_ignore_case : trie_search_body;
|
|
1420
1443
|
Check_Type(entries, T_ARRAY);
|
|
1421
1444
|
Check_Type(tokens, T_ARRAY);
|
|
1422
1445
|
if ((unsigned long)RARRAY_LEN(tokens) > UINT32_MAX) rb_raise(rb_eArgError, "too many trie tokens");
|
|
@@ -1616,6 +1639,107 @@ trie_static_match(VALUE input, long cursor, const trie_static_edge_t *edge,
|
|
|
1616
1639
|
return true;
|
|
1617
1640
|
}
|
|
1618
1641
|
|
|
1642
|
+
typedef struct {
|
|
1643
|
+
rb_encoding *encoding;
|
|
1644
|
+
bool ascii_compatible;
|
|
1645
|
+
} trie_case_mapping_t;
|
|
1646
|
+
|
|
1647
|
+
static unsigned char
|
|
1648
|
+
trie_lowercase_ascii(unsigned char byte)
|
|
1649
|
+
{
|
|
1650
|
+
return byte >= 'A' && byte <= 'Z' ? byte + ('a' - 'A') : byte;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
static int
|
|
1654
|
+
trie_lowercase_character(const trie_case_mapping_t *mapping, const unsigned char **cursor,
|
|
1655
|
+
const unsigned char *end, unsigned char *output)
|
|
1656
|
+
{
|
|
1657
|
+
int bytes = rb_enc_mbclen((const char *)*cursor, (const char *)end, mapping->encoding);
|
|
1658
|
+
OnigCaseFoldType flags = ONIGENC_CASE_DOWNCASE;
|
|
1659
|
+
int length = mapping->encoding->case_map(&flags, cursor, *cursor + bytes,
|
|
1660
|
+
output, output + ONIGENC_MBC_CASE_FOLD_MAXLEN,
|
|
1661
|
+
mapping->encoding);
|
|
1662
|
+
if (length <= 0) rb_raise(rb_eArgError, "cannot lowercase trie input character");
|
|
1663
|
+
return length;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
static void
|
|
1667
|
+
trie_search_push_frame_ignore_case(trie_search_t *search, trie_node_t *node, long cursor,
|
|
1668
|
+
size_t restore_events, unsigned lowercase_offset,
|
|
1669
|
+
const trie_case_mapping_t *mapping)
|
|
1670
|
+
{
|
|
1671
|
+
trie_search_ensure_frames(search, search->frame_count + 1);
|
|
1672
|
+
trie_frame_t *frame = &search->frames[search->frame_count++];
|
|
1673
|
+
memset(frame, 0, sizeof(*frame));
|
|
1674
|
+
frame->node = node;
|
|
1675
|
+
frame->cursor = cursor;
|
|
1676
|
+
frame->restore_events = restore_events;
|
|
1677
|
+
frame->lowercase_offset = lowercase_offset;
|
|
1678
|
+
if (node->static_index && cursor < search->input_length) {
|
|
1679
|
+
const unsigned char *bytes = (const unsigned char *)RSTRING_PTR(search->input) + cursor;
|
|
1680
|
+
unsigned char first;
|
|
1681
|
+
if (*bytes < 128 && mapping->ascii_compatible) first = trie_lowercase_ascii(*bytes);
|
|
1682
|
+
else {
|
|
1683
|
+
unsigned char lowercase[ONIGENC_MBC_CASE_FOLD_MAXLEN];
|
|
1684
|
+
const unsigned char *end = (const unsigned char *)RSTRING_END(search->input);
|
|
1685
|
+
trie_lowercase_character(mapping, &bytes, end, lowercase);
|
|
1686
|
+
first = lowercase[lowercase_offset];
|
|
1687
|
+
}
|
|
1688
|
+
const uint32_t *slot = trie_static_index_slot_const(node->static_index, first);
|
|
1689
|
+
frame->static_index = slot ? *slot : 0;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
static bool
|
|
1694
|
+
trie_static_match_ignore_case(trie_search_t *search, const trie_frame_t *frame,
|
|
1695
|
+
const trie_static_edge_t *edge, const trie_case_mapping_t *mapping,
|
|
1696
|
+
long *end_cursor, unsigned *end_offset)
|
|
1697
|
+
{
|
|
1698
|
+
VALUE input = search->input;
|
|
1699
|
+
VALUE label = edge->label;
|
|
1700
|
+
if (edge->encoding != search->input_encoding && !rb_enc_compatible(label, input)) return false;
|
|
1701
|
+
long cursor = frame->cursor;
|
|
1702
|
+
unsigned offset = frame->lowercase_offset;
|
|
1703
|
+
long index = 0;
|
|
1704
|
+
long length = RSTRING_LEN(label);
|
|
1705
|
+
long checkpoint = 16384;
|
|
1706
|
+
while (index < length) {
|
|
1707
|
+
if (cursor >= search->input_length) return false;
|
|
1708
|
+
const unsigned char *bytes = (const unsigned char *)RSTRING_PTR(input) + cursor;
|
|
1709
|
+
const unsigned char *literal = (const unsigned char *)RSTRING_PTR(label);
|
|
1710
|
+
if (*bytes < 128 && mapping->ascii_compatible) {
|
|
1711
|
+
if (trie_lowercase_ascii(*bytes) != literal[index]) return false;
|
|
1712
|
+
cursor++;
|
|
1713
|
+
index++;
|
|
1714
|
+
}
|
|
1715
|
+
else {
|
|
1716
|
+
unsigned char lowercase[ONIGENC_MBC_CASE_FOLD_MAXLEN];
|
|
1717
|
+
const unsigned char *begin = bytes;
|
|
1718
|
+
const unsigned char *end = (const unsigned char *)RSTRING_END(input);
|
|
1719
|
+
int count = trie_lowercase_character(mapping, &bytes, end, lowercase);
|
|
1720
|
+
while (offset < (unsigned)count && index < length) {
|
|
1721
|
+
if (lowercase[offset++] != literal[index++]) return false;
|
|
1722
|
+
}
|
|
1723
|
+
/* A radix split can fall inside one character's lowercase expansion.
|
|
1724
|
+
* Retain its original cursor until every lowercase byte is consumed. */
|
|
1725
|
+
if (offset == (unsigned)count) {
|
|
1726
|
+
cursor += bytes - begin;
|
|
1727
|
+
offset = 0;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
if (index >= checkpoint) {
|
|
1731
|
+
trie_check_interrupts();
|
|
1732
|
+
checkpoint = index + 16384;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
*end_cursor = cursor;
|
|
1736
|
+
*end_offset = offset;
|
|
1737
|
+
return true;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
/* Keep the original traversal's inlining when compiling both search modes. */
|
|
1741
|
+
ALWAYS_INLINE(static bool trie_regexp_match(const trie_regexp_edge_t *edge, trie_search_t *search,
|
|
1742
|
+
long cursor, long *end_cursor));
|
|
1619
1743
|
static bool
|
|
1620
1744
|
trie_regexp_match(const trie_regexp_edge_t *edge, trie_search_t *search,
|
|
1621
1745
|
long cursor, long *end_cursor)
|
|
@@ -1765,6 +1889,7 @@ trie_seen(trie_search_t *search, uint32_t token)
|
|
|
1765
1889
|
return false;
|
|
1766
1890
|
}
|
|
1767
1891
|
|
|
1892
|
+
ALWAYS_INLINE(static bool trie_emit_terminal(trie_search_t *search, trie_frame_t *frame));
|
|
1768
1893
|
static bool
|
|
1769
1894
|
trie_emit_terminal(trie_search_t *search, trie_frame_t *frame)
|
|
1770
1895
|
{
|
|
@@ -1796,75 +1921,17 @@ trie_emit_terminal(trie_search_t *search, trie_frame_t *frame)
|
|
|
1796
1921
|
return false;
|
|
1797
1922
|
}
|
|
1798
1923
|
|
|
1799
|
-
|
|
1800
|
-
trie_search_body
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
while (frame->static_index) {
|
|
1811
|
-
trie_static_edge_t *edge = &frame->node->static_edges[frame->static_index - 1];
|
|
1812
|
-
long label_length = RSTRING_LEN(edge->label);
|
|
1813
|
-
frame->static_index = edge->next_same_byte;
|
|
1814
|
-
if (!trie_static_match(search->input, frame->cursor, edge,
|
|
1815
|
-
label_length, search->input_encoding)) continue;
|
|
1816
|
-
trie_search_push_frame(search, edge->child, frame->cursor + label_length,
|
|
1817
|
-
search->event_count);
|
|
1818
|
-
descended = true;
|
|
1819
|
-
break;
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
else {
|
|
1823
|
-
while (frame->static_index < frame->node->static_count) {
|
|
1824
|
-
trie_static_edge_t *edge = &frame->node->static_edges[frame->static_index++];
|
|
1825
|
-
long label_length = RSTRING_LEN(edge->label);
|
|
1826
|
-
if (!trie_static_match(search->input, frame->cursor, edge,
|
|
1827
|
-
label_length, search->input_encoding)) continue;
|
|
1828
|
-
trie_search_push_frame(search, edge->child, frame->cursor + label_length,
|
|
1829
|
-
search->event_count);
|
|
1830
|
-
descended = true;
|
|
1831
|
-
break;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
if (descended) continue;
|
|
1835
|
-
frame->phase = 1;
|
|
1836
|
-
}
|
|
1837
|
-
if (frame->phase == 1) {
|
|
1838
|
-
bool descended = false;
|
|
1839
|
-
while (frame->regexp_index < frame->node->regexp_count) {
|
|
1840
|
-
trie_regexp_edge_t *edge = &frame->node->regexp_edges[frame->regexp_index++];
|
|
1841
|
-
size_t restore = search->event_count;
|
|
1842
|
-
long end_cursor;
|
|
1843
|
-
if (!trie_regexp_match(edge, search, frame->cursor, &end_cursor)) {
|
|
1844
|
-
search->event_count = restore;
|
|
1845
|
-
continue;
|
|
1846
|
-
}
|
|
1847
|
-
trie_search_push_frame(search, edge->child, end_cursor, restore);
|
|
1848
|
-
descended = true;
|
|
1849
|
-
break;
|
|
1850
|
-
}
|
|
1851
|
-
if (descended) continue;
|
|
1852
|
-
frame->phase = 2;
|
|
1853
|
-
}
|
|
1854
|
-
if (frame->phase == 2) {
|
|
1855
|
-
frame->phase = 3;
|
|
1856
|
-
if ((search->peek || frame->cursor == search->input_length) &&
|
|
1857
|
-
trie_emit_terminal(search, frame)) break;
|
|
1858
|
-
}
|
|
1859
|
-
if (frame->phase == 3) {
|
|
1860
|
-
size_t restore = frame->restore_events;
|
|
1861
|
-
search->frame_count--;
|
|
1862
|
-
search->event_count = restore;
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
if (search->stream) return search->self;
|
|
1866
|
-
return search->first_only ? search->first : search->results;
|
|
1867
|
-
}
|
|
1924
|
+
#define TRIE_IGNORE_CASE 0
|
|
1925
|
+
#define TRIE_SEARCH_BODY trie_search_body
|
|
1926
|
+
#include "trie_search.h"
|
|
1927
|
+
#undef TRIE_SEARCH_BODY
|
|
1928
|
+
#undef TRIE_IGNORE_CASE
|
|
1929
|
+
|
|
1930
|
+
#define TRIE_IGNORE_CASE 1
|
|
1931
|
+
#define TRIE_SEARCH_BODY trie_search_body_ignore_case
|
|
1932
|
+
#include "trie_search.h"
|
|
1933
|
+
#undef TRIE_SEARCH_BODY
|
|
1934
|
+
#undef TRIE_IGNORE_CASE
|
|
1868
1935
|
|
|
1869
1936
|
static VALUE
|
|
1870
1937
|
trie_search_cleanup(VALUE opaque)
|
|
@@ -1916,7 +1983,7 @@ trie_search(VALUE self, VALUE input, bool peek, bool first_only, bool stream)
|
|
|
1916
1983
|
encoding == rb_usascii_encindex() ||
|
|
1917
1984
|
encoding == rb_ascii8bit_encindex();
|
|
1918
1985
|
}
|
|
1919
|
-
VALUE result = rb_ensure(
|
|
1986
|
+
VALUE result = rb_ensure(search.trie->search_body, (VALUE)(uintptr_t)&search,
|
|
1920
1987
|
trie_search_cleanup, (VALUE)(uintptr_t)&search);
|
|
1921
1988
|
RB_GC_GUARD(self);
|
|
1922
1989
|
RB_GC_GUARD(input);
|
|
@@ -2035,7 +2102,7 @@ containers_init_trie(VALUE namespace)
|
|
|
2035
2102
|
(void)namespace;
|
|
2036
2103
|
cTrie = rb_define_class_under(strict, "Trie", rb_cObject);
|
|
2037
2104
|
rb_define_alloc_func(cTrie, trie_allocate);
|
|
2038
|
-
rb_define_private_method(cTrie, "initialize", trie_initialize,
|
|
2105
|
+
rb_define_private_method(cTrie, "initialize", trie_initialize, -1);
|
|
2039
2106
|
rb_define_private_method(cTrie, "initialize_copy", trie_initialize_copy, 1);
|
|
2040
2107
|
rb_define_method(cTrie, "match", trie_match, 1);
|
|
2041
2108
|
rb_define_method(cTrie, "match_all", trie_match_all, 1);
|
|
@@ -2054,5 +2121,6 @@ containers_init_trie(VALUE namespace)
|
|
|
2054
2121
|
id_captures = rb_intern("captures");
|
|
2055
2122
|
id_named_captures = rb_intern("named_captures");
|
|
2056
2123
|
id_timeout = rb_intern("timeout");
|
|
2124
|
+
id_downcase = rb_intern("downcase");
|
|
2057
2125
|
regexp_timeout_supported = rb_respond_to(rb_cRegexp, id_timeout);
|
|
2058
2126
|
}
|