io-event 1.22.0 → 1.22.1
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/ext/io/event/selector/epoll.c +2 -2
- data/ext/io/event/selector/kqueue.c +2 -2
- data/ext/io/event/selector/selector.c +58 -14
- data/ext/io/event/selector/selector.h +14 -0
- data/ext/io/event/selector/uring.c +3 -3
- data/lib/io/event/version.rb +1 -1
- data/readme.md +5 -4
- data/releases.md +5 -0
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a02e2dca1e03b9cb6e7a23525af5e35bac78cb0d3661ff0d219d4f5f115baac3
|
|
4
|
+
data.tar.gz: aaaf14ed6534424afca5f46cf867c3b5d9e323a12328e58c0c28ab0e7578e06c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd3db9299b34ec8e6143b1fb303da42002ec263cfd90987d5ec693889ab03edc8b3deeee7137596ae17e327e2efa42a89fbc6d89e70c718121ed5780900362fc
|
|
7
|
+
data.tar.gz: f0821c42ff1a0f135a11e59700229236b6d64a408a4dde1c18c9ee637b775c2701d9df877656070d3c312829f3b46c7679129d928839825fb70dd60fc434f6a5
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -437,7 +437,7 @@ VALUE IO_Event_Selector_EPoll_ready_p(VALUE self) {
|
|
|
437
437
|
struct IO_Event_Selector_EPoll *selector = NULL;
|
|
438
438
|
TypedData_Get_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_EPoll_Type, selector);
|
|
439
439
|
|
|
440
|
-
return selector->backend
|
|
440
|
+
return IO_Event_Selector_ready_p(&selector->backend) ? Qtrue : Qfalse;
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
struct process_wait_arguments {
|
|
@@ -1101,7 +1101,7 @@ VALUE IO_Event_Selector_EPoll_select(VALUE self, VALUE duration) {
|
|
|
1101
1101
|
// 2. Didn't process any events from non-blocking select (above), and
|
|
1102
1102
|
// 3. There are no items in the ready list,
|
|
1103
1103
|
// then we can perform a blocking select.
|
|
1104
|
-
if (!ready && !result && !selector->backend
|
|
1104
|
+
if (!ready && !result && !IO_Event_Selector_ready_p(&selector->backend)) {
|
|
1105
1105
|
arguments.timeout = make_timeout(duration, &arguments.storage);
|
|
1106
1106
|
|
|
1107
1107
|
if (select_blocking_allowed(arguments.timeout)) {
|
|
@@ -425,7 +425,7 @@ VALUE IO_Event_Selector_KQueue_ready_p(VALUE self) {
|
|
|
425
425
|
struct IO_Event_Selector_KQueue *selector = NULL;
|
|
426
426
|
TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector);
|
|
427
427
|
|
|
428
|
-
return selector->backend
|
|
428
|
+
return IO_Event_Selector_ready_p(&selector->backend) ? Qtrue : Qfalse;
|
|
429
429
|
}
|
|
430
430
|
|
|
431
431
|
struct process_wait_arguments {
|
|
@@ -1080,7 +1080,7 @@ VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) {
|
|
|
1080
1080
|
// 2. Didn't process any events from non-blocking select (above), and
|
|
1081
1081
|
// 3. There are no items in the ready list,
|
|
1082
1082
|
// then we can perform a blocking select.
|
|
1083
|
-
if (!ready && !result && !selector->backend
|
|
1083
|
+
if (!ready && !result && !IO_Event_Selector_ready_p(&selector->backend)) {
|
|
1084
1084
|
arguments.timeout = make_timeout(duration, &arguments.storage);
|
|
1085
1085
|
|
|
1086
1086
|
if (select_blocking_allowed(arguments.timeout)) {
|
|
@@ -354,27 +354,71 @@ void IO_Event_Selector_ready_pop(struct IO_Event_Selector *backend, struct IO_Ev
|
|
|
354
354
|
IO_Event_Selector_loop_resume(backend, fiber, 0, NULL);
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
|
|
357
|
+
// State shared by the rb_ensure body and cleanup callbacks. Each flush owns a distinct placeholder on its C stack, including when flushes are nested.
|
|
358
|
+
struct ready_flush_arguments {
|
|
359
|
+
struct IO_Event_Selector *backend;
|
|
360
|
+
struct IO_Event_Selector_Queue placeholder;
|
|
361
|
+
int count;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// rb_ensure requires VALUE(VALUE) callbacks. IO_Event_Selector_ready_flush wraps this loop and returns the count stored in the shared arguments.
|
|
365
|
+
static VALUE IO_Event_Selector_ready_flush_begin(VALUE _arguments)
|
|
358
366
|
{
|
|
359
|
-
|
|
367
|
+
struct ready_flush_arguments *arguments = (struct ready_flush_arguments *)_arguments;
|
|
368
|
+
struct IO_Event_Selector *backend = arguments->backend;
|
|
360
369
|
|
|
361
|
-
//
|
|
362
|
-
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
|
|
370
|
+
// rb_ensure has installed cleanup before entering this callback. Link the placeholder before any operation that can raise or yield.
|
|
371
|
+
//
|
|
372
|
+
// Saving the last existing entry is unsafe: another fiber can remove it. Counting entries instead can let newly queued work replace removed entries. This placeholder stays linked until its owning flush finishes, preserving:
|
|
373
|
+
// existing entries -> placeholder -> newly queued entries
|
|
374
|
+
queue_push(backend, &arguments->placeholder);
|
|
366
375
|
|
|
367
|
-
// Process from head to tail in order:
|
|
368
|
-
// During this, more items may be appended to tail.
|
|
369
376
|
while (backend->ready) {
|
|
370
|
-
if (DEBUG) fprintf(stderr, "backend->ready = %p\n", backend->ready);
|
|
371
377
|
struct IO_Event_Selector_Queue *ready = backend->ready;
|
|
372
378
|
|
|
373
|
-
|
|
374
|
-
|
|
379
|
+
// Each flush stops at its own placeholder. Entries beyond an outer placeholder may already have been queued when this flush started. Skip other placeholders without unlinking them: their owners still need them as boundaries and will remove them in their ensure callbacks.
|
|
380
|
+
while (ready && ready != &arguments->placeholder && (ready->flags & IO_EVENT_SELECTOR_QUEUE_PLACEHOLDER)) {
|
|
381
|
+
ready = ready->head;
|
|
382
|
+
}
|
|
375
383
|
|
|
376
|
-
if (ready ==
|
|
384
|
+
if (!ready || ready == &arguments->placeholder) break;
|
|
385
|
+
|
|
386
|
+
// Resuming a fiber can unlink other entries, so read backend->ready again on the next iteration instead of retaining a neighbour pointer.
|
|
387
|
+
arguments->count += 1;
|
|
388
|
+
IO_Event_Selector_ready_pop(backend, ready);
|
|
377
389
|
}
|
|
378
390
|
|
|
379
|
-
return
|
|
391
|
+
return Qnil;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
static VALUE IO_Event_Selector_ready_flush_ensure(VALUE _arguments)
|
|
395
|
+
{
|
|
396
|
+
struct ready_flush_arguments *arguments = (struct ready_flush_arguments *)_arguments;
|
|
397
|
+
|
|
398
|
+
// Only the owning flush removes this placeholder, exactly once. Unlinking it reconnects its neighbours without removing any other placeholders.
|
|
399
|
+
queue_pop(arguments->backend, &arguments->placeholder);
|
|
400
|
+
|
|
401
|
+
return Qnil;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
int IO_Event_Selector_ready_flush(struct IO_Event_Selector *backend)
|
|
405
|
+
{
|
|
406
|
+
if (!backend->ready) return 0;
|
|
407
|
+
|
|
408
|
+
struct ready_flush_arguments arguments = {
|
|
409
|
+
.backend = backend,
|
|
410
|
+
.placeholder = {
|
|
411
|
+
.head = NULL,
|
|
412
|
+
.tail = NULL,
|
|
413
|
+
.flags = IO_EVENT_SELECTOR_QUEUE_PLACEHOLDER,
|
|
414
|
+
// Queue marking and compaction visit every node, including this one.
|
|
415
|
+
.fiber = Qnil,
|
|
416
|
+
},
|
|
417
|
+
.count = 0,
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Always unlink the placeholder on normal return or exception, before the arguments leave scope, so the queue cannot retain a pointer into this stack.
|
|
421
|
+
rb_ensure(IO_Event_Selector_ready_flush_begin, (VALUE)&arguments, IO_Event_Selector_ready_flush_ensure, (VALUE)&arguments);
|
|
422
|
+
|
|
423
|
+
return arguments.count;
|
|
380
424
|
}
|
|
@@ -77,6 +77,8 @@ void IO_Event_Selector_nonblock_restore(int file_descriptor, int flags);
|
|
|
77
77
|
enum IO_Event_Selector_Queue_Flags {
|
|
78
78
|
IO_EVENT_SELECTOR_QUEUE_FIBER = 1,
|
|
79
79
|
IO_EVENT_SELECTOR_QUEUE_INTERNAL = 2,
|
|
80
|
+
// A stack-allocated flush boundary; never dispatched or freed as a fiber entry.
|
|
81
|
+
IO_EVENT_SELECTOR_QUEUE_PLACEHOLDER = 4,
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
struct IO_Event_Selector_Queue {
|
|
@@ -107,6 +109,18 @@ struct IO_Event_Selector {
|
|
|
107
109
|
|
|
108
110
|
void IO_Event_Selector_initialize(struct IO_Event_Selector *backend, VALUE self, VALUE loop);
|
|
109
111
|
|
|
112
|
+
// Whether the queue contains any entries other than flush placeholders.
|
|
113
|
+
static inline
|
|
114
|
+
int IO_Event_Selector_ready_p(struct IO_Event_Selector *backend) {
|
|
115
|
+
struct IO_Event_Selector_Queue *ready = backend->ready;
|
|
116
|
+
// Nested flushes can leave several adjacent placeholders at the front. Look past them for real entries, including work deferred to the next flush.
|
|
117
|
+
while (ready && (ready->flags & IO_EVENT_SELECTOR_QUEUE_PLACEHOLDER)) {
|
|
118
|
+
ready = ready->head;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return ready != NULL;
|
|
122
|
+
}
|
|
123
|
+
|
|
110
124
|
static inline
|
|
111
125
|
void IO_Event_Selector_mark(struct IO_Event_Selector *backend) {
|
|
112
126
|
rb_gc_mark_movable(backend->self);
|
|
@@ -463,7 +463,7 @@ VALUE IO_Event_Selector_URing_ready_p(VALUE self) {
|
|
|
463
463
|
struct IO_Event_Selector_URing *selector = NULL;
|
|
464
464
|
TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
|
|
465
465
|
|
|
466
|
-
return selector->backend
|
|
466
|
+
return IO_Event_Selector_ready_p(&selector->backend) ? Qtrue : Qfalse;
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
#pragma mark - Submission Queue
|
|
@@ -1677,7 +1677,7 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
|
|
|
1677
1677
|
// 2. Didn't process any events from non-blocking select (above), and
|
|
1678
1678
|
// 3. There are no items in the ready list,
|
|
1679
1679
|
// then we can perform a blocking select.
|
|
1680
|
-
if (!ready && !completed && !selector->backend
|
|
1680
|
+
if (!ready && !completed && !IO_Event_Selector_ready_p(&selector->backend)) {
|
|
1681
1681
|
// We might need to wait for events:
|
|
1682
1682
|
struct select_arguments arguments = {
|
|
1683
1683
|
.selector = selector,
|
|
@@ -1687,7 +1687,7 @@ VALUE IO_Event_Selector_URing_select(VALUE self, VALUE duration) {
|
|
|
1687
1687
|
|
|
1688
1688
|
arguments.timeout = make_timeout(duration, &arguments.storage);
|
|
1689
1689
|
|
|
1690
|
-
if (!selector->backend
|
|
1690
|
+
if (!IO_Event_Selector_ready_p(&selector->backend) && select_blocking_allowed(arguments.timeout)) {
|
|
1691
1691
|
struct timespec start_time;
|
|
1692
1692
|
IO_Event_Time_current(&start_time);
|
|
1693
1693
|
|
data/lib/io/event/version.rb
CHANGED
data/readme.md
CHANGED
|
@@ -18,6 +18,11 @@ Please see the [project documentation](https://socketry.github.io/io-event/) for
|
|
|
18
18
|
|
|
19
19
|
Please see the [project releases](https://socketry.github.io/io-event/releases/index) for all releases.
|
|
20
20
|
|
|
21
|
+
### v1.22.1
|
|
22
|
+
|
|
23
|
+
- 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.
|
|
24
|
+
- Preserve each ready queue flush's boundary with its own placeholder so newly queued fibers are deferred even when earlier entries are removed. Nested native flushes skip other placeholders, processing work queued before their own boundary. Native selectors ignore placeholders when checking readiness and remove them if a fiber raises.
|
|
25
|
+
|
|
21
26
|
### v1.22.0
|
|
22
27
|
|
|
23
28
|
- Reject non-finite timer times (`NaN` and positive or negative infinity), preventing invalid heap ordering and permanently retained timer handles.
|
|
@@ -56,10 +61,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
|
|
|
56
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.
|
|
57
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.
|
|
58
63
|
|
|
59
|
-
### v1.18.0
|
|
60
|
-
|
|
61
|
-
- **Fixed**: Avoid entering a blocking native selector wait when an interrupt is already pending for the current thread.
|
|
62
|
-
|
|
63
64
|
## Contributing
|
|
64
65
|
|
|
65
66
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v1.22.1
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
- Preserve each ready queue flush's boundary with its own placeholder so newly queued fibers are deferred even when earlier entries are removed. Nested native flushes skip other placeholders, processing work queued before their own boundary. Native selectors ignore placeholders when checking readiness and remove them if a fiber raises.
|
|
7
|
+
|
|
3
8
|
## v1.22.0
|
|
4
9
|
|
|
5
10
|
- Reject non-finite timer times (`NaN` and positive or negative infinity), preventing invalid heap ordering and permanently retained timer handles.
|
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.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -124,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
124
124
|
- !ruby/object:Gem::Version
|
|
125
125
|
version: '0'
|
|
126
126
|
requirements: []
|
|
127
|
-
rubygems_version: 4.0.
|
|
127
|
+
rubygems_version: 4.0.16
|
|
128
128
|
specification_version: 4
|
|
129
129
|
summary: An event loop.
|
|
130
130
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|