farce 0.1.0-x86-linux-gnu → 0.3.0-x86-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +127 -1
  3. data/README.md +111 -17
  4. data/docs/gems/dry-types.md +8 -8
  5. data/docs/modes.md +75 -15
  6. data/docs/scopes.md +3 -1
  7. data/docs/variants.md +18 -0
  8. data/ext/farce/atom.c +37 -22
  9. data/ext/farce/bounded_map.c +17 -13
  10. data/ext/farce/containers.h +15 -0
  11. data/ext/farce/counter.c +13 -18
  12. data/ext/farce/exchanger.c +5 -1
  13. data/ext/farce/farce.c +1 -1
  14. data/ext/farce/flag.c +39 -3
  15. data/ext/farce/io.c +3 -3
  16. data/ext/farce/lock.c +17 -23
  17. data/ext/farce/map.c +88 -66
  18. data/ext/farce/priority_queue.c +55 -43
  19. data/ext/farce/queue.c +10 -4
  20. data/ext/farce/reactor.c +52 -25
  21. data/ext/farce/reactor.h +1 -1
  22. data/ext/farce/signal.c +5 -1
  23. data/ext/farce/transaction.c +20 -24
  24. data/ext/farce/tree_map.c +33 -18
  25. data/ext/farce/trie.c +81 -43
  26. data/ext/farce/unshareable.c +168 -19
  27. data/ext/farce/unshared_io_pool.h +21 -12
  28. data/ext/farce/unshared_signal.c +13 -2
  29. data/ext/farce/unshared_wait.h +3 -0
  30. data/ext/farce/vector.c +82 -18
  31. data/ext/farce/weak_map.c +75 -63
  32. data/lib/farce/_yard/macros.rb +18 -3
  33. data/lib/farce/abstract/executor.rb +130 -0
  34. data/lib/farce/abstract/flag.rb +73 -0
  35. data/lib/farce/abstract/pool.rb +334 -0
  36. data/lib/farce/abstract/queue.rb +4 -3
  37. data/lib/farce/abstract/scheduler.rb +539 -93
  38. data/lib/farce/abstract/set.rb +17 -5
  39. data/lib/farce/abstract/sorted_set.rb +2 -2
  40. data/lib/farce/abstract/timer_queue.rb +49 -10
  41. data/lib/farce/abstract/vector.rb +510 -28
  42. data/lib/farce/atom.rb +1 -1
  43. data/lib/farce/class_mirror.rb +20 -7
  44. data/lib/farce/config.rb +7 -1
  45. data/lib/farce/engine/jruby/signal.rb +7 -4
  46. data/lib/farce/engine/jvm/flag.rb +15 -3
  47. data/lib/farce/engine/ruby/3.4/farce.so +0 -0
  48. data/lib/farce/engine/ruby/4.0/farce.so +0 -0
  49. data/lib/farce/engine/ruby/shared/require_hook.rb +18 -0
  50. data/lib/farce/engine/ruby.rb +7 -0
  51. data/lib/farce/engine/shared/main_scheduler.rb +2 -2
  52. data/lib/farce/engine/shared/parallel_scheduler.rb +1 -1
  53. data/lib/farce/engine/shared/proxy_owner.rb +1 -1
  54. data/lib/farce/engine/shared/unshared_atom.rb +9 -8
  55. data/lib/farce/engine/shared/unshared_vector.rb +31 -4
  56. data/lib/farce/engine/shared/vector.rb +8 -0
  57. data/lib/farce/engine/shared/weak_map/index.rb +14 -1
  58. data/lib/farce/engine/shared.rb +9 -0
  59. data/lib/farce/engine/truffleruby/native/flag.rb +15 -3
  60. data/lib/farce/envelope.rb +54 -7
  61. data/lib/farce/exchanger.rb +1 -1
  62. data/lib/farce/flag.rb +2 -0
  63. data/lib/farce/integrations/active_support.rb +1 -1
  64. data/lib/farce/integrations/bson.rb +1 -1
  65. data/lib/farce/integrations/cbor.rb +1 -1
  66. data/lib/farce/integrations/concurrent.rb +3 -3
  67. data/lib/farce/integrations/date.rb +8 -0
  68. data/lib/farce/integrations/dry_types.rb +7 -4
  69. data/lib/farce/integrations/json.rb +2 -2
  70. data/lib/farce/integrations/msgpack.rb +1 -1
  71. data/lib/farce/integrations/oj.rb +3 -4
  72. data/lib/farce/integrations/psych.rb +1 -1
  73. data/lib/farce/integrations/ractor_sharing.rb +1 -1
  74. data/lib/farce/integrations/sorted_set.rb +1 -1
  75. data/lib/farce/integrations/weakref.rb +9 -1
  76. data/lib/farce/integrations/yajl.rb +3 -3
  77. data/lib/farce/integrations.rb +33 -23
  78. data/lib/farce/internal/flag_waiting.rb +30 -0
  79. data/lib/farce/internal/freeze.rb +8 -3
  80. data/lib/farce/internal/lease_initialization.rb +1 -1
  81. data/lib/farce/internal/local_executor_methods.rb +33 -0
  82. data/lib/farce/internal/local_scheduled_task.rb +27 -0
  83. data/lib/farce/internal/map_value_modes.rb +1 -1
  84. data/lib/farce/internal/scheduled_task.rb +1 -1
  85. data/lib/farce/internal/scheduler_lifecycle.rb +1 -1
  86. data/lib/farce/internal/thread_pool_supervisor.rb +45 -0
  87. data/lib/farce/internal/unshared_pool_worker.rb +14 -0
  88. data/lib/farce/internal.rb +14 -0
  89. data/lib/farce/lazy.rb +4 -4
  90. data/lib/farce/lazy_ref.rb +3 -3
  91. data/lib/farce/lfu_map.rb +1 -1
  92. data/lib/farce/local/flag.rb +4 -0
  93. data/lib/farce/local/pool.rb +111 -0
  94. data/lib/farce/local/scheduler.rb +219 -0
  95. data/lib/farce/local/scoped.rb +1 -1
  96. data/lib/farce/lru_map.rb +1 -1
  97. data/lib/farce/map.rb +2 -2
  98. data/lib/farce/mode_manager/smart_rule.rb +138 -0
  99. data/lib/farce/mode_manager.rb +12 -3
  100. data/lib/farce/molecule.rb +2 -2
  101. data/lib/farce/pool.rb +2 -290
  102. data/lib/farce/port.rb +4 -4
  103. data/lib/farce/priority_queue.rb +1 -1
  104. data/lib/farce/proxy/register.rb +6 -0
  105. data/lib/farce/proxy/supervisor.rb +1 -1
  106. data/lib/farce/proxy.rb +9 -4
  107. data/lib/farce/queue.rb +1 -1
  108. data/lib/farce/scheduler.rb +1 -502
  109. data/lib/farce/set.rb +1 -1
  110. data/lib/farce/sorted_set.rb +1 -1
  111. data/lib/farce/task.rb +285 -0
  112. data/lib/farce/{thread_scheduler.rb → thread_executor.rb} +7 -7
  113. data/lib/farce/timer_queue.rb +20 -6
  114. data/lib/farce/transaction/vector.rb +43 -9
  115. data/lib/farce/transaction/wrapper.rb +14 -0
  116. data/lib/farce/tree_map.rb +1 -1
  117. data/lib/farce/unsafe/lfu_map.rb +2 -0
  118. data/lib/farce/unsafe/lru_map.rb +2 -0
  119. data/lib/farce/unsafe/tree_map.rb +2 -0
  120. data/lib/farce/unshareable.rb +8 -19
  121. data/lib/farce/unshared/atom.rb +3 -0
  122. data/lib/farce/unshared/lfu_map.rb +2 -0
  123. data/lib/farce/unshared/lru_map.rb +2 -0
  124. data/lib/farce/unshared/map.rb +3 -0
  125. data/lib/farce/unshared/pool.rb +44 -0
  126. data/lib/farce/unshared/scheduler.rb +89 -0
  127. data/lib/farce/unshared/set.rb +1 -0
  128. data/lib/farce/unshared/sorted_set.rb +1 -0
  129. data/lib/farce/unshared/tree_map.rb +5 -1
  130. data/lib/farce/unshared/vector.rb +3 -0
  131. data/lib/farce/unshared/weak_atom.rb +5 -1
  132. data/lib/farce/unshared/weak_key_map.rb +3 -0
  133. data/lib/farce/unshared/weak_map.rb +3 -0
  134. data/lib/farce/unshared/weak_set.rb +1 -0
  135. data/lib/farce/unshared/weak_value_map.rb +3 -0
  136. data/lib/farce/vector.rb +44 -19
  137. data/lib/farce/version.rb +1 -1
  138. data/lib/farce.rb +20 -25
  139. metadata +17 -2
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.
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Abstract
7
+ # A task executor that can submit work or wait for it to finish.
8
+ #
9
+ # {#schedule} submits a task. {#execute} waits for the task to finish and may
10
+ # run it directly when execution is local. Concrete executors decide where
11
+ # tasks run and how their arguments are transferred.
12
+ #
13
+ # @abstract Use a scheduler or pool class.
14
+ #
15
+ # @!method schedule(*args, mode: :smart, auto_local: true)
16
+ # @abstract This method must be implemented by concrete executor subclasses.
17
+ #
18
+ # Schedules a block for execution.
19
+ # What actually happens depends on the concrete executor implementation.
20
+ # Some executors may ignore the `auto_local` option. Some might treat every mode as `:local`.
21
+ #
22
+ # The executor may choose to block until the task has been executed, but most will add it to a queue of pending
23
+ # tasks, only blocking when that queue's capacity has been reached.
24
+ #
25
+ # @!macro modes
26
+ #
27
+ # @param args [Array]
28
+ # Arguments to pass to the scheduled block.
29
+ # May be handled according to `mode`.
30
+ #
31
+ # @param mode [Symbol]
32
+ # The mode in which to schedule the task. Defaults to `:smart`.
33
+ # May be ignored by some executors.
34
+ #
35
+ # @param auto_local [Boolean]
36
+ # Whether to automatically use a local mode when possible. Defaults to `true`.
37
+ # May be ignored by some executors.
38
+ #
39
+ # @yield [*args] The block to be executed by the scheduler.
40
+ # @yieldparam args [Array] The arguments passed to the block.
41
+ # @yieldreceiver [BasicObject, nil]
42
+ # Local mode will not touch the block's binding (self stays the same), but other modes may change it to `nil`.
43
+ #
44
+ # @return [self]
45
+ #
46
+ # @!method close
47
+ # @abstract This method must be implemented by concrete executor subclasses.
48
+ # Closes the executor, releasing any resources it holds.
49
+ # Once closed, the executor cannot be used to schedule new tasks.
50
+ # Will drain any pending tasks before fully closing. If tasks are running indefinitely, it may block.
51
+ # @return [self]
52
+ #
53
+ # @!method closed?
54
+ # @abstract This method must be implemented by concrete executor subclasses.
55
+ # @return [Boolean] Whether the executor is closed.
56
+ #
57
+ # @!method state
58
+ # @abstract This method must be implemented by concrete executor subclasses.
59
+ # @return [Symbol]
60
+ # The current state of the executor.
61
+ #
62
+ # Common states include:
63
+ # * `:initialized`, `:launching` and `:setup` for the setup phase.
64
+ # * `:running` for the execution phase.
65
+ # * `:closing`, `:closed`, and `:error` for the termination phase.
66
+ #
67
+ # Not all executors will implement every state.
68
+ #
69
+ # @!method ractor_shareable?
70
+ # @abstract Executors should include {Shareable} or {Unshareable}, which implement this method.
71
+ # @return [Boolean] Whether the executor is safe to use across Ractors.
72
+ class Executor
73
+ include Internal::Noncopyable
74
+
75
+ # An error that occurred during the executor's operation, if any.
76
+ # This is primarily for scheduling errors, not execution errors.
77
+ # You can check this if the {#state} is `:error`.
78
+ def error = nil
79
+
80
+ # @param wait [Boolean] Whether to wait for the owner to be non-nil before checking if the executor is local.
81
+ # @return [Boolean] Whether the executor is local for the current Ractor.
82
+ def local?(wait: true) = false # rubocop:disable Lint/UnusedMethodArgument
83
+
84
+ # Attempts to submit a task. Queue-based executors return `nil` when full
85
+ # instead of waiting for space. Other executors behave like {#schedule}.
86
+ #
87
+ # @param (see #schedule)
88
+ # @return [self, nil] self when submitted, or nil when the queue is full.
89
+ def try_schedule(...) = schedule(...)
90
+
91
+ # Closes the executor in every scope.
92
+ # For executors without scopes, this behaves like {#close}.
93
+ #
94
+ # @return [self]
95
+ def close_all = close
96
+
97
+ # Like {#schedule}, with two differences:
98
+ # 1. If {#local?} returns true, and it would run in local mode (either due to `auto_local` or explicitly set
99
+ # `mode: :local`), it executes the block directly without enqueuing it.
100
+ # 2. It blocks until the block has been executed, either immediately in local mode or after being scheduled.
101
+ #
102
+ # @param (see #schedule)
103
+ # @return [self]
104
+ def execute(*, mode: :smart, auto_local: true, &callback)
105
+ raise LocalJumpError, "Cannot yield without a block" unless block_given?
106
+ raise SchedulerClosedError, "cannot execute task on a closed scheduler" if closed?
107
+
108
+ if (auto_local || mode == :local) && local?
109
+ raise SchedulerClosedError, "cannot execute task on a closed scheduler" if closed?
110
+ yield(*)
111
+ return self
112
+ end
113
+
114
+ ran = Farce::Flag.new(false)
115
+ signal = Signal.new
116
+ callback = Ractor.shareable_proc(&callback) unless Ractor.shareable?(callback)
117
+
118
+ schedule(callback, ran, signal, *, mode:, auto_local:) do |callback, ran, signal, *args|
119
+ callback.call(*args)
120
+ ensure
121
+ ran.set
122
+ signal.broadcast
123
+ end
124
+
125
+ signal.wait_until { ran.value }
126
+ self
127
+ end
128
+ end
129
+ end
130
+ end