io-event 1.22.1 → 1.22.3

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.
@@ -3,6 +3,7 @@
3
3
 
4
4
  #include "uring.h"
5
5
  #include "selector.h"
6
+ #include "../futex.h"
6
7
  #include "../list.h"
7
8
  #include "../array.h"
8
9
 
@@ -804,6 +805,190 @@ VALUE IO_Event_Selector_URing_process_wait(VALUE self, VALUE fiber, VALUE _pid,
804
805
  return rb_ensure(process_wait_transfer, (VALUE)&process_wait_arguments, process_wait_ensure, (VALUE)&process_wait_arguments);
805
806
  }
806
807
 
808
+ #if defined(IO_EVENT_FUTEX) && defined(HAVE_IO_URING_PREP_FUTEX_WAIT)
809
+
810
+ #pragma mark - Futex Wait
811
+
812
+ struct futex_wait_arguments {
813
+ struct IO_Event_Selector_URing *selector;
814
+ struct IO_Event_Selector_URing_Waiting waiting;
815
+ VALUE futex;
816
+ uint32_t expected;
817
+ bool acquired;
818
+ bool submitted;
819
+ };
820
+
821
+ static VALUE futex_wait_cancel(VALUE _arguments) {
822
+ struct futex_wait_arguments *arguments = (struct futex_wait_arguments *)_arguments;
823
+ if (arguments->submitted) {
824
+ // Drain the original operation before releasing its futex. The shared
825
+ // helper tracks cancellation CQEs to prevent premature completion reuse.
826
+ IO_Event_Selector_URing_Waiting_cancel_and_wait(arguments->selector, &arguments->waiting);
827
+ } else if (arguments->waiting.completion) {
828
+ // Setup failed before an SQE referred to this completion.
829
+ IO_Event_Selector_URing_Completion_complete(arguments->selector, arguments->waiting.completion);
830
+ }
831
+ return Qnil;
832
+ }
833
+
834
+ static VALUE futex_wait_release(VALUE _arguments) {
835
+ struct futex_wait_arguments *arguments = (struct futex_wait_arguments *)_arguments;
836
+ if (arguments->acquired) {
837
+ IO_Event_Futex_release(arguments->futex);
838
+ arguments->acquired = false;
839
+ }
840
+ return Qnil;
841
+ }
842
+
843
+ static VALUE futex_wait_ensure(VALUE _arguments) {
844
+ return rb_ensure(futex_wait_cancel, _arguments, futex_wait_release, _arguments);
845
+ }
846
+
847
+ static VALUE futex_wait_transfer(VALUE _arguments) {
848
+ struct futex_wait_arguments *arguments = (struct futex_wait_arguments *)_arguments;
849
+ uint32_t *address = IO_Event_Futex_acquire(arguments->futex);
850
+ arguments->acquired = true;
851
+
852
+ // Arguments have been coerced and the futex is now held open. From here
853
+ // on, the enclosing ensure owns all completion and cancellation cleanup.
854
+ struct IO_Event_Selector_URing_Completion *completion = IO_Event_Selector_URing_Completion_acquire(arguments->selector, &arguments->waiting);
855
+ struct io_uring_sqe *sqe = io_get_sqe(arguments->selector);
856
+ io_uring_prep_futex_wait(sqe, address, arguments->expected, FUTEX_BITSET_MATCH_ANY, FUTEX2_SIZE_U32, 0);
857
+ io_uring_sqe_set_data(sqe, completion);
858
+ arguments->submitted = true;
859
+ io_uring_submit_pending(arguments->selector);
860
+
861
+ IO_Event_Selector_loop_yield(&arguments->selector->backend);
862
+ if (arguments->waiting.completion) {
863
+ // An out-of-band resume is not a successful futex notification.
864
+ IO_Event_Selector_URing_Waiting_cancel_and_wait(arguments->selector, &arguments->waiting);
865
+ }
866
+
867
+ int32_t result = arguments->waiting.result;
868
+ if (result >= 0) {
869
+ return Qtrue;
870
+ } else if (result == -EAGAIN || result == -ECANCELED) {
871
+ return Qfalse;
872
+ } else {
873
+ rb_syserr_fail(-result, "futex_wait_transfer:io_uring_futex_wait");
874
+ }
875
+
876
+ return Qnil;
877
+ }
878
+
879
+ static VALUE IO_Event_Selector_URing_futex_wait(VALUE self, VALUE fiber, VALUE futex, VALUE expected_value) {
880
+ struct IO_Event_Selector_URing *selector = NULL;
881
+ TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
882
+ struct futex_wait_arguments arguments = {
883
+ .selector = selector,
884
+ .waiting = {.fiber = fiber},
885
+ .futex = futex,
886
+ .expected = NUM2UINT(expected_value),
887
+ };
888
+ RB_OBJ_WRITTEN(self, Qundef, fiber);
889
+ VALUE result = rb_ensure(futex_wait_transfer, (VALUE)&arguments, futex_wait_ensure, (VALUE)&arguments);
890
+ RB_GC_GUARD(futex);
891
+ return result;
892
+ }
893
+
894
+ #if defined(IO_EVENT_FUTEX_WAITV) && defined(HAVE_IO_URING_PREP_FUTEX_WAITV)
895
+
896
+ #pragma mark - Futex Vector Wait
897
+
898
+ struct futex_waitv_arguments {
899
+ struct IO_Event_Selector_URing *selector;
900
+ struct IO_Event_Selector_URing_Waiting waiting;
901
+ VALUE futexes;
902
+ struct futex_waitv *vector;
903
+ long count;
904
+ long acquired;
905
+ bool submitted;
906
+ };
907
+
908
+ static VALUE futex_waitv_cancel(VALUE _arguments) {
909
+ struct futex_waitv_arguments *arguments = (struct futex_waitv_arguments *)_arguments;
910
+ if (arguments->submitted) {
911
+ // Drain the original operation before releasing its futex references or
912
+ // stack-backed wait vector. The shared helper also tracks cancellation
913
+ // CQEs so the completion record cannot be reused prematurely.
914
+ IO_Event_Selector_URing_Waiting_cancel_and_wait(arguments->selector, &arguments->waiting);
915
+ } else if (arguments->waiting.completion) {
916
+ // Setup failed before an SQE referred to this completion.
917
+ IO_Event_Selector_URing_Completion_complete(arguments->selector, arguments->waiting.completion);
918
+ }
919
+ return Qnil;
920
+ }
921
+
922
+ static VALUE futex_waitv_release(VALUE _arguments) {
923
+ struct futex_waitv_arguments *arguments = (struct futex_waitv_arguments *)_arguments;
924
+ while (arguments->acquired) {
925
+ IO_Event_Futex_release(RARRAY_AREF(arguments->futexes, --arguments->acquired));
926
+ }
927
+ return Qnil;
928
+ }
929
+
930
+ static VALUE futex_waitv_ensure(VALUE _arguments) {
931
+ return rb_ensure(futex_waitv_cancel, _arguments, futex_waitv_release, _arguments);
932
+ }
933
+
934
+ static VALUE futex_waitv_transfer(VALUE _arguments) {
935
+ struct futex_waitv_arguments *arguments = (struct futex_waitv_arguments *)_arguments;
936
+ while (arguments->acquired < arguments->count) {
937
+ long index = arguments->acquired;
938
+ arguments->vector[index].uaddr = (uintptr_t)IO_Event_Futex_acquire(RARRAY_AREF(arguments->futexes, index));
939
+ arguments->acquired += 1;
940
+ }
941
+
942
+ // Arguments have been coerced and every futex is now held open. The
943
+ // enclosing ensure also releases entries if acquisition fails partway.
944
+ struct IO_Event_Selector_URing_Completion *completion = IO_Event_Selector_URing_Completion_acquire(arguments->selector, &arguments->waiting);
945
+ struct io_uring_sqe *sqe = io_get_sqe(arguments->selector);
946
+ io_uring_prep_futex_waitv(sqe, arguments->vector, arguments->count, 0);
947
+ io_uring_sqe_set_data(sqe, completion);
948
+ arguments->submitted = true;
949
+ io_uring_submit_pending(arguments->selector);
950
+
951
+ IO_Event_Selector_loop_yield(&arguments->selector->backend);
952
+ if (arguments->waiting.completion) {
953
+ // An out-of-band resume is not a successful futex notification.
954
+ IO_Event_Selector_URing_Waiting_cancel_and_wait(arguments->selector, &arguments->waiting);
955
+ }
956
+
957
+ int32_t result = arguments->waiting.result;
958
+ if (result >= 0) {
959
+ return INT2NUM(result);
960
+ } else if (result == -EAGAIN || result == -ECANCELED) {
961
+ return Qnil;
962
+ } else {
963
+ rb_syserr_fail(-result, "futex_waitv_transfer:io_uring_futex_waitv");
964
+ }
965
+
966
+ return Qnil;
967
+ }
968
+
969
+ static VALUE IO_Event_Selector_URing_futex_waitv(VALUE self, VALUE fiber, VALUE entries) {
970
+ struct IO_Event_Selector_URing *selector = NULL;
971
+ TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
972
+
973
+ struct futex_waitv vector[FUTEX_WAITV_MAX];
974
+ VALUE futexes = IO_Event_Futex_prepare_waitv(entries, vector);
975
+ struct futex_waitv_arguments arguments = {
976
+ .selector = selector,
977
+ .waiting = {.fiber = fiber},
978
+ .futexes = futexes,
979
+ .vector = vector,
980
+ .count = RARRAY_LEN(futexes),
981
+ };
982
+ RB_OBJ_WRITTEN(self, Qundef, fiber);
983
+ VALUE result = rb_ensure(futex_waitv_transfer, (VALUE)&arguments, futex_waitv_ensure, (VALUE)&arguments);
984
+ RB_GC_GUARD(futexes);
985
+ return result;
986
+ }
987
+
988
+ #endif
989
+
990
+ #endif
991
+
807
992
  #pragma mark - IO#wait
808
993
 
809
994
  static inline
@@ -1724,6 +1909,9 @@ VALUE IO_Event_Selector_URing_wakeup(VALUE self) {
1724
1909
 
1725
1910
  #pragma mark - Native Methods
1726
1911
 
1912
+ static int IO_Event_Selector_URing_futex_supported = 0;
1913
+ static int IO_Event_Selector_URing_futex_waitv_supported = 0;
1914
+
1727
1915
  static int IO_Event_Selector_URing_supported_p(void) {
1728
1916
  struct io_uring ring;
1729
1917
 
@@ -1754,6 +1942,17 @@ static int IO_Event_Selector_URing_supported_p(void) {
1754
1942
 
1755
1943
  return 0;
1756
1944
  }
1945
+
1946
+ #if defined(IO_EVENT_FUTEX) && defined(HAVE_IO_URING_PREP_FUTEX_WAIT)
1947
+ struct io_uring_probe *probe = io_uring_get_probe_ring(&ring);
1948
+ if (probe) {
1949
+ IO_Event_Selector_URing_futex_supported = io_uring_opcode_supported(probe, IORING_OP_FUTEX_WAIT);
1950
+ #if defined(IO_EVENT_FUTEX_WAITV) && defined(HAVE_IO_URING_PREP_FUTEX_WAITV)
1951
+ IO_Event_Selector_URing_futex_waitv_supported = io_uring_opcode_supported(probe, IORING_OP_FUTEX_WAITV);
1952
+ #endif
1953
+ io_uring_free_probe(probe);
1954
+ }
1955
+ #endif
1757
1956
 
1758
1957
  io_uring_queue_exit(&ring);
1759
1958
 
@@ -1803,4 +2002,16 @@ void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) {
1803
2002
  rb_define_method(IO_Event_Selector_URing, "io_close", IO_Event_Selector_URing_io_close, 1);
1804
2003
 
1805
2004
  rb_define_method(IO_Event_Selector_URing, "process_wait", IO_Event_Selector_URing_process_wait, 3);
2005
+
2006
+ #if defined(IO_EVENT_FUTEX) && defined(HAVE_IO_URING_PREP_FUTEX_WAIT)
2007
+ if (IO_Event_Selector_URing_futex_supported) {
2008
+ rb_define_method(IO_Event_Selector_URing, "futex_wait", IO_Event_Selector_URing_futex_wait, 3);
2009
+
2010
+ #if defined(IO_EVENT_FUTEX_WAITV) && defined(HAVE_IO_URING_PREP_FUTEX_WAITV)
2011
+ if (IO_Event_Selector_URing_futex_waitv_supported) {
2012
+ rb_define_method(IO_Event_Selector_URing, "futex_waitv", IO_Event_Selector_URing_futex_waitv, 2);
2013
+ }
2014
+ #endif
2015
+ }
2016
+ #endif
1806
2017
  }
@@ -1,5 +1,5 @@
1
1
  // Released under the MIT License.
2
- // Copyright, 2025, by Samuel Williams.
2
+ // Copyright, 2025-2026, by Samuel Williams.
3
3
 
4
4
  #include "worker_pool.h"
5
5
  #include "worker_pool_test.h"
@@ -19,7 +19,9 @@ enum {
19
19
  };
20
20
 
21
21
  static VALUE IO_Event_WorkerPool;
22
+ static VALUE rb_Fiber;
22
23
  static ID id_maximum_worker_count;
24
+ static ID id_blocking;
23
25
 
24
26
  // Thread pool structure
25
27
  struct IO_Event_WorkerPool_Worker {
@@ -318,17 +320,11 @@ static VALUE worker_pool_work_block(VALUE _work) {
318
320
  static VALUE worker_pool_work_wait(VALUE _work) {
319
321
  struct IO_Event_WorkerPool_Work *work = (void*)_work;
320
322
 
321
- while (true) {
323
+ // A stale unblock is not cancellation. Only a non-local exit cancels work.
324
+ do {
322
325
  worker_pool_work_block(_work);
323
326
  if (DEBUG) fprintf(stderr, "-- worker_pool_work_wait:work completed=%d\n", work->completed);
324
-
325
- if (work->completed) {
326
- break;
327
- }
328
-
329
- if (DEBUG) fprintf(stderr, "worker_pool_work_wait:rb_fiber_scheduler_blocking_operation_cancel\n");
330
- rb_fiber_scheduler_blocking_operation_cancel(work->blocking_operation);
331
- }
327
+ } while (!work->completed);
332
328
 
333
329
  return Qtrue;
334
330
  }
@@ -354,6 +350,21 @@ static VALUE worker_pool_work_ensure(VALUE _work) {
354
350
  return Qnil;
355
351
  }
356
352
 
353
+ static VALUE worker_pool_work_submit(RB_BLOCK_CALL_FUNC_ARGLIST(fiber, _work)) {
354
+ struct IO_Event_WorkerPool_Work *work = (void*)_work;
355
+ struct IO_Event_WorkerPool *pool;
356
+ TypedData_Get_Struct(work->blocker, struct IO_Event_WorkerPool, &IO_Event_WorkerPool_type, pool);
357
+
358
+ pthread_mutex_lock(&pool->mutex);
359
+ enqueue_work(pool, work);
360
+ pthread_cond_signal(&pool->work_available);
361
+ pthread_mutex_unlock(&pool->mutex);
362
+
363
+ // Keep cancellation and draining inside the blocking scope too. rb_ensure
364
+ // preserves the original exception or other non-local exit.
365
+ return rb_ensure(worker_pool_work_wait, _work, worker_pool_work_ensure, _work);
366
+ }
367
+
357
368
  // Ruby method to submit work and wait for completion
358
369
  static VALUE worker_pool_call(VALUE self, VALUE _blocking_operation) {
359
370
  struct IO_Event_WorkerPool *pool;
@@ -389,17 +400,13 @@ static VALUE worker_pool_call(VALUE self, VALUE _blocking_operation) {
389
400
  .fiber = fiber,
390
401
  .next = NULL
391
402
  };
392
-
393
- // Enqueue work:
394
- pthread_mutex_lock(&pool->mutex);
395
- enqueue_work(pool, &work);
396
- pthread_cond_signal(&pool->work_available);
397
- pthread_mutex_unlock(&pool->mutex);
398
403
 
399
- // Block the current fiber until work is completed. If blocking exits via an
400
- // exception or another non-local jump, ensure the work is cancelled and fully
401
- // drained before Ruby restores and rethrows the original control-flow state.
402
- return rb_ensure(worker_pool_work_wait, (VALUE)&work, worker_pool_work_ensure, (VALUE)&work);
404
+ // A finalizer inside the scheduler's block hook must not offload another
405
+ // operation on this fiber and consume this work's wakeup. Fiber.blocking
406
+ // makes nested operations synchronous, while explicit fiber transfers still
407
+ // work and other fibers retain their own blocking state. Capture the scheduler
408
+ // above, before Fiber.blocking hides it from rb_fiber_scheduler_current().
409
+ return rb_block_call(rb_Fiber, id_blocking, 0, NULL, worker_pool_work_submit, (VALUE)&work);
403
410
  }
404
411
 
405
412
  static VALUE worker_pool_allocate(VALUE klass) {
@@ -495,6 +502,9 @@ static VALUE worker_pool_statistics(VALUE self) {
495
502
  void Init_IO_Event_WorkerPool(VALUE IO_Event) {
496
503
  // Initialize symbols
497
504
  id_maximum_worker_count = rb_intern("maximum_worker_count");
505
+ id_blocking = rb_intern("blocking");
506
+ rb_Fiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
507
+ rb_global_variable(&rb_Fiber);
498
508
 
499
509
  IO_Event_WorkerPool = rb_define_class_under(IO_Event, "WorkerPool", rb_cObject);
500
510
  rb_define_alloc_func(IO_Event_WorkerPool, worker_pool_allocate);
@@ -18,6 +18,7 @@ struct BusyOperationData {
18
18
  int read_fd;
19
19
  int write_fd;
20
20
  volatile int cancelled;
21
+ volatile int executed;
21
22
  double duration; // How long to wait (for testing)
22
23
  clock_t start_time;
23
24
  clock_t end_time;
@@ -29,6 +30,9 @@ struct BusyOperationData {
29
30
  static void* busy_blocking_operation(void *data) {
30
31
  struct BusyOperationData *busy_data = (struct BusyOperationData*)data;
31
32
 
33
+ // Record that the operation actually ran (a cancelled operation may be skipped entirely):
34
+ busy_data->executed = 1;
35
+
32
36
  // Use select() to wait for the pipe to become readable
33
37
  fd_set read_fds;
34
38
  struct timeval timeout;
@@ -143,6 +147,7 @@ static VALUE worker_pool_test_busy(int argc, VALUE *argv, VALUE self) {
143
147
  .read_fd = pipe_fds[0],
144
148
  .write_fd = pipe_fds[1],
145
149
  .cancelled = 0,
150
+ .executed = 0,
146
151
  .duration = duration,
147
152
  .start_time = 0,
148
153
  .end_time = 0,
@@ -165,6 +170,7 @@ static VALUE worker_pool_test_busy(int argc, VALUE *argv, VALUE self) {
165
170
  VALUE result = rb_hash_new();
166
171
  rb_hash_aset(result, ID2SYM(rb_intern("duration")), DBL2NUM(duration));
167
172
  rb_hash_aset(result, ID2SYM(rb_intern("elapsed")), DBL2NUM(elapsed));
173
+ rb_hash_aset(result, ID2SYM(rb_intern("executed")), busy_data.executed ? Qtrue : Qfalse);
168
174
 
169
175
  // Determine result based on operation outcome
170
176
  if (busy_data.exception != Qnil) {
@@ -19,6 +19,18 @@ module IO::Event
19
19
  log("Closing file descriptor #{descriptor}")
20
20
  @selector.io_close(descriptor)
21
21
  end
22
+
23
+ # Wait for a futex value to change, forwarded to the underlying selector.
24
+ def futex_wait(fiber, futex, expected)
25
+ log("Waiting for futex #{futex.inspect} with value #{expected}")
26
+ @selector.futex_wait(fiber, futex, expected)
27
+ end
28
+
29
+ # Wait for any futex value to change, forwarded to the underlying selector.
30
+ def futex_waitv(fiber, entries)
31
+ log("Waiting for futex vector #{entries.inspect}")
32
+ @selector.futex_waitv(fiber, entries)
33
+ end
22
34
  end
23
35
 
24
36
  # Wrap the given selector with debugging.
@@ -7,6 +7,6 @@
7
7
  class IO
8
8
  # @namespace
9
9
  module Event
10
- VERSION = "1.22.1"
10
+ VERSION = "1.22.3"
11
11
  end
12
12
  end
data/license.md CHANGED
@@ -20,6 +20,8 @@ Copyright, 2026, by Italo Brandão.
20
20
  Copyright, 2026, by Fletcher Dares.
21
21
  Copyright, 2026, by Tavian Barnes.
22
22
  Copyright, 2026, by Sharon Rosner.
23
+ Copyright, 2026, by Yuki Kikuchi.
24
+ Copyright, 2026, by Randy Stauner.
23
25
 
24
26
  Permission is hereby granted, free of charge, to any person obtaining a copy
25
27
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -14,10 +14,21 @@ Please see the [project documentation](https://socketry.github.io/io-event/) for
14
14
 
15
15
  - [Getting Started](https://socketry.github.io/io-event/guides/getting-started/index) - This guide explains how to use `io-event` for non-blocking IO.
16
16
 
17
+ - [Futex Notifications](https://socketry.github.io/io-event/guides/futex/index) - This guide explains how to use `IO::Event::Futex` to notify threads or processes when shared state changes, without continuously polling it.
18
+
17
19
  ## Releases
18
20
 
19
21
  Please see the [project releases](https://socketry.github.io/io-event/releases/index) for all releases.
20
22
 
23
+ ### v1.22.3
24
+
25
+ - Sign releases with the shared Socketry certificate.
26
+
27
+ ### v1.22.2
28
+
29
+ - Prevent nested worker-pool waits by running `WorkerPool#call` in a `Fiber.blocking` scope. Blocking operations triggered inside scheduler callbacks, including finalizers, execute synchronously. Cancellation still drains submitted work, and spurious wakeups no longer cancel operations.
30
+ - Add `IO::Event::Futex` on Linux with Ruby 4.1+, including atomic value operations and blocking and scheduler-aware single and vector waits over shared memory. Futexes retain a counted allocation lock until explicitly closed or finalized; pending waits prevent closing until completion or cancellation has finished.
31
+
21
32
  ### v1.22.1
22
33
 
23
34
  - Fix an infinite loop in the ready queue flush when a queued fiber is resumed out of band, e.g. by a stale `unblock` racing a timeout, while another fiber re-queues itself on every iteration.
@@ -52,15 +63,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
52
63
 
53
64
  - Use `rb_process_status_for` when available to construct `URing` `process_wait` results directly from `waitid`, avoiding the extra reap syscall previously needed to build a `Process::Status`.
54
65
 
55
- ### v1.19.1
56
-
57
- - Fix `Process.waitall` / `Process.detach` under the `URing` selector: when `io_uring`'s `waitid` reported an error (e.g. `ECHILD` when there are no more children), the `process_wait` hook raised instead of returning the error as a `Process::Status`, so callers that expect `waitpid` to *report* "no more children" rather than raise would fail.
58
-
59
- ### v1.19.0
60
-
61
- - Use `io_uring_prep_waitid` for `process_wait` in the `URing` selector (Linux 6.7+), waiting for child exit directly in the ring instead of polling on a `pidfd`. The child is reaped via `rb_process_status_wait` (using `WEXITED | WNOWAIT`) to construct a correct `Process::Status`, and `process_wait(-1, ...)` / `process_wait(0, ...)` are now supported.
62
- - Support waiting for any child or a process group (`pid <= 0`) on all selectors. The `EPoll` (`pidfd_open`) and `KQueue` (`EVFILT_PROC`) selectors can only watch a specific process, so these cases now fall back to a blocking wait on a dedicated thread; joining it is fiber-scheduler aware, so the reactor keeps running.
63
-
64
66
  ## Contributing
65
67
 
66
68
  We welcome contributions to this project.
@@ -82,12 +84,14 @@ bundle exec sus
82
84
 
83
85
  ### Making Releases
84
86
 
85
- To make a new release:
87
+ To prepare a release pull request:
86
88
 
87
89
  ``` shell
88
- bundle exec bake gem:release:patch # or minor or major
90
+ bundle exec bake gem:github:release:patch # or minor or major
89
91
  ```
90
92
 
93
+ After review and merge, GitHub Actions publishes the release. See [bake-gem-github](https://github.com/socketry/bake-gem-github) for setup and release instructions.
94
+
91
95
  ### Developer Certificate of Origin
92
96
 
93
97
  In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
data/releases.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Releases
2
2
 
3
+ ## v1.22.3
4
+
5
+ - Sign releases with the shared Socketry certificate.
6
+
7
+ ## v1.22.2
8
+
9
+ - Prevent nested worker-pool waits by running `WorkerPool#call` in a `Fiber.blocking` scope. Blocking operations triggered inside scheduler callbacks, including finalizers, execute synchronously. Cancellation still drains submitted work, and spurious wakeups no longer cancel operations.
10
+ - Add `IO::Event::Futex` on Linux with Ruby 4.1+, including atomic value operations and blocking and scheduler-aware single and vector waits over shared memory. Futexes retain a counted allocation lock until explicitly closed or finalized; pending waits prevent closing until completion or cancellation has finished.
11
+
3
12
  ## v1.22.1
4
13
 
5
14
  - Fix an infinite loop in the ready queue flush when a queued fiber is resumed out of band, e.g. by a stale `unblock` racing a timeout, while another fiber re-queues itself on every iteration.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-event
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.22.1
4
+ version: 1.22.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -28,32 +28,28 @@ bindir: bin
28
28
  cert_chain:
29
29
  - |
30
30
  -----BEGIN CERTIFICATE-----
31
- MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
32
- ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
33
- CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
34
- MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
35
- MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
36
- bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
37
- igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
38
- 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
39
- sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
40
- e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
41
- XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
42
- RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
43
- tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
44
- zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
45
- xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
46
- BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
47
- aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
48
- aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
49
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
57
53
  -----END CERTIFICATE-----
58
54
  date: 1980-01-02 00:00:00.000000000 Z
59
55
  dependencies: []
@@ -62,7 +58,7 @@ extensions:
62
58
  - ext/extconf.rb
63
59
  extra_rdoc_files: []
64
60
  files:
65
- - agent.md
61
+ - context/futex.md
66
62
  - context/getting-started.md
67
63
  - context/index.yaml
68
64
  - design.md
@@ -72,6 +68,8 @@ files:
72
68
  - ext/io/event/event.h
73
69
  - ext/io/event/fiber.c
74
70
  - ext/io/event/fiber.h
71
+ - ext/io/event/futex.c
72
+ - ext/io/event/futex.h
75
73
  - ext/io/event/interrupt.c
76
74
  - ext/io/event/interrupt.h
77
75
  - ext/io/event/list.h
@@ -124,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
122
  - !ruby/object:Gem::Version
125
123
  version: '0'
126
124
  requirements: []
127
- rubygems_version: 4.0.16
125
+ rubygems_version: 4.0.21
128
126
  specification_version: 4
129
127
  summary: An event loop.
130
128
  test_files: []
metadata.gz.sig CHANGED
Binary file
data/agent.md DELETED
@@ -1,47 +0,0 @@
1
- # Agent
2
-
3
- ## Context
4
-
5
- This section provides links to documentation from installed packages. It is automatically generated and may be updated by running `bake agent:context:install`.
6
-
7
- **Important:** Before performing any code, documentation, or analysis tasks, always read and apply the full content of any relevant documentation referenced in the following sections. These context files contain authoritative standards and best practices for documentation, code style, and project-specific workflows. **Do not proceed with any actions until you have read and incorporated the guidance from relevant context files.**
8
-
9
- ### agent-context
10
-
11
- Install and manage context files from Ruby gems.
12
-
13
- #### [Usage Guide](.context/agent-context/usage.md)
14
-
15
- `agent-context` is a tool that helps you discover and install contextual information from Ruby gems for AI agents. Gems can provide additional documentation, examples, and guidance in a `context/` ...
16
-
17
- ### decode
18
-
19
- Code analysis for documentation generation.
20
-
21
- #### [Getting Started with Decode](.context/decode/getting-started.md)
22
-
23
- The Decode gem provides programmatic access to Ruby code structure and metadata. It can parse Ruby files and extract definitions, comments, and documentation pragmas, enabling code analysis, docume...
24
-
25
- #### [Documentation Coverage](.context/decode/coverage.md)
26
-
27
- This guide explains how to test and monitor documentation coverage in your Ruby projects using the Decode gem's built-in bake tasks.
28
-
29
- #### [Ruby Documentation](.context/decode/ruby-documentation.md)
30
-
31
- This guide covers documentation practices and pragmas supported by the Decode gem for documenting Ruby code. These pragmas provide structured documentation that can be parsed and used to generate A...
32
-
33
- ### sus
34
-
35
- A fast and scalable test runner.
36
-
37
- #### [Using Sus Testing Framework](.context/sus/usage.md)
38
-
39
- Sus is a modern Ruby testing framework that provides a clean, BDD-style syntax for writing tests. It's designed to be fast, simple, and expressive.
40
-
41
- #### [Mocking](.context/sus/mocking.md)
42
-
43
- There are two types of mocking in sus: `receive` and `mock`. The `receive` matcher is a subset of full mocking and is used to set expectations on method calls, while `mock` can be used to replace m...
44
-
45
- #### [Shared Test Behaviors and Fixtures](.context/sus/shared.md)
46
-
47
- Sus provides shared test contexts which can be used to define common behaviours or tests that can be reused across one or more test files.