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