farce 0.4.0-x86-linux-gnu → 0.5.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -1
  3. data/README.md +3 -1
  4. data/docs/benchmarks.md +37 -21
  5. data/docs/compatibility.md +13 -0
  6. data/docs/data-structures.md +37 -0
  7. data/docs/integrations.md +1 -0
  8. data/docs/introduction.md +19 -0
  9. data/docs/modes.md +2 -0
  10. data/docs/scheduling.md +35 -3
  11. data/docs/scopes.md +3 -1
  12. data/docs/shims.md +53 -0
  13. data/docs/utilities.md +31 -0
  14. data/docs/variants.md +9 -0
  15. data/ext/farce/LIBURING-LICENSE +20 -0
  16. data/ext/farce/atom.c +23 -66
  17. data/ext/farce/counter.c +1 -1
  18. data/ext/farce/depend +15 -7
  19. data/ext/farce/drivers.c +44 -2
  20. data/ext/farce/exchanger.c +1 -1
  21. data/ext/farce/extconf.rb +45 -2
  22. data/ext/farce/farce.c +17 -0
  23. data/ext/farce/flag.c +1 -1
  24. data/ext/farce/futex.c +76 -0
  25. data/ext/farce/futex.h +31 -0
  26. data/ext/farce/lock.c +16 -62
  27. data/ext/farce/map.c +24 -57
  28. data/ext/farce/native_wait.c +139 -0
  29. data/ext/farce/native_wait.h +37 -0
  30. data/ext/farce/priority_queue.c +62 -59
  31. data/ext/farce/queue.c +142 -8
  32. data/ext/farce/reactor.c +100 -11
  33. data/ext/farce/reactor.h +6 -2
  34. data/ext/farce/signal.c +17 -64
  35. data/ext/farce/tree_map.c +15 -54
  36. data/ext/farce/unshareable.c +1 -1
  37. data/ext/farce/unsupported.c +13 -1
  38. data/ext/farce/vector.c +16 -63
  39. data/ext/farce/weak_map.c +13 -4
  40. data/ext/java/org/farce/PriorityQueue.java +18 -2
  41. data/lib/farce/abstract/concurrent_map.rb +122 -0
  42. data/lib/farce/abstract/counter.rb +35 -1
  43. data/lib/farce/abstract/executor.rb +30 -7
  44. data/lib/farce/abstract/flag.rb +12 -0
  45. data/lib/farce/abstract/pool.rb +158 -47
  46. data/lib/farce/abstract/priority_queue.rb +1 -1
  47. data/lib/farce/abstract/publisher.rb +189 -0
  48. data/lib/farce/abstract/queue.rb +29 -3
  49. data/lib/farce/abstract/scheduler.rb +41 -14
  50. data/lib/farce/abstract/timer_queue.rb +3 -3
  51. data/lib/farce/abstract/vector.rb +3 -1
  52. data/lib/farce/atom.rb +9 -2
  53. data/lib/farce/clock.rb +12 -0
  54. data/lib/farce/concurrent/count_down_latch.rb +37 -0
  55. data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
  56. data/lib/farce/concurrent/event.rb +60 -0
  57. data/lib/farce/concurrent/fiber_local_var.rb +31 -0
  58. data/lib/farce/concurrent/local_var.rb +65 -0
  59. data/lib/farce/concurrent/thread_local_var.rb +28 -0
  60. data/lib/farce/concurrent/tuple.rb +74 -0
  61. data/lib/farce/concurrent.rb +50 -0
  62. data/lib/farce/config.rb +112 -2
  63. data/lib/farce/counter.rb +0 -16
  64. data/lib/farce/engine/jruby/map.rb +8 -6
  65. data/lib/farce/engine/jruby/signal.rb +2 -2
  66. data/lib/farce/engine/jvm/farce.jar +0 -0
  67. data/lib/farce/engine/jvm/priority_queue.rb +14 -4
  68. data/lib/farce/engine/ruby/3.4/farce.so +0 -0
  69. data/lib/farce/engine/ruby/4.0/farce.so +0 -0
  70. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
  71. data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
  72. data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
  73. data/lib/farce/engine/shared/exchanger.rb +2 -2
  74. data/lib/farce/engine/shared/queue.rb +43 -5
  75. data/lib/farce/engine/shared/ractor_selector.rb +1 -1
  76. data/lib/farce/engine/shared/strict_map.rb +5 -0
  77. data/lib/farce/engine/shared/unshared_atom.rb +1 -1
  78. data/lib/farce/engine/shared/unshared_vector.rb +8 -7
  79. data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
  80. data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
  81. data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
  82. data/lib/farce/engine/truffleruby/map.rb +8 -6
  83. data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
  84. data/lib/farce/engine/truffleruby/signal.rb +1 -1
  85. data/lib/farce/integrations/concurrent.rb +16 -3
  86. data/lib/farce/internal/blocking_priority_queue.rb +4 -9
  87. data/lib/farce/internal/flag_waiting.rb +1 -1
  88. data/lib/farce/internal/lease_initialization.rb +1 -1
  89. data/lib/farce/internal/lease_pool_state.rb +1 -1
  90. data/lib/farce/internal/lease_state.rb +2 -2
  91. data/lib/farce/internal/local_executor_methods.rb +3 -3
  92. data/lib/farce/internal/map_value_modes.rb +9 -2
  93. data/lib/farce/internal/pool_settings.rb +9 -0
  94. data/lib/farce/internal/pool_worker.rb +34 -21
  95. data/lib/farce/internal/reservation_waiting.rb +1 -1
  96. data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
  97. data/lib/farce/internal/weak_map_value_modes.rb +5 -0
  98. data/lib/farce/internal.rb +8 -8
  99. data/lib/farce/lazy.rb +3 -0
  100. data/lib/farce/lazy_ref.rb +3 -0
  101. data/lib/farce/local/lease.rb +2 -9
  102. data/lib/farce/local/lease_map.rb +2 -9
  103. data/lib/farce/local/pool.rb +38 -5
  104. data/lib/farce/local/publisher.rb +30 -0
  105. data/lib/farce/local/scheduler.rb +18 -5
  106. data/lib/farce/local/vector.rb +3 -2
  107. data/lib/farce/memoize.rb +314 -0
  108. data/lib/farce/pool.rb +5 -0
  109. data/lib/farce/priority_queue.rb +1 -1
  110. data/lib/farce/publisher.rb +57 -0
  111. data/lib/farce/signal.rb +1 -1
  112. data/lib/farce/strict/publisher.rb +31 -0
  113. data/lib/farce/strict/vector.rb +3 -2
  114. data/lib/farce/subscription/definitions.rb +27 -0
  115. data/lib/farce/subscription.rb +121 -0
  116. data/lib/farce/transaction/map.rb +35 -1
  117. data/lib/farce/unshared/pool.rb +1 -1
  118. data/lib/farce/unshared/publisher.rb +25 -0
  119. data/lib/farce/unshared/scheduler.rb +3 -3
  120. data/lib/farce/unshared/vector.rb +3 -2
  121. data/lib/farce/vector.rb +12 -9
  122. data/lib/farce/version.rb +1 -1
  123. data/lib/farce/walker/definitions.rb +13 -2
  124. data/lib/farce/weak_atom.rb +5 -2
  125. data/lib/farce.rb +3 -1
  126. metadata +23 -1
data/ext/farce/map.c CHANGED
@@ -1,16 +1,14 @@
1
1
  #include "containers.h"
2
+ #include "native_wait.h"
2
3
  #include "transaction.h"
3
4
  #include "ruby/fiber/scheduler.h"
4
- #include "ruby/io.h"
5
5
 
6
6
  #include <errno.h>
7
- #include <fcntl.h>
8
7
  #include <math.h>
9
8
  #include <stdint.h>
10
9
  #include <stdatomic.h>
11
10
  #include <stdlib.h>
12
11
  #include <time.h>
13
- #include <unistd.h>
14
12
 
15
13
  #define MAP_INITIAL_CAPACITY 16
16
14
 
@@ -42,9 +40,7 @@ typedef struct {
42
40
  } map_slot_t;
43
41
 
44
42
  typedef struct map_waiter {
45
- int read_fd;
46
- int write_fd;
47
- bool notified;
43
+ containers_waiter_t notification;
48
44
  struct map_waiter *next;
49
45
  } map_waiter_t;
50
46
 
@@ -141,46 +137,12 @@ map_parse_timeout(VALUE timeout)
141
137
  return parsed;
142
138
  }
143
139
 
144
- static void
145
- map_set_fd_flags(int fd)
146
- {
147
- #ifdef _WIN32
148
- (void)fd;
149
- #else
150
- int flags = fcntl(fd, F_GETFL, 0);
151
- if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
152
- flags = fcntl(fd, F_GETFD, 0);
153
- if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
154
- #endif
155
- }
156
-
157
- static void
158
- map_waiter_initialize(map_waiter_t *waiter)
159
- {
160
- int descriptors[2];
161
- if (pipe(descriptors) != 0) rb_sys_fail("pipe");
162
- waiter->read_fd = descriptors[0];
163
- waiter->write_fd = descriptors[1];
164
- waiter->notified = false;
165
- waiter->next = NULL;
166
- map_set_fd_flags(waiter->read_fd);
167
- map_set_fd_flags(waiter->write_fd);
168
- }
169
-
170
140
  static void
171
141
  map_notify_waiters_locked(map_t *map)
172
142
  {
173
- unsigned char byte = 1;
174
143
  map->generation++;
175
144
  for (map_waiter_t *waiter = map->waiters; waiter; waiter = waiter->next) {
176
- if (waiter->notified) continue;
177
- ssize_t result;
178
- do {
179
- result = write(waiter->write_fd, &byte, 1);
180
- } while (result < 0 && errno == EINTR);
181
- if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
182
- waiter->notified = true;
183
- }
145
+ (void)containers_waiter_notify(&waiter->notification);
184
146
  }
185
147
  }
186
148
 
@@ -260,7 +222,7 @@ map_memsize(const void *pointer)
260
222
  }
261
223
 
262
224
  static const rb_data_type_t map_type = {
263
- .wrap_struct_name = "Ractor::Containers::Map",
225
+ .wrap_struct_name = "Farce::Internal::Map",
264
226
  .function = {
265
227
  .dmark = map_mark,
266
228
  .dfree = map_free,
@@ -430,13 +392,10 @@ static VALUE
430
392
  map_wait_body(VALUE opaque)
431
393
  {
432
394
  map_wait_context_t *context = (map_wait_context_t *)opaque;
433
- VALUE wait_timeout = Qnil;
434
- if (context->timeout->finite) {
435
- double remaining = context->timeout->deadline - map_monotonic_now();
436
- if (remaining <= 0) return Qfalse;
437
- wait_timeout = DBL2NUM(remaining);
438
- }
439
- return containers_wait_for_readable(context->waiter->read_fd, wait_timeout) ? Qtrue : Qfalse;
395
+ return containers_waiter_wait(
396
+ &context->waiter->notification, &context->map->lock,
397
+ context->timeout->finite, context->timeout->deadline, NULL
398
+ ) ? Qtrue : Qfalse;
440
399
  }
441
400
 
442
401
  static VALUE
@@ -451,8 +410,7 @@ map_wait_cleanup(VALUE opaque)
451
410
  pthread_mutex_unlock(&context->map->lock);
452
411
  context->registered = false;
453
412
  }
454
- close(context->waiter->read_fd);
455
- close(context->waiter->write_fd);
413
+ containers_waiter_close(&context->waiter->notification);
456
414
  return Qnil;
457
415
  }
458
416
 
@@ -460,13 +418,11 @@ static bool
460
418
  map_wait_once(map_t *map, uint64_t generation, map_timeout_t *timeout)
461
419
  {
462
420
  map_waiter_t waiter;
463
- map_waiter_initialize(&waiter);
421
+ containers_waiter_init(&waiter.notification);
464
422
 
465
423
  pthread_mutex_lock(&map->lock);
466
424
  if (map->generation != generation) {
467
425
  pthread_mutex_unlock(&map->lock);
468
- close(waiter.read_fd);
469
- close(waiter.write_fd);
470
426
  return true;
471
427
  }
472
428
  waiter.next = map->waiters;
@@ -1461,9 +1417,15 @@ map_modify_body(VALUE opaque)
1461
1417
  }
1462
1418
 
1463
1419
  static VALUE
1464
- map_modify(VALUE self, VALUE key)
1420
+ map_modify(int argc, VALUE *argv, VALUE self)
1465
1421
  {
1466
1422
  rb_check_frozen(self);
1423
+ VALUE key;
1424
+ VALUE keywords = Qnil;
1425
+ VALUE keyword_values[1] = {Qundef};
1426
+ ID keyword_ids[] = {rb_intern("timeout")};
1427
+ rb_scan_args(argc, argv, "1:", &key, &keywords);
1428
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1467
1429
  rb_need_block();
1468
1430
  map_t *map = get_map(self);
1469
1431
  key = map_prepare_key(map, key);
@@ -1476,7 +1438,12 @@ map_modify(VALUE self, VALUE key)
1476
1438
  };
1477
1439
  operation.hash = map_key_hash(map, key);
1478
1440
  rb_check_frozen(self);
1479
- map_lock_for_key(map, key, operation.hash, &operation.execution);
1441
+ map_timeout_t timeout = map_parse_timeout(
1442
+ keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1443
+ );
1444
+ if (!map_lock_for_key_with_timeout(
1445
+ map, key, operation.hash, &timeout, &operation.execution
1446
+ )) return Qfalse;
1480
1447
  bool found;
1481
1448
  size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
1482
1449
  if (RB_OBJ_FROZEN(self)) {
@@ -2195,7 +2162,7 @@ map_define_methods(VALUE klass)
2195
2162
  rb_define_method(klass, "store", map_store_with_timeout, -1);
2196
2163
  rb_define_method(klass, "swap", map_swap, -1);
2197
2164
  rb_define_method(klass, "update", map_update, -1);
2198
- rb_define_method(klass, "modify", map_modify, 1);
2165
+ rb_define_method(klass, "modify", map_modify, -1);
2199
2166
  rb_define_method(klass, "wait_until_changed", map_wait_until_changed, -1);
2200
2167
  rb_define_method(klass, "wait_until_non_nil", map_wait_until_non_nil, -1);
2201
2168
  rb_define_method(klass, "store_if_absent", map_store_if_absent, -1);
@@ -0,0 +1,139 @@
1
+ #include "native_wait.h"
2
+ #include "ruby/fiber/scheduler.h"
3
+
4
+ #include <errno.h>
5
+ #include <fcntl.h>
6
+ #include <time.h>
7
+ #include <unistd.h>
8
+
9
+ static double
10
+ waiter_monotonic_now(void)
11
+ {
12
+ struct timespec now;
13
+ #ifdef CLOCK_MONOTONIC
14
+ if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
15
+ return (double)now.tv_sec + (double)now.tv_nsec / 1000000000.0;
16
+ }
17
+ #endif
18
+ struct timeval fallback;
19
+ gettimeofday(&fallback, NULL);
20
+ return (double)fallback.tv_sec + (double)fallback.tv_usec / 1000000.0;
21
+ }
22
+
23
+ static void
24
+ waiter_set_fd_flags(int fd)
25
+ {
26
+ #ifndef _WIN32
27
+ int flags = fcntl(fd, F_GETFL, 0);
28
+ if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
29
+ flags = fcntl(fd, F_GETFD, 0);
30
+ if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
31
+ #endif
32
+ }
33
+
34
+ void
35
+ containers_waiter_init(containers_waiter_t *waiter)
36
+ {
37
+ containers_futex_init(&waiter->futex);
38
+ waiter->read_fd = waiter->write_fd = -1;
39
+ waiter->mode = CONTAINERS_WAITER_PENDING;
40
+ waiter->notified = false;
41
+ }
42
+
43
+ bool
44
+ containers_waiter_notify(containers_waiter_t *waiter)
45
+ {
46
+ if (waiter->notified) return true;
47
+ if (waiter->mode == CONTAINERS_WAITER_FUTEX) {
48
+ waiter->notified = true;
49
+ containers_futex_notify(&waiter->futex);
50
+ return true;
51
+ }
52
+
53
+ /* A pending waiter has not selected its backend yet. Latch its wake so
54
+ * setup can return immediately or a future kernel wait sees a mismatch. */
55
+ atomic_store_explicit(&waiter->futex.value, 1, memory_order_release);
56
+ if (waiter->mode == CONTAINERS_WAITER_PENDING) {
57
+ waiter->notified = true;
58
+ return true;
59
+ }
60
+
61
+ unsigned char byte = 1;
62
+ ssize_t result;
63
+ do {
64
+ result = write(waiter->write_fd, &byte, 1);
65
+ } while (result < 0 && errno == EINTR);
66
+ if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
67
+ waiter->notified = true;
68
+ }
69
+ return waiter->notified;
70
+ }
71
+
72
+ bool
73
+ containers_waiter_wait(
74
+ containers_waiter_t *waiter,
75
+ pthread_mutex_t *guard,
76
+ bool finite,
77
+ double deadline,
78
+ int *pipe_error
79
+ )
80
+ {
81
+ if (pipe_error) *pipe_error = 0;
82
+ if (atomic_load_explicit(&waiter->futex.value, memory_order_acquire) == 1) return true;
83
+ if (finite && deadline <= waiter_monotonic_now()) return false;
84
+
85
+ /* Selection can enter Ruby or probe the kernel. The registration stays
86
+ * pending, with no native container mutex held, throughout this work. */
87
+ VALUE scheduler = rb_fiber_scheduler_current();
88
+ /* Blocking futex waits failed the cross-core thread handoff performance
89
+ * gate. Keep Ruby's managed IO path for threads and use futexes only when
90
+ * the owning event loop can complete them asynchronously. */
91
+ bool use_futex = !NIL_P(scheduler) && containers_futex_scheduler_supported(scheduler);
92
+
93
+ int descriptors[2] = {-1, -1};
94
+ if (!use_futex) {
95
+ if (pipe(descriptors) != 0) {
96
+ int error = errno;
97
+ if (atomic_load_explicit(&waiter->futex.value, memory_order_acquire) == 1) return true;
98
+ if (pipe_error) {
99
+ *pipe_error = error;
100
+ return false;
101
+ }
102
+ rb_syserr_fail(error, "pipe");
103
+ }
104
+ waiter_set_fd_flags(descriptors[0]);
105
+ waiter_set_fd_flags(descriptors[1]);
106
+ }
107
+
108
+ /* No Ruby calls occur between creating descriptors and recording ownership.
109
+ * A notifier either latches a pending wake or observes the complete backend. */
110
+ if (guard) pthread_mutex_lock(guard);
111
+ waiter->read_fd = descriptors[0];
112
+ waiter->write_fd = descriptors[1];
113
+ waiter->mode = use_futex ? CONTAINERS_WAITER_FUTEX : CONTAINERS_WAITER_IO;
114
+ bool notified = waiter->notified;
115
+ if (guard) pthread_mutex_unlock(guard);
116
+ if (notified) return true;
117
+
118
+ VALUE timeout = Qnil;
119
+ if (finite) {
120
+ double remaining = deadline - waiter_monotonic_now();
121
+ if (remaining <= 0) {
122
+ return atomic_load_explicit(&waiter->futex.value, memory_order_acquire) == 1;
123
+ }
124
+ timeout = DBL2NUM(remaining);
125
+ }
126
+
127
+ if (use_futex) {
128
+ return containers_futex_scheduler_wait(scheduler, &waiter->futex, timeout);
129
+ }
130
+ return containers_wait_for_readable(waiter->read_fd, timeout);
131
+ }
132
+
133
+ void
134
+ containers_waiter_close(containers_waiter_t *waiter)
135
+ {
136
+ if (waiter->read_fd >= 0) close(waiter->read_fd);
137
+ if (waiter->write_fd >= 0) close(waiter->write_fd);
138
+ waiter->read_fd = waiter->write_fd = -1;
139
+ }
@@ -0,0 +1,37 @@
1
+ #ifndef FARCE_NATIVE_WAIT_H
2
+ #define FARCE_NATIVE_WAIT_H
3
+
4
+ #include "containers.h"
5
+ #include "futex.h"
6
+
7
+ typedef enum {
8
+ CONTAINERS_WAITER_PENDING,
9
+ CONTAINERS_WAITER_FUTEX,
10
+ CONTAINERS_WAITER_IO
11
+ } containers_waiter_mode_t;
12
+
13
+ typedef struct {
14
+ containers_futex_t futex;
15
+ int read_fd, write_fd;
16
+ containers_waiter_mode_t mode;
17
+ bool notified;
18
+ } containers_waiter_t;
19
+
20
+ /* Initialize before linking under the container's guard. Notification is
21
+ * native-only and uses that same guard. A NULL guard denotes owner-local state. */
22
+ void containers_waiter_init(containers_waiter_t *waiter);
23
+ bool containers_waiter_notify(containers_waiter_t *waiter);
24
+ /* Run outside the guard and inside the caller's cleanup ensure. The absolute
25
+ * monotonic deadline includes backend setup. Optional pipe_error lets callers
26
+ * retain their own descriptor-pressure recovery; other errors raise normally. */
27
+ bool containers_waiter_wait(
28
+ containers_waiter_t *waiter,
29
+ pthread_mutex_t *guard,
30
+ bool finite,
31
+ double deadline,
32
+ int *pipe_error
33
+ );
34
+ /* Unlink under the guard first. Waiting has already drained native requests. */
35
+ void containers_waiter_close(containers_waiter_t *waiter);
36
+
37
+ #endif
@@ -1,4 +1,5 @@
1
1
  #include "containers.h"
2
+ #include "native_wait.h"
2
3
  #include "unshared_wait.h"
3
4
  #include <ruby/atomic.h>
4
5
  #include <ruby/fiber/scheduler.h>
@@ -348,9 +349,7 @@ priority_queue_bucket_unlink(
348
349
  }
349
350
 
350
351
  typedef struct priority_queue_lock_waiter {
351
- int read_fd;
352
- int write_fd;
353
- bool notified;
352
+ containers_waiter_t notification;
354
353
  struct priority_queue_lock_waiter *next;
355
354
  } priority_queue_lock_waiter_t;
356
355
 
@@ -753,19 +752,6 @@ priority_queue_get(VALUE self)
753
752
  return queue;
754
753
  }
755
754
 
756
- static void
757
- priority_queue_lock_set_fd_flags(int fd)
758
- {
759
- #ifdef _WIN32
760
- (void)fd;
761
- #else
762
- int flags = fcntl(fd, F_GETFL, 0);
763
- if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
764
- flags = fcntl(fd, F_GETFD, 0);
765
- if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
766
- #endif
767
- }
768
-
769
755
  /* Called with queue->lock held. A logical unlock only needs to make one
770
756
  * waiter runnable. The selected waiter remains linked until ensure cleanup. */
771
757
  static void
@@ -777,20 +763,9 @@ priority_queue_lock_notify_one_locked(priority_queue_t *queue)
777
763
  * next waiter. Reentrant operations can register for a later unlock. */
778
764
  farce_unshared_wait_notify_all(&queue->unshared_lock_waiters);
779
765
  }
780
- unsigned char byte = 1;
781
- priority_queue_lock_waiter_t *waiter;
782
-
783
- for (waiter = queue->waiters; waiter; waiter = waiter->next) {
784
- ssize_t result;
785
- if (waiter->notified) continue;
786
- do {
787
- result = write(waiter->write_fd, &byte, 1);
788
- } while (result < 0 && errno == EINTR);
789
- if (result == 1 ||
790
- (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
791
- waiter->notified = true;
792
- return;
793
- }
766
+ for (priority_queue_lock_waiter_t *waiter = queue->waiters; waiter; waiter = waiter->next) {
767
+ if (waiter->notification.notified) continue;
768
+ if (containers_waiter_notify(&waiter->notification)) return;
794
769
  }
795
770
  }
796
771
 
@@ -799,18 +774,16 @@ typedef struct {
799
774
  priority_queue_lock_waiter_t waiter;
800
775
  } priority_queue_lock_wait_context_t;
801
776
 
802
- static bool
803
- priority_queue_lock_wait_for_descriptor(int fd)
804
- {
805
- return containers_wait_for_readable(fd, Qnil);
806
- }
807
-
808
777
  static VALUE
809
778
  priority_queue_lock_wait_body(VALUE opaque)
810
779
  {
811
780
  priority_queue_lock_wait_context_t *context =
812
781
  (priority_queue_lock_wait_context_t *)opaque;
813
- bool awakened = priority_queue_lock_wait_for_descriptor(context->waiter.read_fd);
782
+ bool awakened = containers_waiter_wait(
783
+ &context->waiter.notification,
784
+ context->queue->shared ? &context->queue->lock : NULL,
785
+ false, 0, NULL
786
+ );
814
787
  return awakened ? Qtrue : Qfalse;
815
788
  }
816
789
 
@@ -835,10 +808,10 @@ priority_queue_lock_wait_cleanup(VALUE opaque)
835
808
  }
836
809
  /* A notification only makes this waiter runnable; it does not reserve
837
810
  * ownership. Pass the baton whenever a notified waiter leaves while the
838
- * queue is still ownerless. This covers both interruption inside rb_io_wait
811
+ * queue is still ownerless. This covers both interruption while waiting
839
812
  * and cancellation after a normal wake but before the acquisition loop can
840
813
  * claim ownership, so later waiters cannot be stranded. */
841
- if (context->waiter.notified &&
814
+ if (context->waiter.notification.notified &&
842
815
  NIL_P(context->queue->owner_fiber) &&
843
816
  !priority_queue_publication_busy(
844
817
  RUBY_ATOMIC_LOAD(context->queue->publication_state)
@@ -846,8 +819,7 @@ priority_queue_lock_wait_cleanup(VALUE opaque)
846
819
  priority_queue_lock_notify_one_locked(context->queue);
847
820
  }
848
821
  priority_queue_mutex_unlock(context->queue);
849
- close(context->waiter.read_fd);
850
- close(context->waiter.write_fd);
822
+ containers_waiter_close(&context->waiter.notification);
851
823
  return Qnil;
852
824
  }
853
825
 
@@ -859,25 +831,11 @@ priority_queue_lock_wait_once(priority_queue_t *queue)
859
831
  (void)farce_unshared_wait(&queue->unshared_lock_waiters, queue->unshared_self, false, 0);
860
832
  return;
861
833
  }
862
- int descriptors[2];
863
- priority_queue_lock_wait_context_t context;
864
-
865
- if (pipe(descriptors) != 0) {
866
- priority_queue_mutex_unlock(queue);
867
- rb_sys_fail("pipe");
868
- }
869
- priority_queue_lock_set_fd_flags(descriptors[0]);
870
- priority_queue_lock_set_fd_flags(descriptors[1]);
871
-
872
- context = (priority_queue_lock_wait_context_t){
834
+ priority_queue_lock_wait_context_t context = {
873
835
  .queue = queue,
874
- .waiter = {
875
- .read_fd = descriptors[0],
876
- .write_fd = descriptors[1],
877
- .notified = false,
878
- .next = NULL,
879
- },
836
+ .waiter = {.next = NULL},
880
837
  };
838
+ containers_waiter_init(&context.waiter.notification);
881
839
  if (queue->last_waiter) queue->last_waiter->next = &context.waiter;
882
840
  else queue->waiters = &context.waiter;
883
841
  queue->last_waiter = &context.waiter;
@@ -2006,11 +1964,55 @@ priority_queue_oldest_age(VALUE self)
2006
1964
  return priority_queue_call(self, queue, priority_queue_oldest_age_body, (VALUE)&operation);
2007
1965
  }
2008
1966
 
1967
+ static VALUE
1968
+ priority_queue_capacity_body(VALUE opaque)
1969
+ {
1970
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1971
+ return operation->queue->bounded ? SIZET2NUM(operation->queue->capacity) : Qnil;
1972
+ }
1973
+
2009
1974
  static VALUE
2010
1975
  priority_queue_capacity(VALUE self)
2011
1976
  {
2012
1977
  priority_queue_t *queue = priority_queue_get(self);
2013
- return queue->bounded ? SIZET2NUM(queue->capacity) : Qnil;
1978
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1979
+ if (priority_queue_can_run_direct(queue)) return priority_queue_capacity_body((VALUE)&operation);
1980
+ return priority_queue_call(self, queue, priority_queue_capacity_body, (VALUE)&operation);
1981
+ }
1982
+
1983
+ typedef struct {
1984
+ priority_queue_t *queue;
1985
+ VALUE value;
1986
+ size_t capacity;
1987
+ bool bounded;
1988
+ } priority_queue_resize_t;
1989
+
1990
+ static VALUE
1991
+ priority_queue_set_capacity_body(VALUE opaque)
1992
+ {
1993
+ priority_queue_resize_t *resize = (priority_queue_resize_t *)opaque;
1994
+ priority_queue_notify_before_commit(resize->queue);
1995
+ resize->queue->capacity = resize->capacity;
1996
+ resize->queue->bounded = resize->bounded;
1997
+ return resize->value;
1998
+ }
1999
+
2000
+ static VALUE
2001
+ priority_queue_set_capacity(VALUE self, VALUE value)
2002
+ {
2003
+ priority_queue_t *queue = priority_queue_get(self);
2004
+ priority_queue_resize_t resize = {.queue = queue, .value = value, .capacity = 0, .bounded = !NIL_P(value)};
2005
+ if (resize.bounded) {
2006
+ resize.value = rb_to_int(value);
2007
+ long long capacity = NUM2LL(resize.value);
2008
+ if (capacity <= 0) rb_raise(rb_eArgError, "capacity must be positive or nil");
2009
+ if ((unsigned long long)capacity > (unsigned long long)SIZE_MAX) {
2010
+ rb_raise(rb_eArgError, "capacity is too large");
2011
+ }
2012
+ resize.capacity = (size_t)capacity;
2013
+ }
2014
+ if (priority_queue_can_run_direct(queue)) return priority_queue_set_capacity_body((VALUE)&resize);
2015
+ return priority_queue_call(self, queue, priority_queue_set_capacity_body, (VALUE)&resize);
2014
2016
  }
2015
2017
 
2016
2018
  static void
@@ -2041,6 +2043,7 @@ priority_queue_define_methods(VALUE klass)
2041
2043
  rb_define_method(klass, "oldest_enqueued_at", priority_queue_oldest_enqueued_at, 0);
2042
2044
  rb_define_method(klass, "oldest_age", priority_queue_oldest_age, 0);
2043
2045
  rb_define_method(klass, "capacity", priority_queue_capacity, 0);
2046
+ rb_define_method(klass, "capacity=", priority_queue_set_capacity, 1);
2044
2047
  rb_define_method(klass, "freeze", priority_queue_freeze, 0);
2045
2048
  rb_define_method(klass, "frozen?", priority_queue_frozen_p, 0);
2046
2049
  }