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/map.c CHANGED
@@ -92,6 +92,7 @@ typedef struct {
92
92
  } map_timeout_t;
93
93
 
94
94
  typedef struct {
95
+ VALUE self;
95
96
  VALUE fiber;
96
97
  VALUE thread;
97
98
  VALUE scheduler;
@@ -101,9 +102,10 @@ typedef struct {
101
102
  * before taking a native mutex so a Ractor GC barrier can never wait on a
102
103
  * peer that is blocked on that mutex. */
103
104
  static map_execution_context_t
104
- map_current_execution_context(void)
105
+ map_current_execution_context(VALUE self)
105
106
  {
106
107
  map_execution_context_t context = {
108
+ .self = self,
107
109
  .fiber = rb_fiber_current(),
108
110
  .thread = rb_thread_current(),
109
111
  .scheduler = rb_fiber_scheduler_current(),
@@ -206,18 +208,18 @@ static void
206
208
  map_compact(void *pointer)
207
209
  {
208
210
  map_t *map = pointer;
209
- map->comparing_owner = rb_gc_location(map->comparing_owner);
210
- map->comparing_thread = rb_gc_location(map->comparing_thread);
211
+ containers_gc_update(&map->comparing_owner);
212
+ containers_gc_update(&map->comparing_thread);
211
213
  for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
212
- reservation->key = rb_gc_location(reservation->key);
213
- reservation->fiber = rb_gc_location(reservation->fiber);
214
- reservation->thread = rb_gc_location(reservation->thread);
214
+ containers_gc_update(&reservation->key);
215
+ containers_gc_update(&reservation->fiber);
216
+ containers_gc_update(&reservation->thread);
215
217
  }
216
218
  if (!map->slots) return;
217
219
  for (size_t index = 0; index < map->capacity; index++) {
218
220
  if (map->slots[index].state == MAP_OCCUPIED) {
219
- map->slots[index].key = rb_gc_location(map->slots[index].key);
220
- map->slots[index].value = rb_gc_location(map->slots[index].value);
221
+ containers_gc_update(&map->slots[index].key);
222
+ containers_gc_update(&map->slots[index].value);
221
223
  }
222
224
  }
223
225
  }
@@ -265,7 +267,11 @@ static const rb_data_type_t map_type = {
265
267
  .dsize = map_memsize,
266
268
  .dcompact = map_compact,
267
269
  },
268
- .flags = RUBY_TYPED_FROZEN_SHAREABLE,
270
+ .flags =
271
+ RUBY_TYPED_WB_PROTECTED |
272
+ RUBY_TYPED_FROZEN_SHAREABLE |
273
+ RUBY_TYPED_FREE_IMMEDIATELY |
274
+ FARCE_TYPED_THREAD_SAFE_FREE,
269
275
  };
270
276
 
271
277
  static const rb_data_type_t unshared_map_type = {
@@ -277,7 +283,7 @@ static const rb_data_type_t unshared_map_type = {
277
283
  .dcompact = map_compact,
278
284
  },
279
285
  .parent = &map_type,
280
- .flags = RUBY_TYPED_FREE_IMMEDIATELY,
286
+ .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | FARCE_TYPED_THREAD_SAFE_FREE,
281
287
  };
282
288
 
283
289
  static size_t
@@ -297,7 +303,7 @@ static const rb_data_type_t unshared_key_lock_type = {
297
303
  .dsize = key_lock_memsize,
298
304
  .dcompact = map_compact,
299
305
  },
300
- .flags = RUBY_TYPED_FREE_IMMEDIATELY,
306
+ .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | FARCE_TYPED_THREAD_SAFE_FREE,
301
307
  };
302
308
 
303
309
  static void
@@ -319,9 +325,15 @@ static const rb_data_type_t shared_key_lock_type = {
319
325
  .dsize = key_lock_memsize,
320
326
  .dcompact = map_compact,
321
327
  },
322
- .flags = RUBY_TYPED_FROZEN_SHAREABLE,
328
+ .flags =
329
+ RUBY_TYPED_WB_PROTECTED |
330
+ RUBY_TYPED_FROZEN_SHAREABLE |
331
+ RUBY_TYPED_FREE_IMMEDIATELY |
332
+ FARCE_TYPED_THREAD_SAFE_FREE,
323
333
  };
324
334
 
335
+ /* Buffer growth also runs under the state mutex. Keep the allocator pair
336
+ * consistent and avoid Ruby allocators that can trigger GC in that section. */
325
337
  static map_slot_t *
326
338
  allocate_slots(size_t capacity)
327
339
  {
@@ -546,8 +558,8 @@ map_keys_equal(
546
558
  int state = 0;
547
559
 
548
560
  map->comparing = true;
549
- map->comparing_owner = context->fiber;
550
- map->comparing_thread = context->thread;
561
+ RB_OBJ_WRITE(context->self, &map->comparing_owner, context->fiber);
562
+ RB_OBJ_WRITE(context->self, &map->comparing_thread, context->thread);
551
563
  pthread_mutex_unlock(&map->lock);
552
564
 
553
565
  VALUE result = rb_protect(map_eql_protected, (VALUE)&arguments, &state);
@@ -735,10 +747,10 @@ map_begin_reservation_locked(
735
747
  pthread_mutex_unlock(&map->lock);
736
748
  rb_memerror();
737
749
  }
738
- reservation->key = key;
750
+ RB_OBJ_WRITE(context->self, &reservation->key, key);
739
751
  reservation->hash = hash;
740
- reservation->fiber = context->fiber;
741
- reservation->thread = context->thread;
752
+ RB_OBJ_WRITE(context->self, &reservation->fiber, context->fiber);
753
+ RB_OBJ_WRITE(context->self, &reservation->thread, context->thread);
742
754
  reservation->invalidated = false;
743
755
  reservation->next = map->reservations;
744
756
  map->reservations = reservation;
@@ -826,13 +838,13 @@ map_store_locked(
826
838
  map_slot_t *slot = &map->slots[index];
827
839
  if (!found) {
828
840
  if (slot->state == MAP_TOMBSTONE) map->tombstones--;
829
- slot->key = key;
841
+ RB_OBJ_WRITE(self, &slot->key, key);
830
842
  slot->hash = hash;
831
843
  slot->state = MAP_OCCUPIED;
832
844
  map->size++;
833
845
  map->iteration_epoch++;
834
846
  }
835
- slot->value = value;
847
+ RB_OBJ_WRITE(self, &slot->value, value);
836
848
  map_notify_waiters_locked(map);
837
849
  return value;
838
850
  }
@@ -914,7 +926,7 @@ map_initialize(int argc, VALUE *argv, VALUE self)
914
926
  map_init_context_t context = {
915
927
  .self = self,
916
928
  .map = map,
917
- .execution = map_current_execution_context(),
929
+ .execution = map_current_execution_context(self),
918
930
  };
919
931
  rb_hash_foreach(mapping, map_initialize_entry, (VALUE)&context);
920
932
  }
@@ -941,7 +953,7 @@ map_get(VALUE self, VALUE key)
941
953
  }
942
954
  pthread_mutex_unlock(&map->lock);
943
955
 
944
- map_execution_context_t execution = map_current_execution_context();
956
+ map_execution_context_t execution = map_current_execution_context(self);
945
957
  map_lock_state(map, &execution);
946
958
  index = map_find_slot(map, key, hash, &found, &execution);
947
959
  if (found) result = map->slots[index].value;
@@ -987,7 +999,7 @@ map_fetch(int argc, VALUE *argv, VALUE self)
987
999
  st_index_t hash = map_key_hash(map, key);
988
1000
  bool found;
989
1001
  VALUE result = Qnil;
990
- map_execution_context_t execution = map_current_execution_context();
1002
+ map_execution_context_t execution = map_current_execution_context(self);
991
1003
  map_lock_state(map, &execution);
992
1004
  size_t index = map_find_slot(map, key, hash, &found, &execution);
993
1005
  if (found) result = map->slots[index].value;
@@ -1029,7 +1041,7 @@ map_set(VALUE self, VALUE key, VALUE value)
1029
1041
  }
1030
1042
  pthread_mutex_unlock(&map->lock);
1031
1043
 
1032
- map_execution_context_t execution = map_current_execution_context();
1044
+ map_execution_context_t execution = map_current_execution_context(self);
1033
1045
  map_lock_for_key(map, key, hash, &execution);
1034
1046
  map_store_locked(self, map, key, value, hash, &execution);
1035
1047
  pthread_mutex_unlock(&map->lock);
@@ -1145,7 +1157,7 @@ map_key_p(VALUE self, VALUE key)
1145
1157
  key = map_prepare_key(map, key);
1146
1158
  st_index_t hash = map_key_hash(map, key);
1147
1159
  bool found;
1148
- map_execution_context_t execution = map_current_execution_context();
1160
+ map_execution_context_t execution = map_current_execution_context(self);
1149
1161
  map_lock_state(map, &execution);
1150
1162
  (void)map_find_slot(map, key, hash, &found, &execution);
1151
1163
  pthread_mutex_unlock(&map->lock);
@@ -1160,7 +1172,7 @@ map_getkey(VALUE self, VALUE key)
1160
1172
  st_index_t hash = map_key_hash(map, key);
1161
1173
  bool found;
1162
1174
  VALUE result = Qnil;
1163
- map_execution_context_t execution = map_current_execution_context();
1175
+ map_execution_context_t execution = map_current_execution_context(self);
1164
1176
  map_lock_state(map, &execution);
1165
1177
  size_t index = map_find_slot(map, key, hash, &found, &execution);
1166
1178
  if (found) result = map->compare_keys_by_identity ? key : map->slots[index].key;
@@ -1178,7 +1190,7 @@ map_delete(VALUE self, VALUE key)
1178
1190
  rb_check_frozen(self);
1179
1191
  bool found;
1180
1192
  VALUE result = Qnil;
1181
- map_execution_context_t execution = map_current_execution_context();
1193
+ map_execution_context_t execution = map_current_execution_context(self);
1182
1194
  map_lock_for_key(map, key, hash, &execution);
1183
1195
  size_t index = map_find_slot(map, key, hash, &found, &execution);
1184
1196
  if (RB_OBJ_FROZEN(self)) {
@@ -1204,7 +1216,7 @@ map_clear(VALUE self)
1204
1216
  {
1205
1217
  rb_check_frozen(self);
1206
1218
  map_t *map = get_map(self);
1207
- map_execution_context_t execution = map_current_execution_context();
1219
+ map_execution_context_t execution = map_current_execution_context(self);
1208
1220
  map_lock_state(map, &execution);
1209
1221
  map_invalidate_reservations_locked(map);
1210
1222
  for (size_t index = 0; index < map->capacity; index++) {
@@ -1290,7 +1302,7 @@ map_store_if_absent(int argc, VALUE *argv, VALUE self)
1290
1302
  map_operation_t operation = {
1291
1303
  .self = self,
1292
1304
  .map = map,
1293
- .execution = map_current_execution_context(),
1305
+ .execution = map_current_execution_context(self),
1294
1306
  .key = key,
1295
1307
  .complete = false,
1296
1308
  };
@@ -1371,7 +1383,7 @@ map_compare_and_set(int argc, VALUE *argv, VALUE self)
1371
1383
  map_operation_t operation = {
1372
1384
  .self = self,
1373
1385
  .map = map,
1374
- .execution = map_current_execution_context(),
1386
+ .execution = map_current_execution_context(self),
1375
1387
  .key = key,
1376
1388
  .argument = expected,
1377
1389
  .replacement = replacement,
@@ -1457,7 +1469,7 @@ map_modify(VALUE self, VALUE key)
1457
1469
  key = map_prepare_key(map, key);
1458
1470
  map_operation_t operation = {
1459
1471
  .self = self,
1460
- .execution = map_current_execution_context(),
1472
+ .execution = map_current_execution_context(self),
1461
1473
  .map = map,
1462
1474
  .key = key,
1463
1475
  .complete = false,
@@ -1520,7 +1532,7 @@ map_upsert(int argc, VALUE *argv, VALUE self)
1520
1532
  map_operation_t operation = {
1521
1533
  .self = self,
1522
1534
  .map = map,
1523
- .execution = map_current_execution_context(),
1535
+ .execution = map_current_execution_context(self),
1524
1536
  .key = key,
1525
1537
  .complete = false,
1526
1538
  };
@@ -1571,7 +1583,7 @@ map_size(VALUE self)
1571
1583
  {
1572
1584
  map_t *map = get_map(self);
1573
1585
  size_t size;
1574
- map_execution_context_t execution = map_current_execution_context();
1586
+ map_execution_context_t execution = map_current_execution_context(self);
1575
1587
  map_lock_state(map, &execution);
1576
1588
  size = map->size;
1577
1589
  pthread_mutex_unlock(&map->lock);
@@ -1587,9 +1599,9 @@ map_enumerator_size(VALUE self, VALUE arguments, VALUE enumerator)
1587
1599
  }
1588
1600
 
1589
1601
  static VALUE
1590
- map_entries_snapshot(map_t *map)
1602
+ map_entries_snapshot(VALUE self, map_t *map)
1591
1603
  {
1592
- map_execution_context_t execution = map_current_execution_context();
1604
+ map_execution_context_t execution = map_current_execution_context(self);
1593
1605
  for (;;) {
1594
1606
  size_t capacity;
1595
1607
  map_lock_state(map, &execution);
@@ -1598,22 +1610,27 @@ map_entries_snapshot(map_t *map)
1598
1610
 
1599
1611
  long reserved_entries = (long)(capacity * 2);
1600
1612
  VALUE entries = rb_ary_new_capa(reserved_entries);
1601
- rb_ary_resize(entries, reserved_entries);
1602
1613
  map_lock_state(map, &execution);
1603
1614
  if (map->size > capacity) {
1604
1615
  pthread_mutex_unlock(&map->lock);
1605
1616
  continue;
1606
1617
  }
1607
- long entry_count = 0;
1608
- VALUE *storage = RARRAY_PTR(entries);
1618
+ /* The private array has capacity for every pair. Bulk appends cannot
1619
+ * grow it or trigger GC while the state mutex is held. */
1620
+ VALUE batch[64];
1621
+ long length = 0;
1609
1622
  for (size_t index = 0; index < map->capacity; index++) {
1610
1623
  map_slot_t *slot = &map->slots[index];
1611
1624
  if (slot->state != MAP_OCCUPIED) continue;
1612
- RB_OBJ_WRITE(entries, &storage[entry_count++], slot->key);
1613
- RB_OBJ_WRITE(entries, &storage[entry_count++], slot->value);
1625
+ batch[length++] = slot->key;
1626
+ batch[length++] = slot->value;
1627
+ if (length == 64) {
1628
+ rb_ary_cat(entries, batch, length);
1629
+ length = 0;
1630
+ }
1614
1631
  }
1632
+ if (length) rb_ary_cat(entries, batch, length);
1615
1633
  pthread_mutex_unlock(&map->lock);
1616
- if (entry_count < reserved_entries) rb_ary_resize(entries, entry_count);
1617
1634
  return entries;
1618
1635
  }
1619
1636
  }
@@ -1621,11 +1638,11 @@ map_entries_snapshot(map_t *map)
1621
1638
  static VALUE
1622
1639
  map_keys(VALUE self)
1623
1640
  {
1624
- VALUE entries = map_entries_snapshot(get_map(self));
1641
+ VALUE entries = map_entries_snapshot(self, get_map(self));
1625
1642
  long entry_count = RARRAY_LEN(entries);
1626
1643
  VALUE keys = rb_ary_new_capa(entry_count / 2);
1627
1644
  for (long index = 0; index < entry_count; index += 2) {
1628
- rb_ary_push(keys, RARRAY_AREF(entries, index));
1645
+ rb_ary_push(keys, rb_ary_entry(entries, index));
1629
1646
  }
1630
1647
  RB_GC_GUARD(entries);
1631
1648
  return keys;
@@ -1635,10 +1652,10 @@ static VALUE
1635
1652
  map_each(VALUE self)
1636
1653
  {
1637
1654
  RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
1638
- VALUE entries = map_entries_snapshot(get_map(self));
1655
+ VALUE entries = map_entries_snapshot(self, get_map(self));
1639
1656
  long entry_count = RARRAY_LEN(entries);
1640
1657
  for (long index = 0; index < entry_count; index += 2) {
1641
- rb_yield(rb_assoc_new(RARRAY_AREF(entries, index), RARRAY_AREF(entries, index + 1)));
1658
+ rb_yield(rb_assoc_new(rb_ary_entry(entries, index), rb_ary_entry(entries, index + 1)));
1642
1659
  }
1643
1660
  RB_GC_GUARD(entries);
1644
1661
  return self;
@@ -1652,18 +1669,17 @@ map_each_live(VALUE self)
1652
1669
  {
1653
1670
  RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
1654
1671
  map_t *map = get_map(self);
1655
- map_execution_context_t execution = map_current_execution_context();
1672
+ map_execution_context_t execution = map_current_execution_context(self);
1656
1673
  size_t cursor = 0;
1657
1674
  uint64_t epoch = 0;
1658
1675
  bool started = false;
1659
1676
  const long batch_slots = 64;
1660
- VALUE batch = rb_ary_new_capa(batch_slots);
1661
- rb_ary_resize(batch, batch_slots);
1677
+ /* C-stack roots keep this bounded batch alive across Ruby yields. */
1678
+ VALUE batch[64];
1662
1679
 
1663
1680
  for (;;) {
1664
- VALUE *storage = RARRAY_PTR(batch);
1665
1681
  for (long index = 0; index < batch_slots; index++) {
1666
- RB_OBJ_WRITE(batch, &storage[index], Qnil);
1682
+ batch[index] = Qnil;
1667
1683
  }
1668
1684
  map_lock_state(map, &execution);
1669
1685
  if (started && epoch != map->iteration_epoch) {
@@ -1676,16 +1692,17 @@ map_each_live(VALUE self)
1676
1692
  while (cursor < map->capacity && length < batch_slots) {
1677
1693
  map_slot_t *slot = &map->slots[cursor++];
1678
1694
  if (slot->state != MAP_OCCUPIED) continue;
1679
- RB_OBJ_WRITE(batch, &storage[length++], slot->key);
1680
- RB_OBJ_WRITE(batch, &storage[length++], slot->value);
1695
+ batch[length++] = slot->key;
1696
+ batch[length++] = slot->value;
1681
1697
  }
1682
1698
  pthread_mutex_unlock(&map->lock);
1683
1699
  if (length == 0) break;
1684
1700
  for (long index = 0; index < length; index += 2) {
1685
- rb_yield(rb_assoc_new(RARRAY_AREF(batch, index), RARRAY_AREF(batch, index + 1)));
1701
+ rb_yield(rb_assoc_new(batch[index], batch[index + 1]));
1686
1702
  }
1687
1703
  }
1688
- RB_GC_GUARD(batch);
1704
+ RB_GC_GUARD(batch[0]);
1705
+ RB_GC_GUARD(batch[63]);
1689
1706
  return self;
1690
1707
  }
1691
1708
 
@@ -1693,10 +1710,10 @@ static VALUE
1693
1710
  map_each_key(VALUE self)
1694
1711
  {
1695
1712
  RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
1696
- VALUE entries = map_entries_snapshot(get_map(self));
1713
+ VALUE entries = map_entries_snapshot(self, get_map(self));
1697
1714
  long entry_count = RARRAY_LEN(entries);
1698
1715
  for (long index = 0; index < entry_count; index += 2) {
1699
- rb_yield(RARRAY_AREF(entries, index));
1716
+ rb_yield(rb_ary_entry(entries, index));
1700
1717
  }
1701
1718
  RB_GC_GUARD(entries);
1702
1719
  return self;
@@ -1706,10 +1723,10 @@ static VALUE
1706
1723
  map_each_value(VALUE self)
1707
1724
  {
1708
1725
  RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
1709
- VALUE entries = map_entries_snapshot(get_map(self));
1726
+ VALUE entries = map_entries_snapshot(self, get_map(self));
1710
1727
  long entry_count = RARRAY_LEN(entries);
1711
1728
  for (long index = 1; index < entry_count; index += 2) {
1712
- rb_yield(RARRAY_AREF(entries, index));
1729
+ rb_yield(rb_ary_entry(entries, index));
1713
1730
  }
1714
1731
  RB_GC_GUARD(entries);
1715
1732
  return self;
@@ -1729,7 +1746,7 @@ map_get_with_timeout(int argc, VALUE *argv, VALUE self)
1729
1746
  key = map_prepare_key(map, key);
1730
1747
  st_index_t hash = map_key_hash(map, key);
1731
1748
  map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
1732
- map_execution_context_t execution = map_current_execution_context();
1749
+ map_execution_context_t execution = map_current_execution_context(self);
1733
1750
  if (!map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
1734
1751
  return map_timeout_result();
1735
1752
  }
@@ -1759,7 +1776,7 @@ map_store_with_timeout(int argc, VALUE *argv, VALUE self)
1759
1776
  st_index_t hash = map_key_hash(map, key);
1760
1777
  rb_check_frozen(self);
1761
1778
  map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
1762
- map_execution_context_t execution = map_current_execution_context();
1779
+ map_execution_context_t execution = map_current_execution_context(self);
1763
1780
  if (!map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
1764
1781
  return map_timeout_result();
1765
1782
  }
@@ -1786,7 +1803,7 @@ map_swap(int argc, VALUE *argv, VALUE self)
1786
1803
  st_index_t hash = map_key_hash(map, key);
1787
1804
  rb_check_frozen(self);
1788
1805
  map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
1789
- map_execution_context_t execution = map_current_execution_context();
1806
+ map_execution_context_t execution = map_current_execution_context(self);
1790
1807
  if (!map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
1791
1808
  return map_timeout_result();
1792
1809
  }
@@ -1826,7 +1843,7 @@ map_wait_for_value(int argc, VALUE *argv, VALUE self, bool non_nil)
1826
1843
  if (!non_nil) map_check_value(map, expected);
1827
1844
  st_index_t hash = map_key_hash(map, key);
1828
1845
  map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
1829
- map_execution_context_t execution = map_current_execution_context();
1846
+ map_execution_context_t execution = map_current_execution_context(self);
1830
1847
 
1831
1848
  for (;;) {
1832
1849
  bool found;
@@ -1881,7 +1898,7 @@ map_update(int argc, VALUE *argv, VALUE self)
1881
1898
  map_operation_t operation = {
1882
1899
  .self = self,
1883
1900
  .map = map,
1884
- .execution = map_current_execution_context(),
1901
+ .execution = map_current_execution_context(self),
1885
1902
  .key = key,
1886
1903
  .complete = false,
1887
1904
  };
@@ -2006,7 +2023,7 @@ key_lock_synchronize(VALUE self, VALUE key)
2006
2023
  map_t *map = &storage->core;
2007
2024
  map_operation_t operation = {
2008
2025
  .map = map,
2009
- .execution = map_current_execution_context(),
2026
+ .execution = map_current_execution_context(self),
2010
2027
  .key = key,
2011
2028
  .complete = false,
2012
2029
  };
@@ -2050,6 +2067,11 @@ map_transaction_apply(farce_transaction_entry_t *entry)
2050
2067
  copy->capacity = capacity;
2051
2068
  copy->size = size;
2052
2069
  copy->tombstones = tombstones;
2070
+ for (size_t i = 0; i < map->capacity; i++) {
2071
+ if (map->slots[i].state != MAP_OCCUPIED) continue;
2072
+ RB_OBJ_WRITTEN(entry->source, Qundef, map->slots[i].key);
2073
+ RB_OBJ_WRITTEN(entry->source, Qundef, map->slots[i].value);
2074
+ }
2053
2075
  }
2054
2076
 
2055
2077
  static void
@@ -2063,8 +2085,8 @@ map_transaction_reserve(farce_transaction_entry_t *entry, VALUE fiber, VALUE thr
2063
2085
  {
2064
2086
  map_t *map = entry->source_data;
2065
2087
  map->comparing = FARCE_TRANSACTION_RESERVED;
2066
- map->comparing_owner = fiber;
2067
- map->comparing_thread = thread;
2088
+ RB_OBJ_WRITE(entry->source, &map->comparing_owner, fiber);
2089
+ RB_OBJ_WRITE(entry->source, &map->comparing_thread, thread);
2068
2090
  }
2069
2091
 
2070
2092
  static void
@@ -2085,7 +2107,7 @@ map_freeze(VALUE self)
2085
2107
  pthread_mutex_lock(&map->lock);
2086
2108
  if (map->comparing == FARCE_TRANSACTION_RESERVED) {
2087
2109
  pthread_mutex_unlock(&map->lock);
2088
- map_execution_context_t context = map_current_execution_context();
2110
+ map_execution_context_t context = map_current_execution_context(self);
2089
2111
  map_lock_state(map, &context);
2090
2112
  }
2091
2113
  rb_obj_freeze(self);