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.
@@ -0,0 +1,489 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2026, by Samuel Williams.
3
+
4
+ #include "futex.h"
5
+
6
+ #ifdef IO_EVENT_FUTEX
7
+
8
+ #include <errno.h>
9
+ #include <stdbool.h>
10
+ #include <sys/syscall.h>
11
+ #include <time.h>
12
+ #include <unistd.h>
13
+
14
+ #include <ruby/fiber/scheduler.h>
15
+ #include <ruby/io/buffer.h>
16
+ #include <ruby/thread.h>
17
+
18
+ // The finalizer owns the allocation lock independently of the Futex. It must
19
+ // never retain the Futex or its C struct: either may be reclaimed before the
20
+ // finalizer runs. Keeping the buffer here preserves it until it is unlocked.
21
+ struct IO_Event_Futex_Finalizer {
22
+ VALUE buffer;
23
+ uint32_t *address;
24
+ size_t waits;
25
+ };
26
+
27
+ static VALUE IO_Event_Futex_Finalizer_Class;
28
+
29
+ static void IO_Event_Futex_Finalizer_mark(void *_finalizer) {
30
+ struct IO_Event_Futex_Finalizer *finalizer = _finalizer;
31
+ rb_gc_mark_movable(finalizer->buffer);
32
+ }
33
+
34
+ static void IO_Event_Futex_Finalizer_compact(void *_finalizer) {
35
+ struct IO_Event_Futex_Finalizer *finalizer = _finalizer;
36
+ finalizer->buffer = rb_gc_location(finalizer->buffer);
37
+ }
38
+
39
+ static size_t IO_Event_Futex_Finalizer_size(const void *_finalizer) {
40
+ return sizeof(struct IO_Event_Futex_Finalizer);
41
+ }
42
+
43
+ static const rb_data_type_t IO_Event_Futex_Finalizer_Type = {
44
+ .wrap_struct_name = "IO::Event::Futex::Finalizer",
45
+ .function = {
46
+ .dmark = IO_Event_Futex_Finalizer_mark,
47
+ .dcompact = IO_Event_Futex_Finalizer_compact,
48
+ .dfree = RUBY_TYPED_DEFAULT_FREE,
49
+ .dsize = IO_Event_Futex_Finalizer_size,
50
+ },
51
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
52
+ };
53
+
54
+ static VALUE IO_Event_Futex_Finalizer_release(VALUE self) {
55
+ struct IO_Event_Futex_Finalizer *finalizer = NULL;
56
+ TypedData_Get_Struct(self, struct IO_Event_Futex_Finalizer, &IO_Event_Futex_Finalizer_Type, finalizer);
57
+ if (!NIL_P(finalizer->buffer)) {
58
+ rb_io_buffer_unlock(finalizer->buffer);
59
+ RB_OBJ_WRITE(self, &finalizer->buffer, Qnil);
60
+ finalizer->address = NULL;
61
+ }
62
+ return Qnil;
63
+ }
64
+
65
+ static VALUE IO_Event_Futex_Finalizer_call(VALUE self, VALUE object_id) {
66
+ struct IO_Event_Futex_Finalizer *finalizer = NULL;
67
+ TypedData_Get_Struct(self, struct IO_Event_Futex_Finalizer, &IO_Event_Futex_Finalizer_Type, finalizer);
68
+ // Ruby also invokes finalizers at shutdown, even for reachable objects.
69
+ // Never unlock memory still used by an outstanding kernel operation.
70
+ if (finalizer->waits) return Qnil;
71
+ return IO_Event_Futex_Finalizer_release(self);
72
+ }
73
+
74
+ struct IO_Event_Futex {
75
+ VALUE finalizer;
76
+ };
77
+
78
+ static void IO_Event_Futex_mark(void *_futex) {
79
+ struct IO_Event_Futex *futex = _futex;
80
+ rb_gc_mark_movable(futex->finalizer);
81
+ }
82
+
83
+ static void IO_Event_Futex_compact(void *_futex) {
84
+ struct IO_Event_Futex *futex = _futex;
85
+ futex->finalizer = rb_gc_location(futex->finalizer);
86
+ }
87
+
88
+ static size_t IO_Event_Futex_size(const void *_futex) {
89
+ return sizeof(struct IO_Event_Futex);
90
+ }
91
+
92
+ static const rb_data_type_t IO_Event_Futex_Type = {
93
+ .wrap_struct_name = "IO::Event::Futex",
94
+ .function = {
95
+ .dmark = IO_Event_Futex_mark,
96
+ .dcompact = IO_Event_Futex_compact,
97
+ .dfree = RUBY_TYPED_DEFAULT_FREE,
98
+ .dsize = IO_Event_Futex_size,
99
+ },
100
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
101
+ };
102
+
103
+ static VALUE IO_Event_Futex_allocate(VALUE klass) {
104
+ struct IO_Event_Futex *futex = NULL;
105
+ VALUE instance = TypedData_Make_Struct(klass, struct IO_Event_Futex, &IO_Event_Futex_Type, futex);
106
+ futex->finalizer = Qnil;
107
+ return instance;
108
+ }
109
+
110
+ static ID id_offset;
111
+ static ID id_futex_wait;
112
+ static ID id_futex_waitv;
113
+
114
+ static VALUE IO_Event_Futex_initialize(int argc, VALUE *argv, VALUE self) {
115
+ rb_check_frozen(self);
116
+ struct IO_Event_Futex *futex = NULL;
117
+ TypedData_Get_Struct(self, struct IO_Event_Futex, &IO_Event_Futex_Type, futex);
118
+ if (!NIL_P(futex->finalizer)) rb_raise(rb_eRuntimeError, "Futex is already initialized!");
119
+
120
+ VALUE buffer, options;
121
+ rb_scan_args(argc, argv, "1:", &buffer, &options);
122
+
123
+ VALUE offset_value = Qundef;
124
+ if (!NIL_P(options)) {
125
+ ID keys[] = {id_offset};
126
+ VALUE values[1];
127
+ rb_get_kwargs(options, keys, 0, 1, values);
128
+ offset_value = values[0];
129
+ }
130
+
131
+ size_t offset = offset_value == Qundef ? 0 : NUM2SIZET(offset_value);
132
+ // Numeric coercion may have invoked initialize recursively.
133
+ if (!NIL_P(futex->finalizer)) rb_raise(rb_eRuntimeError, "Futex is already initialized!");
134
+
135
+ // Register cleanup before acquiring the lock. All allocating operations are
136
+ // performed before extracting the pointer; failures leave an inert finalizer.
137
+ struct IO_Event_Futex_Finalizer *finalizer = NULL;
138
+ VALUE finalizer_value = TypedData_Make_Struct(IO_Event_Futex_Finalizer_Class, struct IO_Event_Futex_Finalizer, &IO_Event_Futex_Finalizer_Type, finalizer);
139
+ finalizer->buffer = Qnil;
140
+ finalizer->address = NULL;
141
+ finalizer->waits = 0;
142
+ rb_define_finalizer(self, finalizer_value);
143
+
144
+ void *base = NULL;
145
+ size_t size = 0;
146
+ rb_io_buffer_get_bytes_for_writing(buffer, &base, &size);
147
+
148
+ if (offset > size || size - offset < sizeof(uint32_t)) {
149
+ rb_raise(rb_eRangeError, "Futex offset exceeds the buffer size!");
150
+ }
151
+
152
+ uint32_t *address = (uint32_t *)((char *)base + offset);
153
+ if ((uintptr_t)address % sizeof(uint32_t) != 0) {
154
+ rb_raise(rb_eArgError, "Futex address must be aligned to 4 bytes!");
155
+ }
156
+
157
+ // Nothing after a successful lock can raise or invoke Ruby code.
158
+ rb_io_buffer_lock(buffer);
159
+ RB_OBJ_WRITE(finalizer_value, &finalizer->buffer, buffer);
160
+ finalizer->address = address;
161
+ RB_OBJ_WRITE(self, &futex->finalizer, finalizer_value);
162
+
163
+ return self;
164
+ }
165
+
166
+ static VALUE IO_Event_Futex_value(VALUE self) {
167
+ return UINT2NUM(__atomic_load_n(IO_Event_Futex_address(self), __ATOMIC_ACQUIRE));
168
+ }
169
+
170
+ static VALUE IO_Event_Futex_set_value(VALUE self, VALUE value) {
171
+ uint32_t converted = NUM2UINT(value);
172
+ __atomic_store_n(IO_Event_Futex_address(self), converted, __ATOMIC_RELEASE);
173
+ return value;
174
+ }
175
+
176
+ static VALUE IO_Event_Futex_increment(int argc, VALUE *argv, VALUE self) {
177
+ VALUE amount_value;
178
+ rb_scan_args(argc, argv, "01", &amount_value);
179
+ uint32_t amount = NIL_P(amount_value) ? 1 : NUM2UINT(amount_value);
180
+
181
+ uint32_t value = __atomic_add_fetch(IO_Event_Futex_address(self), amount, __ATOMIC_ACQ_REL);
182
+ return UINT2NUM(value);
183
+ }
184
+
185
+ static VALUE IO_Event_Futex_decrement(int argc, VALUE *argv, VALUE self) {
186
+ VALUE amount_value;
187
+ rb_scan_args(argc, argv, "01", &amount_value);
188
+ uint32_t amount = NIL_P(amount_value) ? 1 : NUM2UINT(amount_value);
189
+
190
+ uint32_t value = __atomic_sub_fetch(IO_Event_Futex_address(self), amount, __ATOMIC_ACQ_REL);
191
+ return UINT2NUM(value);
192
+ }
193
+
194
+ static VALUE IO_Event_Futex_compare_exchange(VALUE self, VALUE expected_value, VALUE desired_value) {
195
+ uint32_t expected = NUM2UINT(expected_value);
196
+ uint32_t desired = NUM2UINT(desired_value);
197
+
198
+ bool exchanged = __atomic_compare_exchange_n(
199
+ IO_Event_Futex_address(self),
200
+ &expected,
201
+ desired,
202
+ false,
203
+ __ATOMIC_ACQ_REL,
204
+ __ATOMIC_ACQUIRE
205
+ );
206
+
207
+ return exchanged ? Qtrue : Qfalse;
208
+ }
209
+
210
+ static VALUE IO_Event_Futex_wake(int argc, VALUE *argv, VALUE self) {
211
+ VALUE count_value;
212
+ rb_scan_args(argc, argv, "01", &count_value);
213
+ int count = NIL_P(count_value) ? 1 : NUM2INT(count_value);
214
+ if (count < 0) rb_raise(rb_eArgError, "Wake count must be non-negative!");
215
+
216
+ uint32_t *address = IO_Event_Futex_address(self);
217
+ // Legacy FUTEX_WAKE can wake one waiter even when count is zero.
218
+ if (count == 0) return INT2NUM(0);
219
+
220
+ int result = syscall(SYS_futex, address, FUTEX_WAKE, count, NULL, NULL, 0);
221
+ if (result < 0) rb_sys_fail("IO_Event_Futex_wake:futex");
222
+ return INT2NUM(result);
223
+ }
224
+
225
+ static VALUE IO_Event_Futex_signal(int argc, VALUE *argv, VALUE self) {
226
+ VALUE count_value;
227
+ rb_scan_args(argc, argv, "01", &count_value);
228
+
229
+ int count = NIL_P(count_value) ? 1 : NUM2INT(count_value);
230
+ if (count < 0) rb_raise(rb_eArgError, "Wake count must be non-negative!");
231
+ uint32_t *address = IO_Event_Futex_address(self);
232
+ // A zero-count signal neither changes the word nor wakes any waiters.
233
+ if (count == 0) return UINT2NUM(__atomic_load_n(address, __ATOMIC_ACQUIRE));
234
+
235
+ uint32_t value = __atomic_add_fetch(address, 1, __ATOMIC_ACQ_REL);
236
+ int result = syscall(SYS_futex, address, FUTEX_WAKE, count, NULL, NULL, 0);
237
+ if (result < 0) rb_sys_fail("IO_Event_Futex_signal:futex");
238
+ return UINT2NUM(value);
239
+ }
240
+
241
+ static struct IO_Event_Futex_Finalizer *IO_Event_Futex_get(VALUE self) {
242
+ struct IO_Event_Futex *futex = NULL;
243
+ TypedData_Get_Struct(self, struct IO_Event_Futex, &IO_Event_Futex_Type, futex);
244
+ if (NIL_P(futex->finalizer)) rb_raise(rb_eIOError, "Futex is uninitialized!");
245
+ struct IO_Event_Futex_Finalizer *finalizer = NULL;
246
+ TypedData_Get_Struct(futex->finalizer, struct IO_Event_Futex_Finalizer, &IO_Event_Futex_Finalizer_Type, finalizer);
247
+ if (!finalizer->address) rb_raise(rb_eIOError, "Futex is closed!");
248
+ return finalizer;
249
+ }
250
+
251
+ uint32_t *IO_Event_Futex_address(VALUE self) {
252
+ return IO_Event_Futex_get(self)->address;
253
+ }
254
+
255
+ uint32_t *IO_Event_Futex_acquire(VALUE self) {
256
+ struct IO_Event_Futex_Finalizer *finalizer = IO_Event_Futex_get(self);
257
+ if (finalizer->waits == SIZE_MAX) rb_raise(rb_eRuntimeError, "Too many futex waits!");
258
+ finalizer->waits += 1;
259
+ return finalizer->address;
260
+ }
261
+
262
+ void IO_Event_Futex_release(VALUE self) {
263
+ struct IO_Event_Futex_Finalizer *finalizer = IO_Event_Futex_get(self);
264
+ if (!finalizer->waits) rb_bug("IO_Event_Futex_release: no pending waits");
265
+ finalizer->waits -= 1;
266
+ }
267
+
268
+ static VALUE IO_Event_Futex_close(VALUE self) {
269
+ struct IO_Event_Futex *futex = NULL;
270
+ TypedData_Get_Struct(self, struct IO_Event_Futex, &IO_Event_Futex_Type, futex);
271
+ if (NIL_P(futex->finalizer)) return Qnil;
272
+ struct IO_Event_Futex_Finalizer *finalizer = NULL;
273
+ TypedData_Get_Struct(futex->finalizer, struct IO_Event_Futex_Finalizer, &IO_Event_Futex_Finalizer_Type, finalizer);
274
+ if (finalizer->waits) rb_raise(rb_eIOError, "Cannot close a futex with pending waits!");
275
+ return IO_Event_Futex_Finalizer_release(futex->finalizer);
276
+ }
277
+
278
+ static VALUE IO_Event_Futex_closed_p(VALUE self) {
279
+ struct IO_Event_Futex *futex = NULL;
280
+ TypedData_Get_Struct(self, struct IO_Event_Futex, &IO_Event_Futex_Type, futex);
281
+ if (NIL_P(futex->finalizer)) return Qtrue;
282
+ struct IO_Event_Futex_Finalizer *finalizer = NULL;
283
+ TypedData_Get_Struct(futex->finalizer, struct IO_Event_Futex_Finalizer, &IO_Event_Futex_Finalizer_Type, finalizer);
284
+ return finalizer->address ? Qfalse : Qtrue;
285
+ }
286
+
287
+ static VALUE IO_Event_Futex_initialize_copy(VALUE self, VALUE other) {
288
+ struct IO_Event_Futex *futex = NULL;
289
+ TypedData_Get_Struct(self, struct IO_Event_Futex, &IO_Event_Futex_Type, futex);
290
+ // Ruby copies finalizers before invoking initialize_copy. The rejected copy
291
+ // must not release the original's allocation lock when it is collected.
292
+ if (NIL_P(futex->finalizer)) rb_undefine_finalizer(self);
293
+ rb_raise(rb_eTypeError, "Cannot copy a futex!");
294
+ }
295
+
296
+ struct IO_Event_Futex_BlockingWait {
297
+ VALUE futex;
298
+ uint32_t *address;
299
+ uint32_t expected;
300
+ int result;
301
+ int error;
302
+ };
303
+
304
+ static void *IO_Event_Futex_blocking_wait_without_gvl(void *_arguments) {
305
+ struct IO_Event_Futex_BlockingWait *arguments = _arguments;
306
+ arguments->result = syscall(SYS_futex, arguments->address, FUTEX_WAIT, arguments->expected, NULL, NULL, 0);
307
+ arguments->error = arguments->result < 0 ? errno : 0;
308
+ return NULL;
309
+ }
310
+
311
+ static VALUE IO_Event_Futex_blocking_wait_body(VALUE _arguments) {
312
+ struct IO_Event_Futex_BlockingWait *arguments = (void *)_arguments;
313
+ arguments->address = IO_Event_Futex_acquire(arguments->futex);
314
+ rb_thread_call_without_gvl(IO_Event_Futex_blocking_wait_without_gvl, arguments, RUBY_UBF_IO, 0);
315
+
316
+ if (arguments->result == 0) {
317
+ return Qtrue;
318
+ } else if (arguments->error == EAGAIN) {
319
+ return Qfalse;
320
+ } else {
321
+ rb_syserr_fail(arguments->error, "IO_Event_Futex_blocking_wait:futex");
322
+ }
323
+
324
+ return Qfalse;
325
+ }
326
+
327
+ static VALUE IO_Event_Futex_blocking_wait_ensure(VALUE _arguments) {
328
+ struct IO_Event_Futex_BlockingWait *arguments = (void *)_arguments;
329
+ if (arguments->address) IO_Event_Futex_release(arguments->futex);
330
+ return Qnil;
331
+ }
332
+
333
+ static VALUE IO_Event_Futex_blocking_wait(VALUE self, VALUE expected_value) {
334
+ struct IO_Event_Futex_BlockingWait arguments = {
335
+ .futex = self,
336
+ .expected = NUM2UINT(expected_value),
337
+ };
338
+ VALUE result = rb_ensure(IO_Event_Futex_blocking_wait_body, (VALUE)&arguments, IO_Event_Futex_blocking_wait_ensure, (VALUE)&arguments);
339
+ RB_GC_GUARD(self);
340
+ return result;
341
+ }
342
+
343
+ #ifdef IO_EVENT_FUTEX_WAITV
344
+
345
+ // Coerce all arguments before retaining native addresses. The returned array
346
+ // owns a private snapshot of the futex references: mutating the caller's entries
347
+ // while a wait is pending must not make its futexes collectible.
348
+ VALUE IO_Event_Futex_prepare_waitv(VALUE entries, struct futex_waitv *vector) {
349
+ entries = rb_ary_dup(rb_Array(entries));
350
+ long count = RARRAY_LEN(entries);
351
+ if (count < 1 || count > FUTEX_WAITV_MAX) {
352
+ rb_raise(rb_eArgError, "Futex vector must contain between 1 and %d entries!", FUTEX_WAITV_MAX);
353
+ }
354
+ VALUE futexes = rb_ary_new_capa(count);
355
+ for (long index = 0; index < count; index++) {
356
+ VALUE entry = rb_Array(RARRAY_AREF(entries, index));
357
+ if (RARRAY_LEN(entry) != 2) {
358
+ rb_raise(rb_eArgError, "Each futex vector entry must contain a futex and its expected value!");
359
+ }
360
+ rb_ary_push(futexes, RARRAY_AREF(entry, 0));
361
+ vector[index].val = NUM2UINT(RARRAY_AREF(entry, 1));
362
+ vector[index].flags = FUTEX_32;
363
+ vector[index].__reserved = 0;
364
+ }
365
+ return futexes;
366
+ }
367
+
368
+ struct IO_Event_Futex_BlockingWaitV {
369
+ VALUE futexes;
370
+ struct futex_waitv *vector;
371
+ size_t count;
372
+ size_t acquired;
373
+ int result;
374
+ int error;
375
+ };
376
+
377
+ static void *IO_Event_Futex_blocking_waitv_without_gvl(void *_arguments) {
378
+ struct IO_Event_Futex_BlockingWaitV *arguments = _arguments;
379
+ arguments->result = syscall(SYS_futex_waitv, arguments->vector, arguments->count, 0, NULL, CLOCK_MONOTONIC);
380
+ arguments->error = arguments->result < 0 ? errno : 0;
381
+ return NULL;
382
+ }
383
+
384
+ static VALUE IO_Event_Futex_blocking_waitv_body(VALUE _arguments) {
385
+ struct IO_Event_Futex_BlockingWaitV *arguments = (void *)_arguments;
386
+ while (arguments->acquired < arguments->count) {
387
+ size_t index = arguments->acquired;
388
+ arguments->vector[index].uaddr = (uintptr_t)IO_Event_Futex_acquire(RARRAY_AREF(arguments->futexes, index));
389
+ arguments->acquired += 1;
390
+ }
391
+ rb_thread_call_without_gvl(IO_Event_Futex_blocking_waitv_without_gvl, arguments, RUBY_UBF_IO, 0);
392
+
393
+ if (arguments->result >= 0) {
394
+ return INT2NUM(arguments->result);
395
+ } else if (arguments->error == EAGAIN) {
396
+ return Qnil;
397
+ } else {
398
+ rb_syserr_fail(arguments->error, "IO_Event_Futex_blocking_waitv:futex_waitv");
399
+ }
400
+
401
+ return Qnil;
402
+ }
403
+
404
+ static VALUE IO_Event_Futex_blocking_waitv_ensure(VALUE _arguments) {
405
+ struct IO_Event_Futex_BlockingWaitV *arguments = (void *)_arguments;
406
+ while (arguments->acquired) {
407
+ IO_Event_Futex_release(RARRAY_AREF(arguments->futexes, --arguments->acquired));
408
+ }
409
+ return Qnil;
410
+ }
411
+
412
+ static VALUE IO_Event_Futex_blocking_waitv(VALUE entries) {
413
+ struct futex_waitv vector[FUTEX_WAITV_MAX];
414
+ VALUE futexes = IO_Event_Futex_prepare_waitv(entries, vector);
415
+ struct IO_Event_Futex_BlockingWaitV arguments = {
416
+ .futexes = futexes,
417
+ .vector = vector,
418
+ .count = RARRAY_LEN(futexes),
419
+ };
420
+ VALUE result = rb_ensure(IO_Event_Futex_blocking_waitv_body, (VALUE)&arguments, IO_Event_Futex_blocking_waitv_ensure, (VALUE)&arguments);
421
+ RB_GC_GUARD(futexes);
422
+ return result;
423
+ }
424
+
425
+ #endif
426
+
427
+ static VALUE IO_Event_Futex_wait(VALUE self, VALUE expected_value) {
428
+ VALUE scheduler = rb_fiber_scheduler_current();
429
+ if (NIL_P(scheduler)) {
430
+ return IO_Event_Futex_blocking_wait(self, expected_value);
431
+ }
432
+
433
+ if (!rb_respond_to(scheduler, id_futex_wait)) {
434
+ rb_raise(rb_eNotImpError, "The current fiber scheduler does not support futex waits!");
435
+ }
436
+
437
+ return rb_funcall(scheduler, id_futex_wait, 2, self, expected_value);
438
+ }
439
+
440
+ #ifdef IO_EVENT_FUTEX_WAITV
441
+
442
+ static VALUE IO_Event_Futex_wait_any(VALUE klass, VALUE entries) {
443
+ (void)klass;
444
+ VALUE scheduler = rb_fiber_scheduler_current();
445
+ if (NIL_P(scheduler)) {
446
+ return IO_Event_Futex_blocking_waitv(entries);
447
+ }
448
+
449
+ if (!rb_respond_to(scheduler, id_futex_waitv)) {
450
+ rb_raise(rb_eNotImpError, "The current fiber scheduler does not support vector futex waits!");
451
+ }
452
+
453
+ return rb_funcall(scheduler, id_futex_waitv, 1, entries);
454
+ }
455
+
456
+ #endif
457
+
458
+ void Init_IO_Event_Futex(VALUE IO_Event) {
459
+ IO_Event_Futex_Finalizer_Class = rb_class_new(rb_cObject);
460
+ rb_gc_register_mark_object(IO_Event_Futex_Finalizer_Class);
461
+ rb_undef_alloc_func(IO_Event_Futex_Finalizer_Class);
462
+ rb_define_method(IO_Event_Futex_Finalizer_Class, "call", IO_Event_Futex_Finalizer_call, 1);
463
+
464
+ VALUE IO_Event_Futex = rb_define_class_under(IO_Event, "Futex", rb_cObject);
465
+ rb_define_alloc_func(IO_Event_Futex, IO_Event_Futex_allocate);
466
+ rb_define_method(IO_Event_Futex, "initialize", IO_Event_Futex_initialize, -1);
467
+ rb_define_method(IO_Event_Futex, "initialize_copy", IO_Event_Futex_initialize_copy, 1);
468
+ rb_define_method(IO_Event_Futex, "close", IO_Event_Futex_close, 0);
469
+ rb_define_method(IO_Event_Futex, "closed?", IO_Event_Futex_closed_p, 0);
470
+ rb_define_method(IO_Event_Futex, "value", IO_Event_Futex_value, 0);
471
+ rb_define_method(IO_Event_Futex, "value=", IO_Event_Futex_set_value, 1);
472
+ rb_define_method(IO_Event_Futex, "increment", IO_Event_Futex_increment, -1);
473
+ rb_define_method(IO_Event_Futex, "decrement", IO_Event_Futex_decrement, -1);
474
+ rb_define_method(IO_Event_Futex, "compare_exchange", IO_Event_Futex_compare_exchange, 2);
475
+ rb_define_method(IO_Event_Futex, "wake", IO_Event_Futex_wake, -1);
476
+ rb_define_method(IO_Event_Futex, "signal", IO_Event_Futex_signal, -1);
477
+ rb_define_method(IO_Event_Futex, "wait", IO_Event_Futex_wait, 1);
478
+
479
+ #ifdef IO_EVENT_FUTEX_WAITV
480
+ rb_define_const(IO_Event_Futex, "WAITV_LIMIT", INT2NUM(FUTEX_WAITV_MAX));
481
+ rb_define_singleton_method(IO_Event_Futex, "wait_any", IO_Event_Futex_wait_any, 1);
482
+ #endif
483
+
484
+ id_offset = rb_intern("offset");
485
+ id_futex_wait = rb_intern("futex_wait");
486
+ id_futex_waitv = rb_intern("futex_waitv");
487
+ }
488
+
489
+ #endif
@@ -0,0 +1,42 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2026, by Samuel Williams.
3
+
4
+ #pragma once
5
+
6
+ #include <ruby.h>
7
+
8
+ #ifdef HAVE_RUBY_IO_BUFFER_H
9
+ #include <ruby/io/buffer.h>
10
+ #endif
11
+
12
+ // Version 3 (Ruby 4.1) provides counted allocation locks, allowing independent
13
+ // futex words to retain the same buffer without unlocking each other.
14
+ #if defined(__linux__) && RUBY_IO_BUFFER_VERSION >= 3 && defined(HAVE_LINUX_FUTEX_H) && defined(HAVE_SYS_SYSCALL_H)
15
+
16
+ #define IO_EVENT_FUTEX
17
+
18
+ #include <linux/futex.h>
19
+ #include <stdint.h>
20
+ #include <sys/syscall.h>
21
+
22
+ #ifndef FUTEX2_SIZE_U32
23
+ #define FUTEX2_SIZE_U32 2
24
+ #endif
25
+
26
+ #ifndef FUTEX_32
27
+ #define FUTEX_32 2
28
+ #endif
29
+
30
+ #if defined(FUTEX_WAITV_MAX) && defined(SYS_futex_waitv)
31
+ #define IO_EVENT_FUTEX_WAITV
32
+ #endif
33
+
34
+ uint32_t *IO_Event_Futex_address(VALUE self);
35
+ uint32_t *IO_Event_Futex_acquire(VALUE self);
36
+ void IO_Event_Futex_release(VALUE self);
37
+ #ifdef IO_EVENT_FUTEX_WAITV
38
+ VALUE IO_Event_Futex_prepare_waitv(VALUE entries, struct futex_waitv *vector);
39
+ #endif
40
+ void Init_IO_Event_Futex(VALUE IO_Event);
41
+
42
+ #endif