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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/futex.md +208 -0
- data/context/getting-started.md +6 -0
- data/context/index.yaml +4 -0
- data/ext/extconf.rb +7 -2
- data/ext/io/event/event.c +5 -0
- data/ext/io/event/futex.c +489 -0
- data/ext/io/event/futex.h +42 -0
- data/ext/io/event/selector/uring.c +211 -0
- data/ext/io/event/worker_pool.c +30 -20
- data/ext/io/event/worker_pool_test.c +6 -0
- data/lib/io/event/debug/selector.rb +12 -0
- data/lib/io/event/version.rb +1 -1
- data/license.md +2 -0
- data/readme.md +15 -11
- data/releases.md +9 -0
- data.tar.gz.sig +0 -0
- metadata +27 -29
- metadata.gz.sig +0 -0
- data/agent.md +0 -47
|
@@ -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
|
}
|
data/ext/io/event/worker_pool.c
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
|
|
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.
|
data/lib/io/event/version.rb
CHANGED
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
|
|
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.
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
|
|
54
|
-
eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
|
|
55
|
-
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
|
56
|
-
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
|
31
|
+
MIIEFDCCAnygAwIBAgIBATANBgkqhkiG9w0BAQsFADAwMREwDwYDVQQKDAhTb2Nr
|
|
32
|
+
ZXRyeTEbMBkGA1UEAwwSU29ja2V0cnkgUnVieSBHZW1zMB4XDTI2MDkyMTA3NTgx
|
|
33
|
+
NFoXDTI3MDkyMTA3NTgxNFowMDERMA8GA1UECgwIU29ja2V0cnkxGzAZBgNVBAMM
|
|
34
|
+
ElNvY2tldHJ5IFJ1YnkgR2VtczCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC
|
|
35
|
+
ggGBAM/QgjVgDzDo/xJEQoFvAzcVFP7msnswkhPJB2UDsbxXCZmers5jV512TM5s
|
|
36
|
+
NwLHfzJiC4DcI5ax9ZYKM9Q+dS21YYagNqjtg3YPyDqR6phoibEA0VoMuInUQW68
|
|
37
|
+
i5OkCJzYKGD2pYYVZnuFNqyM2ECUvXh/fBmvPoHbncAHhWPaCBH8mQJ8sRNc6+RV
|
|
38
|
+
SoZZu1Yo/aW1zQ+SpZwad7s1ZmigfDNHKhJ22KwZHk+/5Zw1QXVfcajjswV0Mm2P
|
|
39
|
+
irgjKN3fyNVkwlly63EBlR4VydT+g9QtJtqh7ee0ThhI+v4wYovf4dbLNcEBTHu1
|
|
40
|
+
x1pGbXGx/2fBAST5eVwaXIx1V8VU22AJcBk1H2o5/1F0HfbD2HQjmFOA112KUwBW
|
|
41
|
+
9TtspZD3g6rCQFX53XvL9h6j2y1ukl7s5AdEzOe/x6r1MO8tSDtgcYoF89wgF/88
|
|
42
|
+
Q/Eski0FckQ+znfESZcFcpzs2sPDoYW6SNVX4tZcNty85Y6WBHfHKio14x63PeyC
|
|
43
|
+
CG1gMwIDAQABozkwNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU
|
|
44
|
+
SuMc0x24Xshtaa8SQVkBr2Z5WzIwDQYJKoZIhvcNAQELBQADggGBABI62ey5lDWm
|
|
45
|
+
j8jsTQaNBMBb7LbKS3XkBwyL1UNGEwlicp399rJDeuNgVSHqBCZ/nE4yleNjgW9Y
|
|
46
|
+
N6DKdxXCvXmFUoHvUINeAvThHxmlEGhSfXl1x55xHEig0rEP4jgdjnYdQI4fqeOM
|
|
47
|
+
zIDN0F3ruArke4Y62SQgWVN7vspdAA41hBIRl1vsvs0KWG8DIVQ8cmR+TG6zjOmK
|
|
48
|
+
iUiUZGNPnStV1O1xW83c+Ba4Am0krP6/forxDPhRvTehqkVYvMdPfYcICNb5IM58
|
|
49
|
+
5vaYvuj5AtfZuZWN0F/0KtSphhNC17rh6h8QmuYdMJ1clvlrkiEaO1UjPVwjgtxC
|
|
50
|
+
x/ARh5X6q0/YTXl4r7EfSJEeqW7qhgu9EUj3mGIPHYdX0Dqih/NgK20F3GvQtTfB
|
|
51
|
+
7sse0duKBHXONYVwKAsceUVvqtCfyF608bCilCkbhw3QaInRj+BkRQKJ5Tuj2PZR
|
|
52
|
+
76a/hwaV2wWn5RtAFBaUQzQdo/xTqJ7IkwaFjOZe/QyvyoRjCK9Rdg==
|
|
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
|
-
-
|
|
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.
|
|
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.
|