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
data/ext/farce/weak_map.c CHANGED
@@ -84,6 +84,7 @@ typedef struct {
84
84
  } weak_map_timeout_t;
85
85
 
86
86
  typedef struct {
87
+ VALUE self;
87
88
  VALUE fiber;
88
89
  VALUE thread;
89
90
  VALUE scheduler;
@@ -186,21 +187,21 @@ static void
186
187
  weak_map_compact(void *pointer)
187
188
  {
188
189
  weak_map_t *map = pointer;
189
- map->comparing_owner = rb_gc_location(map->comparing_owner);
190
- map->comparing_thread = rb_gc_location(map->comparing_thread);
190
+ containers_gc_update(&map->comparing_owner);
191
+ containers_gc_update(&map->comparing_thread);
191
192
  for (weak_map_reservation_t *entry = map->reservations; entry; entry = entry->next) {
192
- entry->key = rb_gc_location(entry->key);
193
- entry->current = rb_gc_location(entry->current);
194
- entry->fiber = rb_gc_location(entry->fiber);
195
- entry->thread = rb_gc_location(entry->thread);
193
+ containers_gc_update(&entry->key);
194
+ containers_gc_update(&entry->current);
195
+ containers_gc_update(&entry->fiber);
196
+ containers_gc_update(&entry->thread);
196
197
  }
197
198
  if (!map->slots) return;
198
199
 
199
200
  for (size_t index = 0; index < map->capacity; index++) {
200
201
  weak_map_slot_t *slot = &map->slots[index];
201
202
  if (slot->state != WEAK_MAP_OCCUPIED) continue;
202
- slot->key = rb_gc_location(slot->key);
203
- slot->value = rb_gc_location(slot->value);
203
+ containers_gc_update(&slot->key);
204
+ containers_gc_update(&slot->value);
204
205
  }
205
206
  }
206
207
 
@@ -257,7 +258,11 @@ static const rb_data_type_t weak_map_type = {
257
258
  .dcompact = weak_map_compact,
258
259
  .handle_weak_references = weak_map_handle_weak_references,
259
260
  },
260
- .flags = RUBY_TYPED_FROZEN_SHAREABLE,
261
+ .flags =
262
+ RUBY_TYPED_WB_PROTECTED |
263
+ RUBY_TYPED_FROZEN_SHAREABLE |
264
+ RUBY_TYPED_FREE_IMMEDIATELY |
265
+ FARCE_TYPED_THREAD_SAFE_FREE,
261
266
  };
262
267
 
263
268
  static void
@@ -273,6 +278,8 @@ weak_map_validate_references(VALUE self)
273
278
  }
274
279
  }
275
280
 
281
+ /* Buffer growth also runs under the state mutex. Keep the allocator pair
282
+ * consistent and avoid Ruby allocators that can trigger GC in that section. */
276
283
  static weak_map_slot_t *
277
284
  weak_map_allocate_slots(size_t capacity)
278
285
  {
@@ -469,8 +476,8 @@ weak_map_keys_equal(
469
476
  int state = 0;
470
477
 
471
478
  map->comparing = true;
472
- map->comparing_owner = execution->fiber;
473
- map->comparing_thread = execution->thread;
479
+ RB_OBJ_WRITE(execution->self, &map->comparing_owner, execution->fiber);
480
+ RB_OBJ_WRITE(execution->self, &map->comparing_thread, execution->thread);
474
481
  pthread_mutex_unlock(&map->lock);
475
482
 
476
483
  VALUE result = rb_protect(weak_map_eql_protected, (VALUE)&arguments, &state);
@@ -558,11 +565,11 @@ weak_map_begin_reservation_locked(
558
565
  st_index_t hash, const weak_map_execution_context_t *execution
559
566
  )
560
567
  {
561
- entry->key = key;
562
- entry->current = current;
568
+ RB_OBJ_WRITE(execution->self, &entry->key, key);
569
+ RB_OBJ_WRITE(execution->self, &entry->current, current);
563
570
  entry->hash = hash;
564
- entry->fiber = execution->fiber;
565
- entry->thread = execution->thread;
571
+ RB_OBJ_WRITE(execution->self, &entry->fiber, execution->fiber);
572
+ RB_OBJ_WRITE(execution->self, &entry->thread, execution->thread);
566
573
  entry->invalidated = false;
567
574
  entry->next = map->reservations;
568
575
  map->reservations = entry;
@@ -726,7 +733,7 @@ weak_map_initialize_entry(VALUE key, VALUE value, VALUE opaque)
726
733
  key = weak_map_prepare_key(context->map, key);
727
734
  containers_check_shareable(value);
728
735
  st_index_t hash = weak_map_key_hash(context->map, key);
729
- weak_map_execution_context_t execution;
736
+ weak_map_execution_context_t execution = {.self = context->self};
730
737
  weak_map_lock_state(context->map, &execution);
731
738
  weak_map_store_locked(context->self, context->map, key, value, hash, &execution);
732
739
  pthread_mutex_unlock(&context->map->lock);
@@ -782,7 +789,7 @@ weak_map_get(VALUE self, VALUE key)
782
789
  st_index_t hash = weak_map_key_hash(map, key);
783
790
  bool found;
784
791
  VALUE result = Qnil;
785
- weak_map_execution_context_t execution;
792
+ weak_map_execution_context_t execution = {.self = self};
786
793
  weak_map_lock_state(map, &execution);
787
794
  size_t index = weak_map_find_slot(map, key, hash, &found, &execution);
788
795
  if (found) result = map->slots[index].value;
@@ -827,7 +834,7 @@ weak_map_fetch(int argc, VALUE *argv, VALUE self)
827
834
  st_index_t hash = weak_map_key_hash(map, key);
828
835
  bool found;
829
836
  VALUE result = Qnil;
830
- weak_map_execution_context_t execution;
837
+ weak_map_execution_context_t execution = {.self = self};
831
838
  weak_map_lock_state(map, &execution);
832
839
  size_t index = weak_map_find_slot(map, key, hash, &found, &execution);
833
840
  if (found) result = map->slots[index].value;
@@ -849,7 +856,7 @@ weak_map_set(VALUE self, VALUE key, VALUE value)
849
856
  containers_check_shareable(value);
850
857
  st_index_t hash = weak_map_key_hash(map, key);
851
858
  rb_check_frozen(self);
852
- weak_map_execution_context_t execution;
859
+ weak_map_execution_context_t execution = {.self = self};
853
860
  weak_map_lock_for_key(map, key, hash, &execution);
854
861
  weak_map_store_locked(self, map, key, value, hash, &execution);
855
862
  pthread_mutex_unlock(&map->lock);
@@ -863,7 +870,7 @@ weak_map_key_p(VALUE self, VALUE key)
863
870
  key = weak_map_prepare_key(map, key);
864
871
  st_index_t hash = weak_map_key_hash(map, key);
865
872
  bool found;
866
- weak_map_execution_context_t execution;
873
+ weak_map_execution_context_t execution = {.self = self};
867
874
  weak_map_lock_state(map, &execution);
868
875
  (void)weak_map_find_slot(map, key, hash, &found, &execution);
869
876
  pthread_mutex_unlock(&map->lock);
@@ -878,7 +885,7 @@ weak_map_getkey(VALUE self, VALUE key)
878
885
  st_index_t hash = weak_map_key_hash(map, key);
879
886
  bool found;
880
887
  VALUE result = Qnil;
881
- weak_map_execution_context_t execution;
888
+ weak_map_execution_context_t execution = {.self = self};
882
889
  weak_map_lock_state(map, &execution);
883
890
  size_t index = weak_map_find_slot(map, key, hash, &found, &execution);
884
891
  if (found) result = map->compare_keys_by_identity ? key : map->slots[index].key;
@@ -896,7 +903,7 @@ weak_map_delete(VALUE self, VALUE key)
896
903
  rb_check_frozen(self);
897
904
  bool found;
898
905
  VALUE result = Qnil;
899
- weak_map_execution_context_t execution;
906
+ weak_map_execution_context_t execution = {.self = self};
900
907
  weak_map_lock_for_key(map, key, hash, &execution);
901
908
  size_t index = weak_map_find_slot(map, key, hash, &found, &execution);
902
909
  if (RB_OBJ_FROZEN(self)) {
@@ -922,7 +929,7 @@ weak_map_clear(VALUE self)
922
929
  {
923
930
  rb_check_frozen(self);
924
931
  weak_map_t *map = get_weak_map(self);
925
- weak_map_execution_context_t execution;
932
+ weak_map_execution_context_t execution = {.self = self};
926
933
  weak_map_lock_state(map, &execution);
927
934
  for (weak_map_reservation_t *entry = map->reservations; entry; entry = entry->next) {
928
935
  entry->invalidated = true;
@@ -961,7 +968,7 @@ weak_map_operation_cleanup(VALUE opaque)
961
968
  {
962
969
  weak_map_operation_t *operation = (weak_map_operation_t *)opaque;
963
970
  if (!operation->complete) {
964
- weak_map_execution_context_t execution;
971
+ weak_map_execution_context_t execution = {.self = operation->self};
965
972
  weak_map_lock_state(operation->map, &execution);
966
973
  weak_map_finish_reservation_locked(operation->map, &operation->reservation);
967
974
  weak_map_notify_waiters_locked(operation->map);
@@ -977,7 +984,7 @@ weak_map_store_body(VALUE opaque)
977
984
  VALUE result = rb_yield_values(0);
978
985
  containers_check_shareable(result);
979
986
  rb_check_frozen(operation->self);
980
- weak_map_execution_context_t execution;
987
+ weak_map_execution_context_t execution = {.self = operation->self};
981
988
  weak_map_lock_state(operation->map, &execution);
982
989
  bool stored = !operation->reservation.invalidated;
983
990
  if (stored) {
@@ -1022,7 +1029,7 @@ weak_map_store_if_absent(int argc, VALUE *argv, VALUE self)
1022
1029
  keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1023
1030
  );
1024
1031
 
1025
- weak_map_execution_context_t execution;
1032
+ weak_map_execution_context_t execution = {.self = self};
1026
1033
  if (!weak_map_lock_for_key_with_timeout(map, key, operation.hash, &timeout, &execution)) return Qnil;
1027
1034
  bool found;
1028
1035
  size_t index = weak_map_find_slot(map, key, operation.hash, &found, &execution);
@@ -1051,7 +1058,7 @@ weak_map_cas_body(VALUE opaque)
1051
1058
  : RTEST(rb_equal(operation->current, operation->argument));
1052
1059
  rb_check_frozen(operation->self);
1053
1060
 
1054
- weak_map_execution_context_t execution;
1061
+ weak_map_execution_context_t execution = {.self = operation->self};
1055
1062
  weak_map_lock_state(operation->map, &execution);
1056
1063
  matches = matches && !operation->reservation.invalidated;
1057
1064
  if (matches) {
@@ -1103,7 +1110,7 @@ weak_map_compare_and_set(int argc, VALUE *argv, VALUE self)
1103
1110
  keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1104
1111
  );
1105
1112
 
1106
- weak_map_execution_context_t execution;
1113
+ weak_map_execution_context_t execution = {.self = self};
1107
1114
  if (!weak_map_lock_for_key_with_timeout(map, key, operation.hash, &timeout, &execution)) return Qfalse;
1108
1115
  bool found;
1109
1116
  size_t index = weak_map_find_slot(map, key, operation.hash, &found, &execution);
@@ -1133,7 +1140,7 @@ weak_map_modify_body(VALUE opaque)
1133
1140
  bool store = !remove && value != weak_map_keep;
1134
1141
  if (store) containers_check_shareable(value);
1135
1142
  rb_check_frozen(operation->self);
1136
- weak_map_execution_context_t execution;
1143
+ weak_map_execution_context_t execution = {.self = operation->self};
1137
1144
  weak_map_lock_state(operation->map, &execution);
1138
1145
  bool changed = false;
1139
1146
  if (store && !operation->reservation.invalidated) {
@@ -1180,7 +1187,7 @@ weak_map_modify(VALUE self, VALUE key)
1180
1187
  };
1181
1188
  operation.hash = weak_map_key_hash(map, key);
1182
1189
  rb_check_frozen(self);
1183
- weak_map_execution_context_t execution;
1190
+ weak_map_execution_context_t execution = {.self = self};
1184
1191
  weak_map_lock_for_key(map, key, operation.hash, &execution);
1185
1192
  bool found;
1186
1193
  size_t index = weak_map_find_slot(map, key, operation.hash, &found, &execution);
@@ -1204,7 +1211,7 @@ weak_map_upsert_body(VALUE opaque)
1204
1211
  VALUE result = rb_yield(operation->current);
1205
1212
  containers_check_shareable(result);
1206
1213
  rb_check_frozen(operation->self);
1207
- weak_map_execution_context_t execution;
1214
+ weak_map_execution_context_t execution = {.self = operation->self};
1208
1215
  weak_map_lock_state(operation->map, &execution);
1209
1216
  bool stored = !operation->reservation.invalidated;
1210
1217
  if (stored) {
@@ -1251,7 +1258,7 @@ weak_map_upsert(int argc, VALUE *argv, VALUE self)
1251
1258
  keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1252
1259
  );
1253
1260
 
1254
- weak_map_execution_context_t execution;
1261
+ weak_map_execution_context_t execution = {.self = self};
1255
1262
  if (!weak_map_lock_for_key_with_timeout(map, key, operation.hash, &timeout, &execution)) return Qnil;
1256
1263
  bool found;
1257
1264
  size_t index = weak_map_find_slot(map, key, operation.hash, &found, &execution);
@@ -1301,7 +1308,7 @@ weak_map_size(VALUE self)
1301
1308
  {
1302
1309
  weak_map_t *map = get_weak_map(self);
1303
1310
  size_t size;
1304
- weak_map_execution_context_t execution;
1311
+ weak_map_execution_context_t execution = {.self = self};
1305
1312
  weak_map_lock_state(map, &execution);
1306
1313
  size = map->size;
1307
1314
  pthread_mutex_unlock(&map->lock);
@@ -1317,32 +1324,37 @@ weak_map_enumerator_size(VALUE self, VALUE arguments, VALUE enumerator)
1317
1324
  }
1318
1325
 
1319
1326
  static VALUE
1320
- weak_map_entries_snapshot(weak_map_t *map)
1327
+ weak_map_entries_snapshot(VALUE self, weak_map_t *map)
1321
1328
  {
1322
1329
  for (;;) {
1323
1330
  size_t capacity;
1324
- weak_map_execution_context_t execution;
1331
+ weak_map_execution_context_t execution = {.self = self};
1325
1332
  weak_map_lock_state(map, &execution);
1326
1333
  capacity = map->size;
1327
1334
  pthread_mutex_unlock(&map->lock);
1328
1335
 
1329
1336
  VALUE entries = rb_ary_new_capa((long)(capacity * 2));
1330
- rb_ary_resize(entries, (long)(capacity * 2));
1331
1337
  weak_map_lock_state(map, &execution);
1332
1338
  if (map->size > capacity) {
1333
1339
  pthread_mutex_unlock(&map->lock);
1334
1340
  continue;
1335
1341
  }
1336
- VALUE *entry_slots = RARRAY_PTR(entries);
1337
- long entry_count = 0;
1342
+ /* The private array has capacity for every pair. Bulk appends cannot
1343
+ * grow it or trigger GC while the state mutex is held. */
1344
+ VALUE batch[64];
1345
+ long length = 0;
1338
1346
  for (size_t index = 0; index < map->capacity; index++) {
1339
1347
  weak_map_slot_t *slot = &map->slots[index];
1340
1348
  if (slot->state != WEAK_MAP_OCCUPIED) continue;
1341
- RB_OBJ_WRITE(entries, &entry_slots[entry_count++], slot->key);
1342
- RB_OBJ_WRITE(entries, &entry_slots[entry_count++], slot->value);
1349
+ batch[length++] = slot->key;
1350
+ batch[length++] = slot->value;
1351
+ if (length == 64) {
1352
+ rb_ary_cat(entries, batch, length);
1353
+ length = 0;
1354
+ }
1343
1355
  }
1356
+ if (length) rb_ary_cat(entries, batch, length);
1344
1357
  pthread_mutex_unlock(&map->lock);
1345
- rb_ary_resize(entries, entry_count);
1346
1358
  return entries;
1347
1359
  }
1348
1360
  }
@@ -1350,11 +1362,11 @@ weak_map_entries_snapshot(weak_map_t *map)
1350
1362
  static VALUE
1351
1363
  weak_map_keys(VALUE self)
1352
1364
  {
1353
- VALUE entries = weak_map_entries_snapshot(get_weak_map(self));
1365
+ VALUE entries = weak_map_entries_snapshot(self, get_weak_map(self));
1354
1366
  long entry_count = RARRAY_LEN(entries);
1355
1367
  VALUE keys = rb_ary_new_capa(entry_count / 2);
1356
1368
  for (long index = 0; index < entry_count; index += 2) {
1357
- rb_ary_push(keys, RARRAY_AREF(entries, index));
1369
+ rb_ary_push(keys, rb_ary_entry(entries, index));
1358
1370
  }
1359
1371
  RB_GC_GUARD(entries);
1360
1372
  return keys;
@@ -1364,10 +1376,10 @@ static VALUE
1364
1376
  weak_map_each(VALUE self)
1365
1377
  {
1366
1378
  RETURN_SIZED_ENUMERATOR(self, 0, NULL, weak_map_enumerator_size);
1367
- VALUE entries = weak_map_entries_snapshot(get_weak_map(self));
1379
+ VALUE entries = weak_map_entries_snapshot(self, get_weak_map(self));
1368
1380
  long entry_count = RARRAY_LEN(entries);
1369
1381
  for (long index = 0; index < entry_count; index += 2) {
1370
- rb_yield(rb_assoc_new(RARRAY_AREF(entries, index), RARRAY_AREF(entries, index + 1)));
1382
+ rb_yield(rb_assoc_new(rb_ary_entry(entries, index), rb_ary_entry(entries, index + 1)));
1371
1383
  }
1372
1384
  RB_GC_GUARD(entries);
1373
1385
  return self;
@@ -1381,18 +1393,17 @@ weak_map_each_live(VALUE self)
1381
1393
  {
1382
1394
  RETURN_SIZED_ENUMERATOR(self, 0, NULL, weak_map_enumerator_size);
1383
1395
  weak_map_t *map = get_weak_map(self);
1384
- weak_map_execution_context_t execution;
1396
+ weak_map_execution_context_t execution = {.self = self};
1385
1397
  size_t cursor = 0;
1386
1398
  uint64_t epoch = 0;
1387
1399
  bool started = false;
1388
1400
  const long batch_slots = 64;
1389
- VALUE batch = rb_ary_new_capa(batch_slots);
1390
- rb_ary_resize(batch, batch_slots);
1401
+ /* C-stack roots keep this bounded batch alive across Ruby yields. */
1402
+ VALUE batch[64];
1391
1403
 
1392
1404
  for (;;) {
1393
- VALUE *storage = RARRAY_PTR(batch);
1394
1405
  for (long index = 0; index < batch_slots; index++) {
1395
- RB_OBJ_WRITE(batch, &storage[index], Qnil);
1406
+ batch[index] = Qnil;
1396
1407
  }
1397
1408
  weak_map_lock_state(map, &execution);
1398
1409
  if (started && epoch != map->iteration_epoch) {
@@ -1405,16 +1416,17 @@ weak_map_each_live(VALUE self)
1405
1416
  while (cursor < map->capacity && length < batch_slots) {
1406
1417
  weak_map_slot_t *slot = &map->slots[cursor++];
1407
1418
  if (slot->state != WEAK_MAP_OCCUPIED) continue;
1408
- RB_OBJ_WRITE(batch, &storage[length++], slot->key);
1409
- RB_OBJ_WRITE(batch, &storage[length++], slot->value);
1419
+ batch[length++] = slot->key;
1420
+ batch[length++] = slot->value;
1410
1421
  }
1411
1422
  pthread_mutex_unlock(&map->lock);
1412
1423
  if (length == 0) break;
1413
1424
  for (long index = 0; index < length; index += 2) {
1414
- rb_yield(rb_assoc_new(RARRAY_AREF(batch, index), RARRAY_AREF(batch, index + 1)));
1425
+ rb_yield(rb_assoc_new(batch[index], batch[index + 1]));
1415
1426
  }
1416
1427
  }
1417
- RB_GC_GUARD(batch);
1428
+ RB_GC_GUARD(batch[0]);
1429
+ RB_GC_GUARD(batch[63]);
1418
1430
  return self;
1419
1431
  }
1420
1432
 
@@ -1422,10 +1434,10 @@ static VALUE
1422
1434
  weak_map_each_key(VALUE self)
1423
1435
  {
1424
1436
  RETURN_SIZED_ENUMERATOR(self, 0, NULL, weak_map_enumerator_size);
1425
- VALUE entries = weak_map_entries_snapshot(get_weak_map(self));
1437
+ VALUE entries = weak_map_entries_snapshot(self, get_weak_map(self));
1426
1438
  long entry_count = RARRAY_LEN(entries);
1427
1439
  for (long index = 0; index < entry_count; index += 2) {
1428
- rb_yield(RARRAY_AREF(entries, index));
1440
+ rb_yield(rb_ary_entry(entries, index));
1429
1441
  }
1430
1442
  RB_GC_GUARD(entries);
1431
1443
  return self;
@@ -1435,10 +1447,10 @@ static VALUE
1435
1447
  weak_map_each_value(VALUE self)
1436
1448
  {
1437
1449
  RETURN_SIZED_ENUMERATOR(self, 0, NULL, weak_map_enumerator_size);
1438
- VALUE entries = weak_map_entries_snapshot(get_weak_map(self));
1450
+ VALUE entries = weak_map_entries_snapshot(self, get_weak_map(self));
1439
1451
  long entry_count = RARRAY_LEN(entries);
1440
1452
  for (long index = 1; index < entry_count; index += 2) {
1441
- rb_yield(RARRAY_AREF(entries, index));
1453
+ rb_yield(rb_ary_entry(entries, index));
1442
1454
  }
1443
1455
  RB_GC_GUARD(entries);
1444
1456
  return self;
@@ -1460,7 +1472,7 @@ weak_map_get_with_timeout(int argc, VALUE *argv, VALUE self)
1460
1472
  weak_map_timeout_t timeout = weak_map_parse_timeout(
1461
1473
  keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1462
1474
  );
1463
- weak_map_execution_context_t execution;
1475
+ weak_map_execution_context_t execution = {.self = self};
1464
1476
  if (!weak_map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
1465
1477
  return weak_map_timeout_result();
1466
1478
  }
@@ -1492,7 +1504,7 @@ weak_map_store_with_timeout(int argc, VALUE *argv, VALUE self)
1492
1504
  weak_map_timeout_t timeout = weak_map_parse_timeout(
1493
1505
  keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1494
1506
  );
1495
- weak_map_execution_context_t execution;
1507
+ weak_map_execution_context_t execution = {.self = self};
1496
1508
  if (!weak_map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
1497
1509
  return weak_map_timeout_result();
1498
1510
  }
@@ -1521,7 +1533,7 @@ weak_map_swap(int argc, VALUE *argv, VALUE self)
1521
1533
  weak_map_timeout_t timeout = weak_map_parse_timeout(
1522
1534
  keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1523
1535
  );
1524
- weak_map_execution_context_t execution;
1536
+ weak_map_execution_context_t execution = {.self = self};
1525
1537
  if (!weak_map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
1526
1538
  return weak_map_timeout_result();
1527
1539
  }
@@ -1568,7 +1580,7 @@ weak_map_wait_for_value(int argc, VALUE *argv, VALUE self, bool non_nil)
1568
1580
  bool found;
1569
1581
  VALUE current = Qnil;
1570
1582
  uint64_t generation;
1571
- weak_map_execution_context_t execution;
1583
+ weak_map_execution_context_t execution = {.self = self};
1572
1584
  weak_map_lock_state(map, &execution);
1573
1585
  weak_map_reservation_t *entry = weak_map_find_reservation_locked(map, key, hash, &execution);
1574
1586
  size_t index = weak_map_find_slot(map, key, hash, &found, &execution);
@@ -1628,7 +1640,7 @@ weak_map_update(int argc, VALUE *argv, VALUE self)
1628
1640
  keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1629
1641
  );
1630
1642
 
1631
- weak_map_execution_context_t execution;
1643
+ weak_map_execution_context_t execution = {.self = self};
1632
1644
  if (!weak_map_lock_for_key_with_timeout(map, key, operation.hash, &timeout, &execution)) return Qnil;
1633
1645
  bool found;
1634
1646
  size_t index = weak_map_find_slot(map, key, operation.hash, &found, &execution);
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
1
5
  # @!macro unsafe
2
6
  # @note
3
7
  # Instances of this class are **not thread-safe** and should not be shared concurrently.
@@ -11,12 +15,22 @@
11
15
 
12
16
  # @!macro modes
13
17
  # Valid modes are:
14
- # * `:copy` - The value will be copied between Ractors. This is the default mode.
18
+ # * `:smart` - Applies smart rules or a dedicated proxy. Raises for unsupported values.
19
+ # This is the default for most mode-backed classes.
20
+ # * `:smart_raise` - Applies smart rules or a dedicated proxy, with a raising fallback.
21
+ # * `:smart_copy` - Applies smart rules or a dedicated proxy, then falls back to copying or Marshal.
22
+ # This is the default for {Farce::Port}.
23
+ # * `:smart_move` - Applies smart rules or a dedicated proxy, then falls back to moving.
24
+ # * `:copy` - The value will be copied between Ractors.
25
+ # * `:marshal` - The value will be serialized immediately and reconstructed once per Ractor
26
+ # with Ruby's Marshal protocol.
27
+ # Contents must support Marshal. Nested shareable values may be reconstructed too.
15
28
  # * `:make_shareable` - The value will be made Ractor-shareable using {Ractor.make_shareable}.
16
29
  # * `:move` - The value will be moved between Ractors. This saves memory compared to copying, and supports values
17
30
  # that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor
18
31
  # that pushed it.
19
- # * `:mutable` - A {Farce::Mutable} instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.
32
+ # * `:mutable` - A {Farce::Mutable} instance will be created for the value.
33
+ # This isn't done recursively and thus will fail for nested unshareable values.
20
34
  # * `:local` - The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will
21
35
  # get an error. Useful for usage contained within a single Ractor.
22
36
  # * `:proxy` - The value will be wrapped in a `Farce::Proxy` that executes calls in the original Ractor.
@@ -40,7 +54,8 @@
40
54
  # Converts incoming keys to their canonical stored form:
41
55
  # * If a `Symbol` is provided, it will be used as a method name to call on each key.
42
56
  # * If a `Proc` is provided, it will be called with each key and should return the normalized key.
43
- # * If a `Hash` or {Farce::Abstract::Map Map} is provided, it will be used to look up the normalized key for each incoming key.
57
+ # * If a `Hash` or {Farce::Abstract::Map Map} is provided, it will be used to look up
58
+ # the normalized key for each incoming key.
44
59
 
45
60
  # @!macro map_initialization
46
61
  # Create a map with optional initial entries.
@@ -41,6 +41,14 @@ module Farce
41
41
  # @return [Boolean] whether the replacement succeeded
42
42
  # @raise [ArgumentError] if either argument is not true or false
43
43
  #
44
+ # @!method wait_until_changed(expected, timeout: nil)
45
+ # Wait until the current value differs from the expected value.
46
+ # @param expected [Boolean] the value to wait to change from
47
+ # @param timeout [Numeric, nil] the total seconds available
48
+ # @yield called when the timeout expires
49
+ # @return [Boolean, BasicObject, nil] the changed value or timeout fallback
50
+ # @raise [ArgumentError] if expected is not true or false
51
+ #
44
52
  # @!method toggle
45
53
  # Atomically invert the value.
46
54
  # @return [Boolean] the new value
@@ -51,6 +59,71 @@ module Farce
51
59
  include Internal::ValueSerialization
52
60
  include Internal::Inspect
53
61
 
62
+ # Wait until the flag becomes true.
63
+ # @param timeout [Numeric, nil] the total seconds available
64
+ # @yield called when the timeout expires
65
+ # @return [Boolean, BasicObject, nil] the changed value or timeout fallback
66
+ def wait_until_set(timeout: nil, &) = wait_until_changed(false, timeout:, &)
67
+ alias wait_until_true wait_until_set
68
+ alias wait_while_unset wait_until_set
69
+ alias wait_while_false wait_until_set
70
+
71
+ # Wait until the flag becomes false.
72
+ # @param timeout [Numeric, nil] the total seconds available
73
+ # @yield called when the timeout expires
74
+ # @return [Boolean, BasicObject, nil] the changed value or timeout fallback
75
+ def wait_until_unset(timeout: nil, &) = wait_until_changed(true, timeout:, &)
76
+ alias wait_until_false wait_until_unset
77
+ alias wait_while_set wait_until_unset
78
+ alias wait_while_true wait_until_unset
79
+
80
+ # Wait until a block condition matches the current value.
81
+ # One timeout budget covers all checks and waits. The block is not interrupted.
82
+ # @yieldparam value [Boolean] the current value
83
+ # @yieldreturn [Boolean] whether the value matches
84
+ # @param timeout [Numeric, nil] the total seconds available
85
+ # @return [Boolean, nil] the matching value, or nil on timeout
86
+ # @raise [LocalJumpError] if no block is given
87
+ def wait_until(timeout: nil, &) = Internal.wait_until(self, timeout:, &)
88
+
89
+ # Wait while the block returns a truthy value.
90
+ # @yieldparam value [Boolean] the current value
91
+ # @yieldreturn [BasicObject] a truthy value to keep waiting, or nil or false to stop
92
+ # @param timeout [Numeric, nil] the total seconds available
93
+ # @return [Boolean, nil] the value when the condition becomes false, or nil on timeout
94
+ # @raise [LocalJumpError] if no block is given
95
+ def wait_while(timeout: nil)
96
+ raise LocalJumpError, "no block given" unless block_given?
97
+ wait_until(timeout:) { |value| !yield(value) }
98
+ end
99
+
100
+ # Wait while `object === value` is true.
101
+ # @param object [#===] the pattern to stop matching
102
+ # @param timeout [Numeric, nil] the total seconds available
103
+ # @return [Boolean, nil] the first nonmatching value, or nil on timeout
104
+ def wait_while_match(object, timeout: nil)
105
+ wait_while(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality
106
+ end
107
+
108
+ # (see #wait_until_changed)
109
+ def wait_while_value(...) = wait_until_changed(...)
110
+
111
+ # Wait until `object == value` is true.
112
+ # @param object [BasicObject] the value to compare with the current value
113
+ # @param timeout [Numeric, nil] the total seconds available
114
+ # @return [Boolean, nil] the matching value, or nil on timeout
115
+ def wait_until_value(object, timeout: nil)
116
+ wait_until(timeout:) { |value| object == value }
117
+ end
118
+
119
+ # Wait until `object === value` is true.
120
+ # @param object [#===] the pattern to match
121
+ # @param timeout [Numeric, nil] the total seconds available
122
+ # @return [Boolean, nil] the matching value, or nil on timeout
123
+ def wait_until_match(object, timeout: nil)
124
+ wait_until(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality
125
+ end
126
+
54
127
  # @api private
55
128
  def inspect_with(inspector)
56
129
  begin
@@ -98,7 +98,7 @@ module Farce
98
98
  # @raise [Farce::ClosedQueueError] when the queue is closed
99
99
  # @raise [Farce::SealedQueueError] when the queue is sealed but not yet closed
100
100
  # @raise [ThreadError] when the queue is full and non_block is true
101
- # @return [Boolean] `true` if the item was added to the queue
101
+ # @return [BasicObject, false] a truthy object if the item was added, or `false` if the timeout expired
102
102
  #
103
103
  # @!method try_pop
104
104
  # @abstract Subclasses may add additional parameters to this method.
@@ -116,8 +116,9 @@ module Farce
116
116
  # @yield Block called if the queue is at capacity.
117
117
  # @raise [Farce::ClosedQueueError] when the queue is closed
118
118
  # @raise [Farce::SealedQueueError] when the queue is sealed but not yet closed
119
- # @return [Boolean]
120
- # `true` if the item was added to the queue, `false` if the queue was at capacity and no block was given.
119
+ # @return [BasicObject]
120
+ # A truthy object if the item was added, or the fallback result if the queue was at capacity.
121
+ # Returns `false` if the queue was at capacity and no block was given.
121
122
  #
122
123
  # @!method wait_pop(timeout: nil)
123
124
  # @abstract Subclasses may add additional parameters to this method.
@@ -6,7 +6,7 @@ module Farce
6
6
  module Abstract
7
7
  # @abstract A shared super class for all scheduler implementations.
8
8
  #
9
- # @!method schedule(*args, mode: :copy, auto_local: true)
9
+ # @!method schedule(*args, mode: :smart, auto_local: true)
10
10
  # @abstract This method must be implemented by concrete scheduler subclasses.
11
11
  #
12
12
  # Schedules a block for execution.
@@ -23,7 +23,7 @@ module Farce
23
23
  # May be handled according to `mode`.
24
24
  #
25
25
  # @param mode [Symbol]
26
- # The mode in which to schedule the task. Defaults to `:copy`.
26
+ # The mode in which to schedule the task. Defaults to `:smart`.
27
27
  # May be ignored by some schedulers.
28
28
  #
29
29
  # @param auto_local [Boolean]
@@ -82,7 +82,7 @@ module Farce
82
82
  #
83
83
  # @param (see #schedule)
84
84
  # @return [self]
85
- def execute(*, mode: :copy, auto_local: true, &callback)
85
+ def execute(*, mode: :smart, auto_local: true, &callback)
86
86
  raise LocalJumpError, "Cannot yield without a block" unless block_given?
87
87
  raise SchedulerClosedError, "cannot execute task on a closed scheduler" if closed?
88
88
 
@@ -61,7 +61,7 @@ module Farce
61
61
  # @param normalize [Symbol, Proc, Hash, Farce::Abstract::Map, nil] The element normalizer.
62
62
  # @param compare_by_identity [Boolean] Whether membership uses object identity.
63
63
  # @param mode [Symbol] The default transfer mode for Farce::Set, Farce::SortedSet,
64
- # and Farce::WeakSet. Defaults to :copy for strong sets and :raise for WeakSet.
64
+ # and Farce::WeakSet. Defaults to :smart for strong sets and :raise for WeakSet.
65
65
  # @param options [Hash] Additional options for the selected variant.
66
66
  # @option options [Symbol] scope (:ractor) The scope used by Farce::Local variants.
67
67
  # @yield [element] Optionally transform each initial element before storage.
@@ -753,11 +753,22 @@ module Farce
753
753
  def prepare_mode_entry(element, requested_mode)
754
754
  if identity_membership?(element) && !Ractor.shareable?(element)
755
755
  case requested_mode
756
- when :copy
756
+ when :copy, :marshal
757
757
  raise ArgumentError, "identity comparison cannot copy a non-shareable element"
758
758
  when :shareable_copy, :make_shareable
759
759
  payload = @manager.wrap(element, mode: requested_mode)
760
760
  return [insertion_key(payload), payload]
761
+ when :smart, :smart_raise, :smart_copy, :smart_move
762
+ token = IDENTITY_TOKENS[element]
763
+ payload = @manager.wrap(element, mode: requested_mode)
764
+ case payload
765
+ when Envelope::Copy, Envelope::Marshal
766
+ raise ArgumentError, "identity comparison cannot copy a non-shareable element"
767
+ when Envelope::Move
768
+ return [MembershipKey.new(token || Object.new.freeze, identity: true), payload]
769
+ end
770
+ key = @manager.managed_envelope?(payload) ? insertion_key(element) : insertion_key(payload)
771
+ return [key, payload]
761
772
  end
762
773
  end
763
774
 
@@ -812,9 +823,10 @@ module Farce
812
823
 
813
824
  def stored_mode(entry)
814
825
  case entry.payload
815
- when Envelope::Copy then :copy
816
- when Envelope::Local then :local
817
- when Envelope::Move then :move
826
+ when Envelope::Copy then :copy
827
+ when Envelope::Marshal then :marshal
828
+ when Envelope::Local then :local
829
+ when Envelope::Move then :move
818
830
  else @manager.mode
819
831
  end
820
832
  end
@@ -11,12 +11,12 @@ module Farce
11
11
  # elements raise during insertion or lookup.
12
12
  class SortedSet < Set
13
13
  # Construct a set in ascending comparator order.
14
- # @overload initialize(enumerable = nil, normalize: nil, compare_by_identity: false, mode: :copy, **options)
14
+ # @overload initialize(enumerable = nil, normalize: nil, compare_by_identity: false, mode: :smart, **options)
15
15
  # @param enumerable [#each, nil] The initial elements, or nil for an empty set.
16
16
  # @param normalize [Symbol, Proc, Hash, Farce::Abstract::Map, nil] The element normalizer.
17
17
  # @param compare_by_identity [Boolean] Must be false. Membership uses <=>.
18
18
  # @param mode [Symbol] The default transfer mode. Only Farce::SortedSet accepts
19
- # this option, which defaults to :copy.
19
+ # this option, which defaults to :smart.
20
20
  # @param options [Hash] Additional options for the selected variant.
21
21
  # @option options [Symbol] scope (:ractor) The scope used by Farce::Local::SortedSet.
22
22
  # @yield [element] Optionally transform each initial element before normalization.