io-event 1.19.3 → 1.19.5
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/getting-started.md +31 -5
- data/ext/io/event/selector/epoll.c +12 -9
- data/ext/io/event/selector/kqueue.c +7 -5
- data/ext/io/event/worker_pool.c +45 -34
- data/lib/io/event/version.rb +1 -1
- data/readme.md +8 -8
- data/releases.md +8 -0
- data.tar.gz.sig +0 -0
- metadata +1 -1
- 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: f7b5b3a617c2f1ef060c072fc860927172b31a8e7e574cc3607f8107326b57bf
|
|
4
|
+
data.tar.gz: '078687c1ee12579d38d9c5d85f8ad4a49827872567ed48f6835098a8081a6929'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '063501649153028126c8b1087d60c8c089aeb03f536ca86e3f5be2a494d891ab1bfca0b08fc796f5d050a8d28dab68c1ffda6fcf2f6e3ea4b20c4ea0310ae8c3'
|
|
7
|
+
data.tar.gz: cd0df567da5d936491da4c454106ae0c80e4b5a0368bc2ce2ddf02642dd5e313aec14e34d74d3b700be46d9f15f9e76413dae7a8397716afcc08e24a8280b1d9
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/context/getting-started.md
CHANGED
|
@@ -25,29 +25,55 @@ This example shows how to perform a blocking operation
|
|
|
25
25
|
require "fiber"
|
|
26
26
|
require "io/event"
|
|
27
27
|
|
|
28
|
+
# Create an I/O event selector controlled by the main Fiber.
|
|
28
29
|
selector = IO::Event::Selector.new(Fiber.current)
|
|
30
|
+
|
|
31
|
+
# input: read end, output: write end.
|
|
29
32
|
input, output = IO.pipe
|
|
30
33
|
|
|
31
34
|
writer = Fiber.new do
|
|
35
|
+
puts "[writer] Writing data"
|
|
36
|
+
|
|
32
37
|
output.write("Hello World")
|
|
33
38
|
output.close
|
|
34
39
|
end
|
|
35
40
|
|
|
36
41
|
reader = Fiber.new do
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
puts "[reader] No data available, waiting for input"
|
|
43
|
+
|
|
44
|
+
# Suspend this Fiber until the input becomes readable.
|
|
45
|
+
selector.io_wait(
|
|
46
|
+
Fiber.current,
|
|
47
|
+
input,
|
|
48
|
+
IO::READABLE
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Execution resumes here once the selector detects the event.
|
|
52
|
+
puts "[reader] Received: #{input.read.inspect}"
|
|
39
53
|
end
|
|
40
54
|
|
|
41
|
-
|
|
55
|
+
puts "[main] Transferring to reader fiber"
|
|
42
56
|
reader.transfer
|
|
43
57
|
|
|
44
|
-
|
|
58
|
+
puts "[main] Reader is waiting, but main can keep running"
|
|
59
|
+
|
|
60
|
+
puts "[main] Transferring to writer fiber"
|
|
45
61
|
writer.transfer
|
|
46
62
|
|
|
63
|
+
puts "[main] Checking for I/O events"
|
|
47
64
|
selector.select(1)
|
|
48
65
|
|
|
66
|
+
puts "[main] Done"
|
|
67
|
+
|
|
49
68
|
# Results in:
|
|
50
|
-
#
|
|
69
|
+
# [main] Transferring to reader fiber
|
|
70
|
+
# [reader] No data available, waiting for input
|
|
71
|
+
# [main] Reader is waiting, but main can keep running
|
|
72
|
+
# [main] Transferring to writer fiber
|
|
73
|
+
# [writer] Writing data
|
|
74
|
+
# [main] Checking for I/O events
|
|
75
|
+
# [reader] Received: "Hello World"
|
|
76
|
+
# [main] Done
|
|
51
77
|
```
|
|
52
78
|
|
|
53
79
|
## Debugging
|
|
@@ -847,6 +847,7 @@ struct select_arguments {
|
|
|
847
847
|
|
|
848
848
|
int count;
|
|
849
849
|
int result;
|
|
850
|
+
int error;
|
|
850
851
|
struct epoll_event events[EPOLL_MAX_EVENTS];
|
|
851
852
|
|
|
852
853
|
struct timespec * timeout;
|
|
@@ -868,9 +869,9 @@ static int make_timeout_ms(struct timespec * timeout) {
|
|
|
868
869
|
}
|
|
869
870
|
|
|
870
871
|
static
|
|
871
|
-
int enosys_error(int result) {
|
|
872
|
+
int enosys_error(int result, int error) {
|
|
872
873
|
if (result == -1) {
|
|
873
|
-
return
|
|
874
|
+
return error == ENOSYS;
|
|
874
875
|
}
|
|
875
876
|
|
|
876
877
|
return 0;
|
|
@@ -882,12 +883,13 @@ void * select_internal(void *_arguments) {
|
|
|
882
883
|
|
|
883
884
|
#if defined(HAVE_EPOLL_PWAIT2)
|
|
884
885
|
arguments->result = epoll_pwait2(arguments->selector->descriptor, arguments->events, arguments->count, arguments->timeout, NULL);
|
|
886
|
+
arguments->error = errno;
|
|
885
887
|
|
|
886
888
|
// Comment out the above line and enable the below lines to test ENOSYS code path.
|
|
887
889
|
// arguments->result = -1;
|
|
888
|
-
//
|
|
890
|
+
// arguments->error = ENOSYS;
|
|
889
891
|
|
|
890
|
-
if (!enosys_error(arguments->result)) {
|
|
892
|
+
if (!enosys_error(arguments->result, arguments->error)) {
|
|
891
893
|
return NULL;
|
|
892
894
|
}
|
|
893
895
|
else {
|
|
@@ -896,6 +898,7 @@ void * select_internal(void *_arguments) {
|
|
|
896
898
|
#endif
|
|
897
899
|
|
|
898
900
|
arguments->result = epoll_wait(arguments->selector->descriptor, arguments->events, arguments->count, make_timeout_ms(arguments->timeout));
|
|
901
|
+
arguments->error = errno;
|
|
899
902
|
|
|
900
903
|
return NULL;
|
|
901
904
|
}
|
|
@@ -903,12 +906,12 @@ void * select_internal(void *_arguments) {
|
|
|
903
906
|
static
|
|
904
907
|
int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
905
908
|
arguments->result = -1;
|
|
909
|
+
arguments->error = EINTR;
|
|
906
910
|
IO_Event_Selector_blocking_operation(&arguments->selector->backend, select_internal, (void *)arguments, RUBY_UBF_IO, 0);
|
|
907
911
|
|
|
908
912
|
if (arguments->result == -1) {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
rb_sys_fail("select_internal_without_gvl:epoll_wait");
|
|
913
|
+
if (arguments->error != EINTR) {
|
|
914
|
+
rb_syserr_fail(arguments->error, "select_internal_without_gvl:epoll_wait");
|
|
912
915
|
} else {
|
|
913
916
|
return 0;
|
|
914
917
|
}
|
|
@@ -922,8 +925,8 @@ int select_internal_with_gvl(struct select_arguments *arguments) {
|
|
|
922
925
|
select_internal((void *)arguments);
|
|
923
926
|
|
|
924
927
|
if (arguments->result == -1) {
|
|
925
|
-
if (
|
|
926
|
-
|
|
928
|
+
if (arguments->error != EINTR) {
|
|
929
|
+
rb_syserr_fail(arguments->error, "select_internal_with_gvl:epoll_wait");
|
|
927
930
|
} else {
|
|
928
931
|
return 0;
|
|
929
932
|
}
|
|
@@ -851,6 +851,7 @@ struct select_arguments {
|
|
|
851
851
|
|
|
852
852
|
int count;
|
|
853
853
|
int result;
|
|
854
|
+
int error;
|
|
854
855
|
struct kevent events[KQUEUE_MAX_EVENTS];
|
|
855
856
|
|
|
856
857
|
struct timespec storage;
|
|
@@ -864,6 +865,7 @@ void * select_internal(void *_arguments) {
|
|
|
864
865
|
struct select_arguments * arguments = (struct select_arguments *)_arguments;
|
|
865
866
|
|
|
866
867
|
arguments->result = kevent(arguments->selector->descriptor, NULL, 0, arguments->events, arguments->count, arguments->timeout);
|
|
868
|
+
arguments->error = errno;
|
|
867
869
|
|
|
868
870
|
return NULL;
|
|
869
871
|
}
|
|
@@ -871,12 +873,12 @@ void * select_internal(void *_arguments) {
|
|
|
871
873
|
static
|
|
872
874
|
int select_internal_without_gvl(struct select_arguments *arguments) {
|
|
873
875
|
arguments->result = -1;
|
|
876
|
+
arguments->error = EINTR;
|
|
874
877
|
IO_Event_Selector_blocking_operation(&arguments->selector->backend, select_internal, (void *)arguments, RUBY_UBF_IO, 0);
|
|
875
878
|
|
|
876
879
|
if (arguments->result == -1) {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
rb_sys_fail("select_internal_without_gvl:kevent");
|
|
880
|
+
if (arguments->error != EINTR) {
|
|
881
|
+
rb_syserr_fail(arguments->error, "select_internal_without_gvl:kevent");
|
|
880
882
|
} else {
|
|
881
883
|
return 0;
|
|
882
884
|
}
|
|
@@ -890,8 +892,8 @@ int select_internal_with_gvl(struct select_arguments *arguments) {
|
|
|
890
892
|
select_internal((void *)arguments);
|
|
891
893
|
|
|
892
894
|
if (arguments->result == -1) {
|
|
893
|
-
if (
|
|
894
|
-
|
|
895
|
+
if (arguments->error != EINTR) {
|
|
896
|
+
rb_syserr_fail(arguments->error, "select_internal_with_gvl:kevent");
|
|
895
897
|
} else {
|
|
896
898
|
return 0;
|
|
897
899
|
}
|
data/ext/io/event/worker_pool.c
CHANGED
|
@@ -306,15 +306,54 @@ static VALUE worker_pool_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
306
306
|
return self;
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
static VALUE
|
|
309
|
+
static VALUE worker_pool_work_block(VALUE _work) {
|
|
310
310
|
struct IO_Event_WorkerPool_Work *work = (void*)_work;
|
|
311
311
|
|
|
312
|
-
if (DEBUG) fprintf(stderr, "
|
|
312
|
+
if (DEBUG) fprintf(stderr, "worker_pool_work_block:rb_fiber_scheduler_block work=%p\n", work);
|
|
313
313
|
rb_fiber_scheduler_block(work->scheduler, work->blocker, Qnil);
|
|
314
314
|
|
|
315
315
|
return Qnil;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
static VALUE worker_pool_work_wait(VALUE _work) {
|
|
319
|
+
struct IO_Event_WorkerPool_Work *work = (void*)_work;
|
|
320
|
+
|
|
321
|
+
while (true) {
|
|
322
|
+
worker_pool_work_block(_work);
|
|
323
|
+
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
|
+
}
|
|
332
|
+
|
|
333
|
+
return Qtrue;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
static VALUE worker_pool_work_ensure(VALUE _work) {
|
|
337
|
+
struct IO_Event_WorkerPool_Work *work = (void*)_work;
|
|
338
|
+
|
|
339
|
+
while (!work->completed) {
|
|
340
|
+
if (DEBUG) fprintf(stderr, "worker_pool_work_ensure:rb_fiber_scheduler_blocking_operation_cancel\n");
|
|
341
|
+
rb_fiber_scheduler_blocking_operation_cancel(work->blocking_operation);
|
|
342
|
+
|
|
343
|
+
int state = 0;
|
|
344
|
+
rb_protect(worker_pool_work_block, _work, &state);
|
|
345
|
+
if (DEBUG) fprintf(stderr, "-- worker_pool_work_ensure:work completed=%d, state=%d\n", work->completed, state);
|
|
346
|
+
|
|
347
|
+
if (state) {
|
|
348
|
+
// Ignore errors raised while waiting for cancellation to complete. rb_ensure
|
|
349
|
+
// will restore and rethrow the original control-flow state after this returns.
|
|
350
|
+
rb_set_errinfo(Qnil);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return Qnil;
|
|
355
|
+
}
|
|
356
|
+
|
|
318
357
|
// Ruby method to submit work and wait for completion
|
|
319
358
|
static VALUE worker_pool_call(VALUE self, VALUE _blocking_operation) {
|
|
320
359
|
struct IO_Event_WorkerPool *pool;
|
|
@@ -357,38 +396,10 @@ static VALUE worker_pool_call(VALUE self, VALUE _blocking_operation) {
|
|
|
357
396
|
pthread_cond_signal(&pool->work_available);
|
|
358
397
|
pthread_mutex_unlock(&pool->mutex);
|
|
359
398
|
|
|
360
|
-
// Block the current fiber until work is completed
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
rb_protect(worker_pool_work_begin, (VALUE)&work, ¤t_state);
|
|
365
|
-
if (DEBUG) fprintf(stderr, "-- worker_pool_call:work completed=%d, current_state=%d, state=%d\n", work.completed, current_state, state);
|
|
366
|
-
|
|
367
|
-
// Store the first exception state:
|
|
368
|
-
if (!state) {
|
|
369
|
-
state = current_state;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// If the work is still in the queue, we must wait for a worker to complete it (even if cancelled):
|
|
373
|
-
if (work.completed) {
|
|
374
|
-
// The work was completed, we can exit the loop:
|
|
375
|
-
break;
|
|
376
|
-
} else {
|
|
377
|
-
if (DEBUG) fprintf(stderr, "worker_pool_call:rb_fiber_scheduler_blocking_operation_cancel\n");
|
|
378
|
-
// Ensure the blocking operation is cancelled:
|
|
379
|
-
rb_fiber_scheduler_blocking_operation_cancel(blocking_operation);
|
|
380
|
-
|
|
381
|
-
// The work was not completed, we need to wait for it to be completed, so we go around the loop again.
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (DEBUG) fprintf(stderr, "<- worker_pool_call:work completed=%d, state=%d\n", work.completed, state);
|
|
386
|
-
|
|
387
|
-
if (state) {
|
|
388
|
-
rb_jump_tag(state);
|
|
389
|
-
} else {
|
|
390
|
-
return Qtrue;
|
|
391
|
-
}
|
|
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);
|
|
392
403
|
}
|
|
393
404
|
|
|
394
405
|
static VALUE worker_pool_allocate(VALUE klass) {
|
data/lib/io/event/version.rb
CHANGED
data/readme.md
CHANGED
|
@@ -18,6 +18,14 @@ 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.19.5
|
|
22
|
+
|
|
23
|
+
- Preserve the original exception or non-local control flow when `IO::Event::WorkerPool` cancellation interrupts a blocked fiber, while still cancelling and draining the in-flight blocking operation before returning control to Ruby.
|
|
24
|
+
|
|
25
|
+
### v1.19.4
|
|
26
|
+
|
|
27
|
+
- Capture `errno` immediately after `epoll_wait` / `kevent`, preventing stale or subsequently clobbered values from raising a spurious `Errno::*` when a native selector wait is interrupted or skipped.
|
|
28
|
+
|
|
21
29
|
### v1.19.3
|
|
22
30
|
|
|
23
31
|
- Prevent `URing`, `EPoll`, and `KQueue` from entering a native wait when a signal exception becomes pending during the GVL transition. On Ruby versions without `RB_NOGVL_PENDING_INTR_FAIL`, the fallback now refreshes pending-interrupt state immediately before releasing the GVL while preserving the caller's exception-delivery timing.
|
|
@@ -51,14 +59,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
|
|
|
51
59
|
|
|
52
60
|
- Handle `IOError` raised while shutting down the pure Ruby interrupt pipe, so `IO::Event::Interrupt#close` does not leak expected shutdown errors from the interrupt fiber.
|
|
53
61
|
|
|
54
|
-
### v1.16.2
|
|
55
|
-
|
|
56
|
-
- Improve timer heap performance by batching scheduled timer insertion, compacting cancelled timers during flush, and avoiding unnecessary heap rebuilds for small incremental inserts.
|
|
57
|
-
|
|
58
|
-
### v1.16.1
|
|
59
|
-
|
|
60
|
-
- Ensure the pure Ruby `Select` selector returns `false`, not `nil`, when `io_wait` resumes without any ready events.
|
|
61
|
-
|
|
62
62
|
## Contributing
|
|
63
63
|
|
|
64
64
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v1.19.5
|
|
4
|
+
|
|
5
|
+
- Preserve the original exception or non-local control flow when `IO::Event::WorkerPool` cancellation interrupts a blocked fiber, while still cancelling and draining the in-flight blocking operation before returning control to Ruby.
|
|
6
|
+
|
|
7
|
+
## v1.19.4
|
|
8
|
+
|
|
9
|
+
- Capture `errno` immediately after `epoll_wait` / `kevent`, preventing stale or subsequently clobbered values from raising a spurious `Errno::*` when a native selector wait is interrupted or skipped.
|
|
10
|
+
|
|
3
11
|
## v1.19.3
|
|
4
12
|
|
|
5
13
|
- Prevent `URing`, `EPoll`, and `KQueue` from entering a native wait when a signal exception becomes pending during the GVL transition. On Ruby versions without `RB_NOGVL_PENDING_INTR_FAIL`, the fallback now refreshes pending-interrupt state immediately before releasing the GVL while preserving the caller's exception-delivery timing.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
|
Binary file
|