farce 0.1.0-aarch64-linux-gnu → 0.2.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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -1
  3. data/README.md +72 -16
  4. data/docs/gems/dry-types.md +8 -8
  5. data/docs/modes.md +67 -10
  6. data/ext/farce/atom.c +37 -22
  7. data/ext/farce/bounded_map.c +17 -13
  8. data/ext/farce/containers.h +15 -0
  9. data/ext/farce/counter.c +13 -18
  10. data/ext/farce/exchanger.c +5 -1
  11. data/ext/farce/farce.c +1 -1
  12. data/ext/farce/flag.c +39 -3
  13. data/ext/farce/io.c +3 -3
  14. data/ext/farce/lock.c +17 -23
  15. data/ext/farce/map.c +88 -66
  16. data/ext/farce/priority_queue.c +55 -43
  17. data/ext/farce/queue.c +10 -4
  18. data/ext/farce/reactor.c +52 -25
  19. data/ext/farce/reactor.h +1 -1
  20. data/ext/farce/signal.c +5 -1
  21. data/ext/farce/transaction.c +20 -24
  22. data/ext/farce/tree_map.c +33 -18
  23. data/ext/farce/trie.c +81 -43
  24. data/ext/farce/unshareable.c +168 -19
  25. data/ext/farce/unshared_io_pool.h +21 -12
  26. data/ext/farce/unshared_signal.c +13 -2
  27. data/ext/farce/unshared_wait.h +3 -0
  28. data/ext/farce/vector.c +82 -18
  29. data/ext/farce/weak_map.c +75 -63
  30. data/lib/farce/_yard/macros.rb +18 -3
  31. data/lib/farce/abstract/flag.rb +73 -0
  32. data/lib/farce/abstract/queue.rb +4 -3
  33. data/lib/farce/abstract/scheduler.rb +3 -3
  34. data/lib/farce/abstract/set.rb +17 -5
  35. data/lib/farce/abstract/sorted_set.rb +2 -2
  36. data/lib/farce/abstract/timer_queue.rb +49 -10
  37. data/lib/farce/abstract/vector.rb +510 -28
  38. data/lib/farce/atom.rb +1 -1
  39. data/lib/farce/class_mirror.rb +20 -7
  40. data/lib/farce/config.rb +7 -1
  41. data/lib/farce/engine/jvm/flag.rb +15 -3
  42. data/lib/farce/engine/ruby/3.4/farce.so +0 -0
  43. data/lib/farce/engine/ruby/4.0/farce.so +0 -0
  44. data/lib/farce/engine/ruby/shared/require_hook.rb +18 -0
  45. data/lib/farce/engine/ruby.rb +7 -0
  46. data/lib/farce/engine/shared/unshared_vector.rb +31 -4
  47. data/lib/farce/engine/shared/vector.rb +8 -0
  48. data/lib/farce/engine/shared/weak_map/index.rb +14 -1
  49. data/lib/farce/engine/truffleruby/native/flag.rb +15 -3
  50. data/lib/farce/envelope.rb +54 -7
  51. data/lib/farce/exchanger.rb +1 -1
  52. data/lib/farce/flag.rb +2 -0
  53. data/lib/farce/integrations/active_support.rb +1 -1
  54. data/lib/farce/integrations/bson.rb +1 -1
  55. data/lib/farce/integrations/cbor.rb +1 -1
  56. data/lib/farce/integrations/concurrent.rb +3 -3
  57. data/lib/farce/integrations/date.rb +8 -0
  58. data/lib/farce/integrations/dry_types.rb +7 -4
  59. data/lib/farce/integrations/json.rb +2 -2
  60. data/lib/farce/integrations/msgpack.rb +1 -1
  61. data/lib/farce/integrations/oj.rb +3 -4
  62. data/lib/farce/integrations/psych.rb +1 -1
  63. data/lib/farce/integrations/ractor_sharing.rb +1 -1
  64. data/lib/farce/integrations/sorted_set.rb +1 -1
  65. data/lib/farce/integrations/weakref.rb +9 -1
  66. data/lib/farce/integrations/yajl.rb +3 -3
  67. data/lib/farce/integrations.rb +33 -23
  68. data/lib/farce/internal/flag_waiting.rb +30 -0
  69. data/lib/farce/internal/freeze.rb +8 -3
  70. data/lib/farce/internal/map_value_modes.rb +1 -1
  71. data/lib/farce/internal/scheduler_lifecycle.rb +1 -1
  72. data/lib/farce/lazy.rb +4 -4
  73. data/lib/farce/lazy_ref.rb +3 -3
  74. data/lib/farce/lfu_map.rb +1 -1
  75. data/lib/farce/local/flag.rb +4 -0
  76. data/lib/farce/local/scoped.rb +1 -1
  77. data/lib/farce/lru_map.rb +1 -1
  78. data/lib/farce/map.rb +2 -2
  79. data/lib/farce/mode_manager/smart_rule.rb +138 -0
  80. data/lib/farce/mode_manager.rb +12 -3
  81. data/lib/farce/molecule.rb +2 -2
  82. data/lib/farce/pool.rb +1 -1
  83. data/lib/farce/port.rb +4 -4
  84. data/lib/farce/priority_queue.rb +1 -1
  85. data/lib/farce/proxy/register.rb +6 -0
  86. data/lib/farce/proxy/supervisor.rb +1 -1
  87. data/lib/farce/proxy.rb +7 -2
  88. data/lib/farce/queue.rb +1 -1
  89. data/lib/farce/scheduler.rb +2 -2
  90. data/lib/farce/set.rb +1 -1
  91. data/lib/farce/sorted_set.rb +1 -1
  92. data/lib/farce/timer_queue.rb +20 -6
  93. data/lib/farce/transaction/vector.rb +43 -9
  94. data/lib/farce/transaction/wrapper.rb +14 -0
  95. data/lib/farce/tree_map.rb +1 -1
  96. data/lib/farce/unsafe/lfu_map.rb +2 -0
  97. data/lib/farce/unsafe/lru_map.rb +2 -0
  98. data/lib/farce/unsafe/tree_map.rb +2 -0
  99. data/lib/farce/unshareable.rb +8 -19
  100. data/lib/farce/unshared/atom.rb +3 -0
  101. data/lib/farce/unshared/lfu_map.rb +2 -0
  102. data/lib/farce/unshared/lru_map.rb +2 -0
  103. data/lib/farce/unshared/map.rb +3 -0
  104. data/lib/farce/unshared/set.rb +1 -0
  105. data/lib/farce/unshared/sorted_set.rb +1 -0
  106. data/lib/farce/unshared/tree_map.rb +5 -1
  107. data/lib/farce/unshared/vector.rb +3 -0
  108. data/lib/farce/unshared/weak_atom.rb +5 -1
  109. data/lib/farce/unshared/weak_key_map.rb +3 -0
  110. data/lib/farce/unshared/weak_map.rb +3 -0
  111. data/lib/farce/unshared/weak_set.rb +1 -0
  112. data/lib/farce/unshared/weak_value_map.rb +3 -0
  113. data/lib/farce/vector.rb +44 -19
  114. data/lib/farce/version.rb +1 -1
  115. data/lib/farce.rb +11 -8
  116. metadata +5 -1
@@ -52,7 +52,7 @@ priority_queue_snapshot_string(VALUE string)
52
52
  rb_str_replace(copy, string);
53
53
  instance_variables = rb_obj_instance_variables(string);
54
54
  for (index = 0; index < RARRAY_LEN(instance_variables); index++) {
55
- ID id = SYM2ID(RARRAY_AREF(instance_variables, index));
55
+ ID id = SYM2ID(rb_ary_entry(instance_variables, index));
56
56
  rb_ivar_set(copy, id, rb_ivar_get(string, id));
57
57
  }
58
58
  rb_obj_freeze(copy);
@@ -436,7 +436,7 @@ priority_queue_track_remove(priority_queue_t *queue, priority_queue_value_t *val
436
436
  queue->generation++;
437
437
  }
438
438
 
439
- static void priority_queue_wait_for_publication(priority_queue_t *queue);
439
+ static void priority_queue_wait_for_publication(VALUE self, priority_queue_t *queue);
440
440
 
441
441
  typedef struct {
442
442
  VALUE receiver;
@@ -581,25 +581,27 @@ priority_queue_compact(void *pointer)
581
581
  dnode_t *node;
582
582
 
583
583
  if (!queue) return;
584
- queue->owner_fiber = rb_gc_location(queue->owner_fiber);
585
- queue->owner_ruby_thread = rb_gc_location(queue->owner_ruby_thread);
584
+ containers_gc_update(&queue->owner_fiber);
585
+ containers_gc_update(&queue->owner_ruby_thread);
586
586
  /* The VM already roots the running publisher. dcompact only refreshes the
587
587
  * unmarked identity handles if that Fiber or Thread moved. */
588
- queue->publication_owner_fiber =
589
- rb_gc_location(queue->publication_owner_fiber);
590
- queue->publication_owner_ruby_thread =
591
- rb_gc_location(queue->publication_owner_ruby_thread);
592
- queue->signal = rb_gc_location(queue->signal);
593
- queue->unshared_self = rb_gc_location(queue->unshared_self);
588
+ containers_gc_update(&queue->publication_owner_fiber);
589
+ containers_gc_update(&queue->publication_owner_ruby_thread);
590
+ containers_gc_update(&queue->signal);
591
+ containers_gc_update(&queue->unshared_self);
594
592
  for (node = dict_first(&queue->tree); node; node = dict_next(&queue->tree, node)) {
595
593
  priority_queue_bucket_t *bucket = (priority_queue_bucket_t *)node;
596
594
  priority_queue_value_t *entry;
597
- bucket->priority = rb_gc_location(bucket->priority);
598
- node->dict_key = (const void *)bucket->priority;
595
+ VALUE previous_priority = bucket->priority;
596
+ containers_gc_update(&bucket->priority);
597
+ if (previous_priority != bucket->priority) node->dict_key = (const void *)bucket->priority;
598
+ bool rebuild = false;
599
599
  for (entry = bucket->head; entry; entry = entry->next) {
600
- entry->value = rb_gc_location(entry->value);
600
+ VALUE previous_value = entry->value;
601
+ containers_gc_update(&entry->value);
602
+ if (previous_value != entry->value) rebuild = true;
601
603
  }
602
- priority_queue_identity_index_rebuild(bucket);
604
+ if (rebuild) priority_queue_identity_index_rebuild(bucket);
603
605
  }
604
606
  }
605
607
 
@@ -645,7 +647,11 @@ static const rb_data_type_t priority_queue_type = {
645
647
  .dsize = priority_queue_memsize,
646
648
  .dcompact = priority_queue_compact,
647
649
  },
648
- .flags = RUBY_TYPED_FROZEN_SHAREABLE,
650
+ .flags =
651
+ RUBY_TYPED_WB_PROTECTED |
652
+ RUBY_TYPED_FROZEN_SHAREABLE |
653
+ RUBY_TYPED_FREE_IMMEDIATELY |
654
+ FARCE_TYPED_THREAD_SAFE_FREE,
649
655
  };
650
656
 
651
657
  static const rb_data_type_t unshared_priority_queue_type = {
@@ -657,6 +663,7 @@ static const rb_data_type_t unshared_priority_queue_type = {
657
663
  .dcompact = priority_queue_compact,
658
664
  },
659
665
  .parent = &priority_queue_type,
666
+ .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | FARCE_TYPED_THREAD_SAFE_FREE,
660
667
  };
661
668
 
662
669
  /* The GVL protects the native fields within one Ractor. Keep the logical
@@ -737,7 +744,7 @@ priority_queue_get(VALUE self)
737
744
  * traversal cannot leak a usable half-published queue. The steady-state
738
745
  * path is one atomic load and does not touch the physical mutex. */
739
746
  if (priority_queue_publication_busy(state)) {
740
- priority_queue_wait_for_publication(queue);
747
+ priority_queue_wait_for_publication(self, queue);
741
748
  state = RUBY_ATOMIC_LOAD(queue->publication_state);
742
749
  }
743
750
  if (state != PRIORITY_QUEUE_INITIALIZED) {
@@ -884,7 +891,7 @@ priority_queue_lock_wait_once(priority_queue_t *queue)
884
891
  }
885
892
 
886
893
  static void
887
- priority_queue_lock(priority_queue_t *queue, bool *acquired)
894
+ priority_queue_lock(VALUE self, priority_queue_t *queue, bool *acquired)
888
895
  {
889
896
  VALUE current = rb_fiber_current();
890
897
  VALUE current_thread = rb_thread_current();
@@ -903,8 +910,8 @@ priority_queue_lock(priority_queue_t *queue, bool *acquired)
903
910
  rb_raise(rb_eThreadError, "deadlock; recursive priority queue access");
904
911
  }
905
912
  if (NIL_P(queue->owner_fiber) && !publication_busy) {
906
- queue->owner_fiber = current;
907
- queue->owner_ruby_thread = current_thread;
913
+ RB_OBJ_WRITE(self, &queue->owner_fiber, current);
914
+ RB_OBJ_WRITE(self, &queue->owner_ruby_thread, current_thread);
908
915
  *acquired = true;
909
916
  priority_queue_mutex_unlock(queue);
910
917
  return;
@@ -945,6 +952,7 @@ priority_queue_unlock(priority_queue_t *queue)
945
952
  }
946
953
 
947
954
  typedef struct {
955
+ VALUE self;
948
956
  priority_queue_t *queue;
949
957
  bool acquired;
950
958
  } priority_queue_publication_wait_t;
@@ -955,7 +963,7 @@ priority_queue_publication_wait_body(VALUE opaque)
955
963
  priority_queue_publication_wait_t *wait =
956
964
  (priority_queue_publication_wait_t *)opaque;
957
965
 
958
- priority_queue_lock(wait->queue, &wait->acquired);
966
+ priority_queue_lock(wait->self, wait->queue, &wait->acquired);
959
967
  return Qnil;
960
968
  }
961
969
 
@@ -970,9 +978,10 @@ priority_queue_publication_wait_ensure(VALUE opaque)
970
978
  }
971
979
 
972
980
  static void
973
- priority_queue_wait_for_publication(priority_queue_t *queue)
981
+ priority_queue_wait_for_publication(VALUE self, priority_queue_t *queue)
974
982
  {
975
983
  priority_queue_publication_wait_t wait = {
984
+ .self = self,
976
985
  .queue = queue,
977
986
  .acquired = false,
978
987
  };
@@ -991,6 +1000,7 @@ priority_queue_wait_for_publication(priority_queue_t *queue)
991
1000
  typedef VALUE (*priority_queue_operation_t)(VALUE opaque);
992
1001
 
993
1002
  typedef struct {
1003
+ VALUE self;
994
1004
  priority_queue_t *queue;
995
1005
  priority_queue_operation_t operation;
996
1006
  VALUE argument;
@@ -1001,7 +1011,7 @@ static VALUE
1001
1011
  priority_queue_call_body(VALUE opaque)
1002
1012
  {
1003
1013
  priority_queue_call_t *call = (priority_queue_call_t *)opaque;
1004
- priority_queue_lock(call->queue, &call->acquired);
1014
+ priority_queue_lock(call->self, call->queue, &call->acquired);
1005
1015
  return call->operation(call->argument);
1006
1016
  }
1007
1017
 
@@ -1016,12 +1026,14 @@ priority_queue_unlock_ensure(VALUE opaque)
1016
1026
 
1017
1027
  static VALUE
1018
1028
  priority_queue_call(
1029
+ VALUE self,
1019
1030
  priority_queue_t *queue,
1020
1031
  priority_queue_operation_t operation,
1021
1032
  VALUE argument
1022
1033
  )
1023
1034
  {
1024
1035
  priority_queue_call_t call = {
1036
+ .self = self,
1025
1037
  .queue = queue,
1026
1038
  .operation = operation,
1027
1039
  .argument = argument,
@@ -1217,7 +1229,7 @@ priority_queue_initialize_commit(VALUE opaque)
1217
1229
  queue->bounded = initialization->bounded;
1218
1230
  queue->capacity = initialization->capacity;
1219
1231
  queue->track_age = initialization->track_age;
1220
- queue->signal = initialization->signal;
1232
+ RB_OBJ_WRITE(initialization->self, &queue->signal, initialization->signal);
1221
1233
  /* Publication installs its logical gate before marking the object shareable
1222
1234
  * and keeps that whole transition under nested ensure cleanup. */
1223
1235
  if (queue->shared) return priority_queue_publish_shareable(initialization);
@@ -1278,7 +1290,7 @@ priority_queue_initialize(int argc, VALUE *argv, VALUE self)
1278
1290
  priority_queue_check_initializable(self, queue);
1279
1291
 
1280
1292
  return priority_queue_call(
1281
- queue,
1293
+ self, queue,
1282
1294
  priority_queue_initialize_commit,
1283
1295
  (VALUE)&initialization
1284
1296
  );
@@ -1363,7 +1375,7 @@ priority_queue_push_body(VALUE opaque)
1363
1375
  priority_queue_identity_index_prepare_insert(bucket->identity_index, push->value);
1364
1376
  entry = calloc(1, queue->track_age ? sizeof(priority_queue_tracked_value_t) : sizeof(priority_queue_value_t));
1365
1377
  if (!entry) rb_memerror();
1366
- entry->value = push->value;
1378
+ RB_OBJ_WRITE(push->self, &entry->value, push->value);
1367
1379
  entry->previous = bucket->tail;
1368
1380
  entry->next = NULL;
1369
1381
  entry->identity_next = NULL;
@@ -1377,7 +1389,7 @@ priority_queue_push_body(VALUE opaque)
1377
1389
  free(entry);
1378
1390
  rb_memerror();
1379
1391
  }
1380
- entry->value = push->value;
1392
+ RB_OBJ_WRITE(push->self, &entry->value, push->value);
1381
1393
  entry->previous = NULL;
1382
1394
  entry->next = NULL;
1383
1395
  entry->identity_next = NULL;
@@ -1393,7 +1405,7 @@ priority_queue_push_body(VALUE opaque)
1393
1405
  else {
1394
1406
  push->stored_priority = push->priority;
1395
1407
  }
1396
- bucket->priority = push->stored_priority;
1408
+ RB_OBJ_WRITE(push->self, &bucket->priority, push->stored_priority);
1397
1409
  if (CLASS_OF(push->priority) == rb_cFloat) {
1398
1410
  bucket->float_priority = RFLOAT_VALUE(push->priority);
1399
1411
  }
@@ -1475,7 +1487,7 @@ priority_queue_push(VALUE self, VALUE priority, VALUE value)
1475
1487
  return priority_queue_push_locked((VALUE)&push);
1476
1488
  }
1477
1489
  return priority_queue_call(
1478
- queue,
1490
+ self, queue,
1479
1491
  priority_queue_push_locked,
1480
1492
  (VALUE)&push
1481
1493
  );
@@ -1559,7 +1571,7 @@ priority_queue_read(VALUE self, priority_queue_read_kind_t kind, bool last)
1559
1571
  priority_queue_read_body((VALUE)&read);
1560
1572
  }
1561
1573
  else {
1562
- priority_queue_call(queue, priority_queue_read_body, (VALUE)&read);
1574
+ priority_queue_call(self, queue, priority_queue_read_body, (VALUE)&read);
1563
1575
  }
1564
1576
  if (read.empty) return rb_block_given_p() ? rb_yield_values(0) : Qnil;
1565
1577
  return read.result;
@@ -1596,7 +1608,7 @@ priority_queue_pop_before(VALUE self, VALUE latest_priority)
1596
1608
  priority_queue_read_body((VALUE)&read);
1597
1609
  }
1598
1610
  else {
1599
- priority_queue_call(queue, priority_queue_read_body, (VALUE)&read);
1611
+ priority_queue_call(self, queue, priority_queue_read_body, (VALUE)&read);
1600
1612
  }
1601
1613
  if (read.empty) return rb_block_given_p() ? rb_yield_values(0) : Qnil;
1602
1614
  return read.result;
@@ -1692,7 +1704,7 @@ priority_queue_delete(VALUE self, VALUE priority, VALUE value)
1692
1704
  if (queue->shared) priority_queue_check_shareable(priority);
1693
1705
  if (queue->shared) priority_queue_check_shareable(value);
1694
1706
  return priority_queue_call(
1695
- queue,
1707
+ self, queue,
1696
1708
  priority_queue_delete_body,
1697
1709
  (VALUE)&deletion
1698
1710
  );
@@ -1713,7 +1725,7 @@ priority_queue_delete_match(VALUE self, VALUE priority, VALUE pattern)
1713
1725
  };
1714
1726
  if (queue->shared) priority_queue_check_shareable(priority);
1715
1727
  if (queue->shared) priority_queue_check_shareable(pattern);
1716
- return priority_queue_call(queue, priority_queue_delete_body, (VALUE)&deletion);
1728
+ return priority_queue_call(self, queue, priority_queue_delete_body, (VALUE)&deletion);
1717
1729
  }
1718
1730
 
1719
1731
  static VALUE
@@ -1774,7 +1786,7 @@ priority_queue_delete_identity(VALUE self, VALUE priority, VALUE value)
1774
1786
  if (queue->shared) priority_queue_check_shareable(priority);
1775
1787
  if (queue->shared) priority_queue_check_shareable(value);
1776
1788
  return priority_queue_call(
1777
- queue,
1789
+ self, queue,
1778
1790
  priority_queue_delete_identity_body,
1779
1791
  (VALUE)&deletion
1780
1792
  );
@@ -1799,7 +1811,7 @@ priority_queue_size(VALUE self)
1799
1811
  priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1800
1812
  if (priority_queue_can_run_direct(queue)) return priority_queue_size_body((VALUE)&operation);
1801
1813
  return priority_queue_call(
1802
- queue,
1814
+ self, queue,
1803
1815
  priority_queue_size_body,
1804
1816
  (VALUE)&operation
1805
1817
  );
@@ -1819,7 +1831,7 @@ priority_queue_empty(VALUE self)
1819
1831
  priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1820
1832
  if (priority_queue_can_run_direct(queue)) return priority_queue_empty_body((VALUE)&operation);
1821
1833
  return priority_queue_call(
1822
- queue,
1834
+ self, queue,
1823
1835
  priority_queue_empty_body,
1824
1836
  (VALUE)&operation
1825
1837
  );
@@ -1859,7 +1871,7 @@ priority_queue_seal(VALUE self)
1859
1871
  priority_queue_t *queue = priority_queue_get(self);
1860
1872
  priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1861
1873
  if (priority_queue_can_run_direct(queue)) return priority_queue_seal_body((VALUE)&operation);
1862
- return priority_queue_call(queue, priority_queue_seal_body, (VALUE)&operation);
1874
+ return priority_queue_call(self, queue, priority_queue_seal_body, (VALUE)&operation);
1863
1875
  }
1864
1876
 
1865
1877
  static VALUE
@@ -1869,7 +1881,7 @@ priority_queue_clear(VALUE self)
1869
1881
  priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1870
1882
  if (priority_queue_can_run_direct(queue)) return priority_queue_clear_body((VALUE)&operation);
1871
1883
  return priority_queue_call(
1872
- queue,
1884
+ self, queue,
1873
1885
  priority_queue_clear_body,
1874
1886
  (VALUE)&operation
1875
1887
  );
@@ -1893,7 +1905,7 @@ priority_queue_close(VALUE self)
1893
1905
  priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1894
1906
  if (priority_queue_can_run_direct(queue)) return priority_queue_close_body((VALUE)&operation);
1895
1907
  return priority_queue_call(
1896
- queue,
1908
+ self, queue,
1897
1909
  priority_queue_close_body,
1898
1910
  (VALUE)&operation
1899
1911
  );
@@ -1913,7 +1925,7 @@ priority_queue_closed(VALUE self)
1913
1925
  priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1914
1926
  if (priority_queue_can_run_direct(queue)) return priority_queue_closed_body((VALUE)&operation);
1915
1927
  return priority_queue_call(
1916
- queue,
1928
+ self, queue,
1917
1929
  priority_queue_closed_body,
1918
1930
  (VALUE)&operation
1919
1931
  );
@@ -1932,7 +1944,7 @@ priority_queue_sealed(VALUE self)
1932
1944
  priority_queue_t *queue = priority_queue_get(self);
1933
1945
  priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1934
1946
  if (priority_queue_can_run_direct(queue)) return priority_queue_sealed_body((VALUE)&operation);
1935
- return priority_queue_call(queue, priority_queue_sealed_body, (VALUE)&operation);
1947
+ return priority_queue_call(self, queue, priority_queue_sealed_body, (VALUE)&operation);
1936
1948
  }
1937
1949
 
1938
1950
  static VALUE
@@ -1955,7 +1967,7 @@ priority_queue_generation(VALUE self)
1955
1967
  if (!queue->track_age) return Qnil;
1956
1968
  priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1957
1969
  if (priority_queue_can_run_direct(queue)) return priority_queue_generation_body((VALUE)&operation);
1958
- return priority_queue_call(queue, priority_queue_generation_body, (VALUE)&operation);
1970
+ return priority_queue_call(self, queue, priority_queue_generation_body, (VALUE)&operation);
1959
1971
  }
1960
1972
 
1961
1973
  static VALUE
@@ -1973,7 +1985,7 @@ priority_queue_oldest_enqueued_at(VALUE self)
1973
1985
  if (!queue->track_age) return Qnil;
1974
1986
  priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1975
1987
  if (priority_queue_can_run_direct(queue)) return priority_queue_oldest_body((VALUE)&operation);
1976
- return priority_queue_call(queue, priority_queue_oldest_body, (VALUE)&operation);
1988
+ return priority_queue_call(self, queue, priority_queue_oldest_body, (VALUE)&operation);
1977
1989
  }
1978
1990
 
1979
1991
  static VALUE
@@ -1991,7 +2003,7 @@ priority_queue_oldest_age(VALUE self)
1991
2003
  if (!queue->track_age) return Qnil;
1992
2004
  priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1993
2005
  if (priority_queue_can_run_direct(queue)) return priority_queue_oldest_age_body((VALUE)&operation);
1994
- return priority_queue_call(queue, priority_queue_oldest_age_body, (VALUE)&operation);
2006
+ return priority_queue_call(self, queue, priority_queue_oldest_age_body, (VALUE)&operation);
1995
2007
  }
1996
2008
 
1997
2009
  static VALUE
data/ext/farce/queue.c CHANGED
@@ -198,7 +198,7 @@ queue_compact(void *pointer)
198
198
  if (!queue->values) return;
199
199
  size_t index = queue->head;
200
200
  for (size_t remaining = queue->size; remaining > 0; remaining--) {
201
- queue->values[index] = rb_gc_location(queue->values[index]);
201
+ containers_gc_update(&queue->values[index]);
202
202
  if (++index == queue->storage_capacity) index = 0;
203
203
  }
204
204
  }
@@ -231,7 +231,11 @@ static const rb_data_type_t queue_type = {
231
231
  .dsize = queue_memsize,
232
232
  .dcompact = queue_compact,
233
233
  },
234
- .flags = RUBY_TYPED_FROZEN_SHAREABLE,
234
+ .flags =
235
+ RUBY_TYPED_WB_PROTECTED |
236
+ RUBY_TYPED_FROZEN_SHAREABLE |
237
+ RUBY_TYPED_FREE_IMMEDIATELY |
238
+ FARCE_TYPED_THREAD_SAFE_FREE,
235
239
  };
236
240
 
237
241
  static const rb_data_type_t unshared_queue_type = {
@@ -243,6 +247,7 @@ static const rb_data_type_t unshared_queue_type = {
243
247
  .dcompact = queue_compact,
244
248
  },
245
249
  .parent = &queue_type,
250
+ .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | FARCE_TYPED_THREAD_SAFE_FREE,
246
251
  };
247
252
 
248
253
  /* Unshared queues are confined to one Ractor. These critical sections call no
@@ -662,7 +667,8 @@ queue_capacity(VALUE self)
662
667
  return queue->bounded ? SIZET2NUM(queue->limit) : Qnil;
663
668
  }
664
669
 
665
- /* Called with queue->lock held. The queue is unchanged if allocation fails. */
670
+ /* Called with queue->lock held. The queue is unchanged if allocation fails.
671
+ * Ruby allocators could trigger GC while a peer is blocked on this mutex. */
666
672
  static bool
667
673
  queue_grow(queue_t *queue)
668
674
  {
@@ -921,7 +927,7 @@ queue_push_with_timeout(VALUE self, queue_t *queue, VALUE value, queue_timeout_t
921
927
  queue_unlock(queue);
922
928
  rb_memerror();
923
929
  }
924
- queue->values[queue->tail] = value;
930
+ RB_OBJ_WRITE(self, &queue->values[queue->tail], value);
925
931
  if (queue->track_age) queue->enqueued_at[queue->tail] = monotonic_now();
926
932
  if (++queue->tail == queue->storage_capacity) queue->tail = 0;
927
933
  queue->size++;
data/ext/farce/reactor.c CHANGED
@@ -9,21 +9,46 @@
9
9
  // Tell Ruby's garbage collector which Ruby objects one fiber's wait still needs.
10
10
  static int mark_wait(st_data_t key, st_data_t value, st_data_t arg) {
11
11
  fs_wait *w = (fs_wait *)value;
12
- rb_gc_mark(w->fiber); rb_gc_mark(w->token); rb_gc_mark(w->value);
13
- rb_gc_mark(w->error); rb_gc_mark(w->groups); rb_gc_mark(w->ios);
12
+ rb_gc_mark_movable(w->fiber); rb_gc_mark_movable(w->token); rb_gc_mark_movable(w->value);
13
+ rb_gc_mark_movable(w->error); rb_gc_mark_movable(w->groups); rb_gc_mark_movable(w->ios);
14
14
  return ST_CONTINUE;
15
15
  }
16
16
 
17
17
  // Keep the scheduler's Ruby objects alive, including fibers that are starting or waiting.
18
18
  static void mark(void *data) {
19
19
  fs_state *s = data;
20
- rb_gc_mark(s->root); rb_gc_mark(s->active);
21
- for (fs_admission *a = s->admission; a; a = a->previous) { rb_gc_mark(a->parent); rb_gc_mark(a->child); }
22
- rb_gc_mark(s->owner);
23
- rb_gc_mark(s->backend); rb_gc_mark(s->fallback);
20
+ rb_gc_mark_movable(s->root); rb_gc_mark_movable(s->active);
21
+ for (fs_admission *a = s->admission; a; a = a->previous) { rb_gc_mark_movable(a->parent); rb_gc_mark_movable(a->child); }
22
+ rb_gc_mark_movable(s->owner);
23
+ rb_gc_mark_movable(s->backend); rb_gc_mark_movable(s->fallback);
24
24
  if (s->fibers) st_foreach(s->fibers, mark_wait, 0);
25
25
  }
26
26
 
27
+ // Update wait references and the table's fiber identity keys together.
28
+ static int compact_wait(st_data_t key, st_data_t value, st_data_t arg, int error) {
29
+ fs_wait *w = (fs_wait *)value;
30
+ containers_gc_update(&w->fiber); containers_gc_update(&w->token);
31
+ containers_gc_update(&w->value); containers_gc_update(&w->error);
32
+ containers_gc_update(&w->groups); containers_gc_update(&w->ios);
33
+ return key == (st_data_t)w->fiber ? ST_CONTINUE : ST_REPLACE;
34
+ }
35
+
36
+ static int compact_wait_key(st_data_t *key, st_data_t *value, st_data_t arg, int existing) {
37
+ *key = (st_data_t)((fs_wait *)*value)->fiber;
38
+ return ST_CONTINUE;
39
+ }
40
+
41
+ static void compact(void *data) {
42
+ fs_state *s = data;
43
+ containers_gc_update(&s->root); containers_gc_update(&s->active);
44
+ containers_gc_update(&s->owner); containers_gc_update(&s->backend);
45
+ containers_gc_update(&s->fallback);
46
+ for (fs_admission *a = s->admission; a; a = a->previous) {
47
+ containers_gc_update(&a->parent); containers_gc_update(&a->child);
48
+ }
49
+ if (s->fibers) st_foreach_with_replace(s->fibers, compact_wait, compact_wait_key, 0);
50
+ }
51
+
27
52
  // Free a fiber's wait record and any extra IO watches it owns.
28
53
  // The first watch is stored inside the record and needs no separate free.
29
54
  static int free_wait(st_data_t key, st_data_t value, st_data_t arg) {
@@ -97,8 +122,8 @@ static size_t memsize(const void *data) {
97
122
  // Describe how Ruby should mark, free, and measure the native scheduler object.
98
123
  const rb_data_type_t fs_type = {
99
124
  .wrap_struct_name = "Farce::Internal::FiberScheduler",
100
- .function = {.dmark = mark, .dfree = free_state, .dsize = memsize},
101
- .flags = RUBY_TYPED_FREE_IMMEDIATELY
125
+ .function = {.dmark = mark, .dfree = free_state, .dsize = memsize, .dcompact = compact},
126
+ .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | FARCE_TYPED_THREAD_SAFE_FREE
102
127
  };
103
128
 
104
129
  // Create an empty scheduler in a closed state.
@@ -128,8 +153,8 @@ static VALUE initialize(int argc, VALUE *argv, VALUE self) {
128
153
  }
129
154
  fs_state *s = fs_get(self);
130
155
  if (!NIL_P(s->owner)) rb_raise(rb_eRuntimeError, "scheduler already initialized");
131
- s->owner = rb_thread_current();
132
- s->root = rb_fiber_current();
156
+ RB_OBJ_WRITE(self, &s->owner, rb_thread_current());
157
+ RB_OBJ_WRITE(self, &s->root, rb_fiber_current());
133
158
  s->fibers = st_init_numtable();
134
159
  int driver = 0;
135
160
  if (backend == ID2SYM(rb_intern("auto"))) {
@@ -144,7 +169,7 @@ static VALUE initialize(int argc, VALUE *argv, VALUE self) {
144
169
  else rb_raise(rb_eArgError, "unknown native scheduler backend");
145
170
  int error = fs_driver_init(s, driver);
146
171
  if (error) rb_syserr_fail(error, "requested fiber scheduler backend is unavailable");
147
- s->backend = backend;
172
+ RB_OBJ_WRITE(self, &s->backend, backend);
148
173
  if (s->descriptor >= 0) fcntl(s->descriptor, F_SETFD, FD_CLOEXEC);
149
174
  if (pipe(s->wake) < 0) rb_sys_fail("scheduler wake pipe");
150
175
  for (int i = 0; i < 2; i++) {
@@ -212,7 +237,7 @@ VALUE fs_arm_checked(VALUE self) {
212
237
  fs_slot *slot = &s->slots[w->slot];
213
238
  if (slot->generation == (UINT64_MAX >> (FS_SLOT_BITS + 2))) rb_raise(rb_eRangeError, "scheduler generation exhausted");
214
239
  VALUE token = ULL2NUM((++slot->generation << FS_SLOT_BITS) | w->slot);
215
- w->token = token; w->status = w->events = w->selected = w->native_error = 0;
240
+ RB_OBJ_WRITE(self, &w->token, token); w->status = w->events = w->selected = w->native_error = 0;
216
241
  w->value = w->error = w->groups = w->ios = Qnil;
217
242
  s->pending++;
218
243
  return token;
@@ -257,7 +282,7 @@ static fs_wait *ready_pop(fs_state *s) {
257
282
  VALUE fs_resume(VALUE self, VALUE token, VALUE value, VALUE error) {
258
283
  fs_state *s = fs_get(self); fs_wait *w = fs_lookup(s, token);
259
284
  if (!w || w->status != 0) return Qfalse;
260
- w->status = 1; w->value = value; w->error = error;
285
+ w->status = 1; RB_OBJ_WRITE(self, &w->value, value); RB_OBJ_WRITE(self, &w->error, error);
261
286
  ready_push(s, w); return Qtrue;
262
287
  }
263
288
 
@@ -273,7 +298,7 @@ static VALUE interrupt(VALUE self, VALUE token, VALUE error) {
273
298
  fs_state *s = fs_get(self); fs_wait *w = fs_lookup(s, token);
274
299
  if (!w || w->status == 2) return Qfalse;
275
300
  if (!w->status) ready_push(s, w);
276
- w->status = 1; w->error = error; return Qtrue;
301
+ w->status = 1; RB_OBJ_WRITE(self, &w->error, error); return Qtrue;
277
302
  }
278
303
 
279
304
  // Declare the closed-IO check used below to detect descriptor reuse.
@@ -359,14 +384,14 @@ VALUE fs_retire(VALUE self, VALUE token) {
359
384
  // Switch from the root to a ready fiber.
360
385
  // If a newly started child finishes, return control to its waiting parent
361
386
  // before returning to the root dispatcher.
362
- static VALUE transfer_to(fs_state *s, VALUE fiber) {
363
- s->active = fiber;
387
+ static VALUE transfer_to(VALUE self, fs_state *s, VALUE fiber) {
388
+ RB_OBJ_WRITE(self, &s->active, fiber);
364
389
  VALUE result = rb_fiber_transfer(fiber, 0, NULL);
365
390
  s->active = Qnil;
366
391
  // A transferred fiber returns to the thread root when its block finishes.
367
392
  // Complete nested immediate admissions before returning to the driver.
368
393
  while (s->admission && s->admission->parent != s->root) {
369
- s->active = s->admission->parent;
394
+ RB_OBJ_WRITE(self, &s->active, s->admission->parent);
370
395
  result = rb_fiber_transfer(s->admission->parent, 0, NULL);
371
396
  s->active = Qnil;
372
397
  }
@@ -377,8 +402,8 @@ static VALUE transfer_to(fs_state *s, VALUE fiber) {
377
402
  static VALUE start_body(VALUE data) {
378
403
  VALUE *args = (VALUE *)data;
379
404
  fs_state *s = fs_get(args[0]);
380
- if (rb_fiber_current() == s->root) return transfer_to(s, args[1]);
381
- s->active = args[1];
405
+ if (rb_fiber_current() == s->root) return transfer_to(args[0], s, args[1]);
406
+ RB_OBJ_WRITE(args[0], &s->active, args[1]);
382
407
  return rb_fiber_transfer(args[1], 0, NULL);
383
408
  }
384
409
 
@@ -387,7 +412,7 @@ static VALUE start_body(VALUE data) {
387
412
  static VALUE start_cleanup(VALUE data) {
388
413
  VALUE *args = (VALUE *)data;
389
414
  fs_state *s = fs_get(args[0]);
390
- s->active = s->admission->parent == s->root ? Qnil : s->admission->parent;
415
+ RB_OBJ_WRITE(args[0], &s->active, s->admission->parent == s->root ? Qnil : s->admission->parent);
391
416
  s->admission = s->admission->previous;
392
417
  return Qnil;
393
418
  }
@@ -410,9 +435,11 @@ static VALUE start_fiber(VALUE self, VALUE fiber) {
410
435
  index = s->slot_count++;
411
436
  }
412
437
  fs_wait *w = ZALLOC(fs_wait); w->slot = index; s->slots[index].wait = w;
413
- w->fiber = fiber; w->token = w->value = w->error = w->groups = w->ios = Qnil;
438
+ RB_OBJ_WRITE(self, &w->fiber, fiber); w->token = w->value = w->error = w->groups = w->ios = Qnil;
414
439
  st_insert(s->fibers, (st_data_t)fiber, (st_data_t)w);
415
440
  fs_admission admission = {rb_fiber_current(), fiber, s->admission};
441
+ RB_OBJ_WRITTEN(self, Qundef, admission.parent);
442
+ RB_OBJ_WRITTEN(self, Qundef, admission.child);
416
443
  s->admission = &admission;
417
444
  VALUE args[] = {self, fiber};
418
445
  return rb_ensure(start_body, (VALUE)args, start_cleanup, (VALUE)args);
@@ -441,9 +468,9 @@ static VALUE park_body(VALUE data) {
441
468
  break;
442
469
  }
443
470
  }
444
- s->active = next == s->root ? Qnil : next;
471
+ RB_OBJ_WRITE(args[0], &s->active, next == s->root ? Qnil : next);
445
472
  rb_fiber_transfer(next, 0, NULL);
446
- s->active = current;
473
+ RB_OBJ_WRITE(args[0], &s->active, current);
447
474
  fs_wait *w = fs_lookup(fs_get(args[0]), args[1]);
448
475
  if (!w) rb_raise(rb_eRuntimeError, "retired scheduler suspension");
449
476
  if (!NIL_P(w->error)) rb_exc_raise(w->error);
@@ -486,7 +513,7 @@ VALUE fs_checkpoint(VALUE self, VALUE value) {
486
513
  // Keep the original groups so the result can return the caller's objects.
487
514
  static VALUE register_select(VALUE self, VALUE token, VALUE groups, VALUE ios) {
488
515
  fs_state *s = fs_get(self); fs_wait *w = fs_lookup(s, token);
489
- w->groups = groups; w->ios = ios;
516
+ RB_OBJ_WRITE(self, &w->groups, groups); RB_OBJ_WRITE(self, &w->ios, ios);
490
517
  for (int i = 0; i < 3; i++) {
491
518
  VALUE list = rb_ary_entry(ios, i);
492
519
  for (long j = 0; j < RARRAY_LEN(list); j++)
@@ -728,7 +755,7 @@ drain_ready:;
728
755
  if (w->status != 1) continue;
729
756
  w->status = 2;
730
757
  s->dispatch_left--;
731
- transfer_to(s, w->fiber);
758
+ transfer_to(self, s, w->fiber);
732
759
  }
733
760
  int count = limit - s->dispatch_left;
734
761
  s->dispatch_left = 0;
data/ext/farce/reactor.h CHANGED
@@ -1,6 +1,6 @@
1
1
  #ifndef FARCE_FIBER_REACTOR_H
2
2
  #define FARCE_FIBER_REACTOR_H
3
- #include <ruby.h>
3
+ #include "containers.h"
4
4
  #include <ruby/io.h>
5
5
  #include <ruby/io/buffer.h>
6
6
  #include <ruby/thread.h>
data/ext/farce/signal.c CHANGED
@@ -119,7 +119,11 @@ static const rb_data_type_t containers_signal_type = {
119
119
  .dsize = containers_signal_memsize,
120
120
  .dcompact = NULL,
121
121
  },
122
- .flags = RUBY_TYPED_FROZEN_SHAREABLE,
122
+ .flags =
123
+ RUBY_TYPED_WB_PROTECTED |
124
+ RUBY_TYPED_FROZEN_SHAREABLE |
125
+ RUBY_TYPED_FREE_IMMEDIATELY |
126
+ FARCE_TYPED_THREAD_SAFE_FREE,
123
127
  };
124
128
 
125
129
  static VALUE