farce 0.3.0-x86_64-linux-gnu → 0.4.1-x86_64-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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +117 -1489
  4. data/docs/compatibility.md +74 -0
  5. data/docs/concurrency.md +151 -0
  6. data/docs/data-structures.md +313 -0
  7. data/docs/installation.md +63 -0
  8. data/docs/integrations.md +124 -0
  9. data/docs/introduction.md +266 -0
  10. data/docs/scheduling.md +176 -0
  11. data/docs/sharing.md +211 -0
  12. data/docs/shims.md +144 -0
  13. data/docs/utilities.md +93 -0
  14. data/docs/variants-and-scopes.md +92 -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 +8 -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 +10 -54
  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 +16 -58
  31. data/ext/farce/queue.c +1 -1
  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 +13 -60
  39. data/ext/farce/weak_map.c +1 -1
  40. data/lib/farce/abstract/flag.rb +6 -0
  41. data/lib/farce/backports.rb +274 -0
  42. data/lib/farce/engine/jruby/fiber_scheduler.rb +3 -0
  43. data/lib/farce/engine/jruby/scheduler_synchronization.rb +87 -0
  44. data/lib/farce/engine/ruby/3.4/farce.so +0 -0
  45. data/lib/farce/engine/ruby/3.4/null_port.rb +21 -0
  46. data/lib/farce/engine/ruby/3.4/patch.rb +67 -0
  47. data/lib/farce/engine/ruby/3.4/port.rb +13 -12
  48. data/lib/farce/engine/ruby/3.4/ractor_methods.rb +0 -1
  49. data/lib/farce/engine/ruby/4.0/farce.so +0 -0
  50. data/lib/farce/engine/ruby/4.0/null_port.rb +36 -0
  51. data/lib/farce/engine/ruby/4.0/port.rb +1 -2
  52. data/lib/farce/engine/ruby/4.1/null_port.rb +5 -0
  53. data/lib/farce/engine/ruby.rb +7 -0
  54. data/lib/farce/engine/shared/null_port.rb +18 -0
  55. data/lib/farce/engine/shared/port.rb +41 -3
  56. data/lib/farce/engine/shared/queue.rb +4 -4
  57. data/lib/farce/engine/shared/ractor_methods.rb +2 -2
  58. data/lib/farce/engine/shared/ractor_selector.rb +73 -0
  59. data/lib/farce/extension.rb +150 -0
  60. data/lib/farce/integrations/ractor_tmvar.rb +4 -1
  61. data/lib/farce/internal/fake_ractor.rb +10 -2
  62. data/lib/farce/internal/scheduler_lifecycle.rb +7 -4
  63. data/lib/farce/null_port.rb +12 -0
  64. data/lib/farce/system.rb +10 -2
  65. data/lib/farce/version.rb +1 -1
  66. data/lib/farce.rb +1 -1
  67. metadata +27 -1
data/ext/farce/signal.c CHANGED
@@ -1,21 +1,16 @@
1
1
  #include "containers.h"
2
- #include "ruby/io.h"
2
+ #include "native_wait.h"
3
3
 
4
- #include <errno.h>
5
- #include <fcntl.h>
6
4
  #include <math.h>
7
5
  #include <stdint.h>
8
6
  #include <time.h>
9
- #include <unistd.h>
10
7
 
11
8
  static VALUE cSignal;
12
9
 
13
10
  typedef struct containers_signal_waiter containers_signal_waiter_t;
14
11
 
15
12
  struct containers_signal_waiter {
16
- int read_fd;
17
- int write_fd;
18
- bool notified;
13
+ containers_waiter_t notification;
19
14
  containers_signal_waiter_t *next;
20
15
  };
21
16
 
@@ -66,34 +61,13 @@ containers_signal_parse_timeout(VALUE value)
66
61
  return timeout;
67
62
  }
68
63
 
69
- static void
70
- containers_signal_set_fd_flags(int fd)
71
- {
72
- #ifdef _WIN32
73
- (void)fd;
74
- #else
75
- int flags = fcntl(fd, F_GETFL, 0);
76
- if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
77
- flags = fcntl(fd, F_GETFD, 0);
78
- if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
79
- #endif
80
- }
81
-
82
- /* Called with signal->lock held. Each waiter has a separate descriptor, so a
83
- * single broadcast makes every scheduled Fiber or native thread runnable. */
64
+ /* Called with signal->lock held. The notification remains latched while
65
+ * the waiting caller selects its parking backend outside the state mutex. */
84
66
  static void
85
67
  containers_signal_notify_waiters_locked(containers_signal_t *signal)
86
68
  {
87
- unsigned char byte = 1;
88
69
  for (containers_signal_waiter_t *waiter = signal->waiters; waiter; waiter = waiter->next) {
89
- if (waiter->notified) continue;
90
- ssize_t result;
91
- do {
92
- result = write(waiter->write_fd, &byte, 1);
93
- } while (result < 0 && errno == EINTR);
94
- if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
95
- waiter->notified = true;
96
- }
70
+ (void)containers_waiter_notify(&waiter->notification);
97
71
  }
98
72
  }
99
73
 
@@ -112,7 +86,7 @@ containers_signal_memsize(const void *pointer)
112
86
  }
113
87
 
114
88
  static const rb_data_type_t containers_signal_type = {
115
- .wrap_struct_name = "Ractor::Containers::Signal",
89
+ .wrap_struct_name = "Farce::Internal::Signal",
116
90
  .function = {
117
91
  .dmark = NULL,
118
92
  .dfree = containers_signal_free,
@@ -184,26 +158,14 @@ containers_signal_new(void)
184
158
  return containers_signal_initialize(signal);
185
159
  }
186
160
 
187
- static bool
188
- containers_signal_wait_for_descriptor(int fd, containers_signal_timeout_t *timeout)
189
- {
190
- VALUE wait_timeout = Qnil;
191
- if (timeout->finite) {
192
- double remaining = timeout->deadline - containers_signal_monotonic_now();
193
- if (remaining <= 0) return false;
194
- wait_timeout = DBL2NUM(remaining);
195
- }
196
-
197
- return containers_wait_for_readable(fd, wait_timeout);
198
- }
199
-
200
161
  static VALUE
201
162
  containers_signal_wait_body(VALUE opaque)
202
163
  {
203
164
  containers_signal_wait_context_t *context = (containers_signal_wait_context_t *)opaque;
204
- return containers_signal_wait_for_descriptor(context->waiter.read_fd, context->timeout)
205
- ? Qtrue
206
- : Qfalse;
165
+ return containers_waiter_wait(
166
+ &context->waiter.notification, &context->signal->lock,
167
+ context->timeout->finite, context->timeout->deadline, NULL
168
+ ) ? Qtrue : Qfalse;
207
169
  }
208
170
 
209
171
  static VALUE
@@ -220,8 +182,7 @@ containers_signal_wait_cleanup(VALUE opaque)
220
182
  }
221
183
  }
222
184
  pthread_mutex_unlock(&context->signal->lock);
223
- close(context->waiter.read_fd);
224
- close(context->waiter.write_fd);
185
+ containers_waiter_close(&context->waiter.notification);
225
186
  return Qnil;
226
187
  }
227
188
 
@@ -232,24 +193,12 @@ containers_signal_wait_once(
232
193
  containers_signal_timeout_t *timeout
233
194
  )
234
195
  {
235
- int descriptors[2];
236
- if (pipe(descriptors) != 0) {
237
- pthread_mutex_unlock(&signal->lock);
238
- rb_sys_fail("pipe");
239
- }
240
- containers_signal_set_fd_flags(descriptors[0]);
241
- containers_signal_set_fd_flags(descriptors[1]);
242
-
243
196
  containers_signal_wait_context_t context = {
244
197
  .signal = signal,
245
- .waiter = {
246
- .read_fd = descriptors[0],
247
- .write_fd = descriptors[1],
248
- .notified = false,
249
- .next = signal->waiters,
250
- },
198
+ .waiter = {.next = signal->waiters},
251
199
  .timeout = timeout,
252
200
  };
201
+ containers_waiter_init(&context.waiter.notification);
253
202
  signal->waiters = &context.waiter;
254
203
  pthread_mutex_unlock(&signal->lock);
255
204
  return RTEST(rb_ensure(
@@ -330,6 +279,10 @@ containers_signal_wait(int argc, VALUE *argv, VALUE self)
330
279
  pthread_mutex_unlock(&signal->lock);
331
280
  return ULL2NUM(generation);
332
281
  }
282
+ if (timeout.finite && timeout.deadline <= containers_signal_monotonic_now()) {
283
+ pthread_mutex_unlock(&signal->lock);
284
+ return containers_signal_timeout_result();
285
+ }
333
286
  bool awakened = containers_signal_wait_once(signal, &timeout);
334
287
  pthread_mutex_lock(&signal->lock);
335
288
  if (!awakened && signal->generation == observed) {
data/ext/farce/tree_map.c CHANGED
@@ -1,4 +1,5 @@
1
1
  #include "containers.h"
2
+ #include "native_wait.h"
2
3
  #include "transaction.h"
3
4
  #include <ruby/atomic.h>
4
5
  #include <ruby/fiber/scheduler.h>
@@ -558,9 +559,7 @@ tc_map_store_unlocked(VALUE self, tc_map *map, VALUE key, VALUE value,
558
559
  typedef struct tc_core_map_waiter tc_core_map_waiter;
559
560
 
560
561
  struct tc_core_map_waiter {
561
- int read_fd;
562
- int write_fd;
563
- bool notified;
562
+ containers_waiter_t notification;
564
563
  tc_core_map_waiter *next;
565
564
  };
566
565
 
@@ -787,40 +786,14 @@ typedef struct {
787
786
  tc_core_map_waiter waiter;
788
787
  } tc_core_map_fiber_wait;
789
788
 
790
- static void
791
- tc_core_map_set_fd_flags(int fd)
792
- {
793
- #ifdef _WIN32
794
- (void)fd;
795
- #else
796
- int flags = fcntl(fd, F_GETFL, 0);
797
- if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
798
- flags = fcntl(fd, F_GETFD, 0);
799
- if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
800
- #endif
801
- }
802
-
803
789
  /* Called with core->lock held. The waiter remains linked until its ensure
804
790
  * cleanup so an interrupt cannot leave a dangling stack pointer behind. */
805
791
  static void
806
792
  tc_core_map_notify_one_locked(tc_core_map *core)
807
793
  {
808
- unsigned char byte = 1;
809
-
810
- for (tc_core_map_waiter *waiter = core->waiters;
811
- waiter != NULL;
812
- waiter = waiter->next) {
813
- ssize_t result;
814
-
815
- if (waiter->notified) continue;
816
- do {
817
- result = write(waiter->write_fd, &byte, 1);
818
- } while (result < 0 && errno == EINTR);
819
- if (result == 1 ||
820
- (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
821
- waiter->notified = true;
822
- return;
823
- }
794
+ for (tc_core_map_waiter *waiter = core->waiters; waiter; waiter = waiter->next) {
795
+ if (waiter->notification.notified) continue;
796
+ if (containers_waiter_notify(&waiter->notification)) return;
824
797
  }
825
798
  }
826
799
 
@@ -828,7 +801,9 @@ static VALUE
828
801
  tc_core_map_fiber_wait_body(VALUE opaque)
829
802
  {
830
803
  tc_core_map_fiber_wait *wait = (tc_core_map_fiber_wait *)opaque;
831
- return containers_wait_for_readable(wait->waiter.read_fd, Qnil) ? Qtrue : Qfalse;
804
+ return containers_waiter_wait(
805
+ &wait->waiter.notification, &wait->core->lock, false, 0, NULL
806
+ ) ? Qtrue : Qfalse;
832
807
  }
833
808
 
834
809
  static VALUE
@@ -851,9 +826,9 @@ tc_core_map_fiber_wait_cleanup(VALUE opaque)
851
826
  }
852
827
  /* Notification makes a waiter runnable, but does not reserve ownership.
853
828
  * Always pass the baton if a notified waiter leaves while ownerless,
854
- * including cancellation after rb_io_wait returned but before the lock
829
+ * including cancellation after a wait returned but before the lock
855
830
  * acquisition loop claimed ownership. */
856
- if (wait->waiter.notified &&
831
+ if (wait->waiter.notification.notified &&
857
832
  NIL_P(wait->core->owner_fiber) &&
858
833
  !tc_core_map_publication_busy(
859
834
  RUBY_ATOMIC_LOAD(wait->core->publication_state)
@@ -862,8 +837,7 @@ tc_core_map_fiber_wait_cleanup(VALUE opaque)
862
837
  }
863
838
  pthread_mutex_unlock(&wait->core->lock);
864
839
 
865
- close(wait->waiter.read_fd);
866
- close(wait->waiter.write_fd);
840
+ containers_waiter_close(&wait->waiter.notification);
867
841
  return Qnil;
868
842
  }
869
843
 
@@ -871,24 +845,11 @@ tc_core_map_fiber_wait_cleanup(VALUE opaque)
871
845
  static void
872
846
  tc_core_map_wait_once(tc_core_map *core)
873
847
  {
874
- int descriptors[2];
875
-
876
- if (pipe(descriptors) != 0) {
877
- pthread_mutex_unlock(&core->lock);
878
- rb_sys_fail("pipe");
879
- }
880
- tc_core_map_set_fd_flags(descriptors[0]);
881
- tc_core_map_set_fd_flags(descriptors[1]);
882
-
883
848
  tc_core_map_fiber_wait wait = {
884
849
  .core = core,
885
- .waiter = {
886
- .read_fd = descriptors[0],
887
- .write_fd = descriptors[1],
888
- .notified = false,
889
- .next = NULL,
890
- },
850
+ .waiter = {.next = NULL},
891
851
  };
852
+ containers_waiter_init(&wait.waiter.notification);
892
853
 
893
854
  if (core->last_waiter != NULL) core->last_waiter->next = &wait.waiter;
894
855
  else core->waiters = &wait.waiter;
@@ -904,8 +865,8 @@ tc_core_map_wait_once(tc_core_map *core)
904
865
  }
905
866
 
906
867
  /* CRuby needs logical Fiber ownership rather than pthread ownership: multiple
907
- * scheduled Fibers can contend on the same native thread. Descriptor waits
908
- * let rb_io_wait park only the current Fiber while its owner resumes. */
868
+ * scheduled Fibers can contend on the same native thread. Native notifications
869
+ * park only the current Fiber while its owner resumes. */
909
870
  static void
910
871
  tc_core_map_lock(VALUE self, tc_core_map *core)
911
872
  {
@@ -40,7 +40,7 @@ containers_marker_size(const void *pointer)
40
40
  }
41
41
 
42
42
  static const rb_data_type_t containers_pin_marker_type = {
43
- .wrap_struct_name = "Ractor::Containers pin marker",
43
+ .wrap_struct_name = "Farce pin marker",
44
44
  .function = {
45
45
  .dmark = containers_marker_mark,
46
46
  .dfree = RUBY_TYPED_DEFAULT_FREE,
@@ -1,6 +1,18 @@
1
- #include "ruby.h"
1
+ #include "futex.h"
2
2
 
3
3
  void farce_init_fiber_scheduler(VALUE internal) {
4
4
  (void)internal;
5
5
  rb_raise(rb_eLoadError, "native Farce fiber scheduling requires epoll or kqueue; use the select implementation");
6
6
  }
7
+
8
+ bool containers_futex_scheduler_supported(VALUE scheduler) {
9
+ (void)scheduler;
10
+ return false;
11
+ }
12
+
13
+ bool containers_futex_scheduler_wait(VALUE scheduler, containers_futex_t *word, VALUE timeout) {
14
+ (void)scheduler;
15
+ (void)word;
16
+ (void)timeout;
17
+ return true;
18
+ }
data/ext/farce/vector.c CHANGED
@@ -1,15 +1,13 @@
1
1
  #include "containers.h"
2
+ #include "native_wait.h"
2
3
  #include "transaction.h"
3
- #include "ruby/io.h"
4
4
  #include "ruby/fiber/scheduler.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 <stdlib.h>
11
10
  #include <time.h>
12
- #include <unistd.h>
13
11
 
14
12
  #define VECTOR_INITIAL_CAPACITY 16
15
13
  static VALUE cVector;
@@ -19,9 +17,7 @@ static VALUE vector_transaction_marker;
19
17
  typedef struct vector_waiter vector_waiter_t;
20
18
 
21
19
  struct vector_waiter {
22
- int read_fd;
23
- int write_fd;
24
- bool notified;
20
+ containers_waiter_t notification;
25
21
  vector_waiter_t *next;
26
22
  };
27
23
 
@@ -85,7 +81,7 @@ vector_memsize(const void *pointer)
85
81
  }
86
82
 
87
83
  static const rb_data_type_t vector_type = {
88
- .wrap_struct_name = "Ractor::Containers::Vector",
84
+ .wrap_struct_name = "Farce::Internal::Vector",
89
85
  .function = {
90
86
  .dmark = vector_mark,
91
87
  .dfree = vector_free,
@@ -214,35 +210,14 @@ vector_timeout_keyword(VALUE keywords)
214
210
  return timeout;
215
211
  }
216
212
 
217
- static void
218
- vector_set_fd_flags(int fd)
219
- {
220
- #ifdef _WIN32
221
- (void)fd;
222
- #else
223
- int flags = fcntl(fd, F_GETFL, 0);
224
- if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
225
- flags = fcntl(fd, F_GETFD, 0);
226
- if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
227
- #endif
228
- }
229
-
230
- /* Called with vector->lock held. Each waiter has an independent pipe, so a
231
- * transition wakes every waiting thread or scheduled fiber. */
213
+ /* Called with vector->lock held. A transition wakes every registered
214
+ * thread or fiber without sharing a consumable notification. */
232
215
  static void
233
216
  vector_notify_waiters_locked(vector_t *vector)
234
217
  {
235
- unsigned char byte = 1;
236
218
  vector->generation++;
237
219
  for (vector_waiter_t *waiter = vector->waiters; waiter; waiter = waiter->next) {
238
- if (waiter->notified) continue;
239
- ssize_t result;
240
- do {
241
- result = write(waiter->write_fd, &byte, 1);
242
- } while (result < 0 && errno == EINTR);
243
- if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
244
- waiter->notified = true;
245
- }
220
+ (void)containers_waiter_notify(&waiter->notification);
246
221
  }
247
222
  }
248
223
 
@@ -259,23 +234,14 @@ typedef struct {
259
234
  vector_timeout_t *timeout;
260
235
  } vector_wait_context_t;
261
236
 
262
- static bool
263
- vector_wait_for_descriptor(int fd, vector_timeout_t *timeout)
264
- {
265
- VALUE wait_timeout = Qnil;
266
- if (timeout->finite) {
267
- double remaining = timeout->deadline - vector_monotonic_now();
268
- if (remaining <= 0) return false;
269
- wait_timeout = DBL2NUM(remaining);
270
- }
271
- return containers_wait_for_readable(fd, wait_timeout);
272
- }
273
-
274
237
  static VALUE
275
238
  vector_wait_body(VALUE opaque)
276
239
  {
277
240
  vector_wait_context_t *context = (vector_wait_context_t *)opaque;
278
- return vector_wait_for_descriptor(context->waiter.read_fd, context->timeout) ? Qtrue : Qfalse;
241
+ return containers_waiter_wait(
242
+ &context->waiter.notification, &context->vector->lock,
243
+ context->timeout->finite, context->timeout->deadline, NULL
244
+ ) ? Qtrue : Qfalse;
279
245
  }
280
246
 
281
247
  static VALUE
@@ -292,8 +258,7 @@ vector_wait_cleanup(VALUE opaque)
292
258
  }
293
259
  }
294
260
  pthread_mutex_unlock(&context->vector->lock);
295
- close(context->waiter.read_fd);
296
- close(context->waiter.write_fd);
261
+ containers_waiter_close(&context->waiter.notification);
297
262
  return Qnil;
298
263
  }
299
264
 
@@ -303,24 +268,12 @@ vector_wait_cleanup(VALUE opaque)
303
268
  static bool
304
269
  vector_wait_once(vector_t *vector, vector_timeout_t *timeout)
305
270
  {
306
- int descriptors[2];
307
- if (pipe(descriptors) != 0) {
308
- pthread_mutex_unlock(&vector->lock);
309
- rb_sys_fail("pipe");
310
- }
311
- vector_set_fd_flags(descriptors[0]);
312
- vector_set_fd_flags(descriptors[1]);
313
-
314
271
  vector_wait_context_t context = {
315
272
  .vector = vector,
316
- .waiter = {
317
- .read_fd = descriptors[0],
318
- .write_fd = descriptors[1],
319
- .notified = false,
320
- .next = vector->waiters,
321
- },
273
+ .waiter = {.next = vector->waiters},
322
274
  .timeout = timeout,
323
275
  };
276
+ containers_waiter_init(&context.waiter.notification);
324
277
  vector->waiters = &context.waiter;
325
278
  pthread_mutex_unlock(&vector->lock);
326
279
  return RTEST(rb_ensure(vector_wait_body, (VALUE)&context, vector_wait_cleanup, (VALUE)&context));
data/ext/farce/weak_map.c CHANGED
@@ -250,7 +250,7 @@ weak_map_memsize(const void *pointer)
250
250
  }
251
251
 
252
252
  static const rb_data_type_t weak_map_type = {
253
- .wrap_struct_name = "Ractor::Containers::WeakMap",
253
+ .wrap_struct_name = "Farce::Internal::WeakMap",
254
254
  .function = {
255
255
  .dmark = weak_map_mark,
256
256
  .dfree = weak_map_free,
@@ -59,6 +59,12 @@ module Farce
59
59
  include Internal::ValueSerialization
60
60
  include Internal::Inspect
61
61
 
62
+ # @return [Boolean] whether the flag is set
63
+ def set? = value
64
+
65
+ # @return [Boolean] whether the flag is unset
66
+ def unset? = !value
67
+
62
68
  # Wait until the flag becomes true.
63
69
  # @param timeout [Numeric, nil] the total seconds available
64
70
  # @yield called when the timeout expires