io-event 1.19.4 → 1.20.0

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.
@@ -81,8 +81,26 @@ struct IO_Event_Selector_URing_Completion
81
81
  struct IO_Event_List list;
82
82
 
83
83
  struct IO_Event_Selector_URing_Waiting *waiting;
84
+ bool cancellation_pending;
84
85
  };
85
86
 
87
+ // Cancellation completions use the low bit to distinguish them from the
88
+ // completion of the operation being cancelled. Completion records are aligned,
89
+ // so the low bit is otherwise always clear.
90
+ #define IO_EVENT_SELECTOR_URING_CANCELLATION_TAG ((uint64_t)1)
91
+
92
+ static inline uint64_t
93
+ IO_Event_Selector_URing_Completion_cancellation_data(struct IO_Event_Selector_URing_Completion *completion)
94
+ {
95
+ return ((uint64_t)(uintptr_t)completion) | IO_EVENT_SELECTOR_URING_CANCELLATION_TAG;
96
+ }
97
+
98
+ static inline struct IO_Event_Selector_URing_Completion *
99
+ IO_Event_Selector_URing_Completion_from_cancellation_data(uint64_t data)
100
+ {
101
+ return (struct IO_Event_Selector_URing_Completion *)(uintptr_t)(data & ~IO_EVENT_SELECTOR_URING_CANCELLATION_TAG);
102
+ }
103
+
86
104
  static
87
105
  void IO_Event_Selector_URing_Completion_mark(void *_completion)
88
106
  {
@@ -188,6 +206,9 @@ struct IO_Event_Selector_URing_Completion * IO_Event_Selector_URing_Completion_a
188
206
 
189
207
  if (DEBUG_COMPLETION) fprintf(stderr, "IO_Event_Selector_URing_Completion_acquire(%p, limit=%ld)\n", (void*)completion, selector->completions.limit);
190
208
 
209
+ assert(completion->waiting == NULL);
210
+ assert(!completion->cancellation_pending);
211
+
191
212
  waiting->completion = completion;
192
213
  completion->waiting = waiting;
193
214
 
@@ -206,14 +227,41 @@ void IO_Event_Selector_URing_Completion_cancel(struct IO_Event_Selector_URing_Co
206
227
  }
207
228
 
208
229
  inline static
209
- void IO_Event_Selector_URing_Completion_release(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion)
230
+ void IO_Event_Selector_URing_Completion_recycle(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion)
210
231
  {
211
- if (DEBUG_COMPLETION) fprintf(stderr, "IO_Event_Selector_URing_Completion_release(%p)\n", (void*)completion);
232
+ assert(completion->waiting == NULL);
233
+ assert(!completion->cancellation_pending);
212
234
 
213
- IO_Event_Selector_URing_Completion_cancel(completion);
214
235
  IO_Event_List_prepend(&selector->free_list, &completion->list);
215
236
  }
216
237
 
238
+ inline static
239
+ void IO_Event_Selector_URing_Completion_complete(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion)
240
+ {
241
+ if (DEBUG_COMPLETION) fprintf(stderr, "IO_Event_Selector_URing_Completion_complete(%p)\n", (void*)completion);
242
+
243
+ IO_Event_Selector_URing_Completion_cancel(completion);
244
+
245
+ // A cancellation SQE still refers to this completion record. Keep it out of
246
+ // the free list until that CQE has also been observed, avoiding ABA reuse.
247
+ if (!completion->cancellation_pending) {
248
+ IO_Event_Selector_URing_Completion_recycle(selector, completion);
249
+ }
250
+ }
251
+
252
+ inline static
253
+ void IO_Event_Selector_URing_Completion_cancellation_complete(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion)
254
+ {
255
+ if (DEBUG_COMPLETION) fprintf(stderr, "IO_Event_Selector_URing_Completion_cancellation_complete(%p)\n", (void*)completion);
256
+
257
+ assert(completion->cancellation_pending);
258
+ completion->cancellation_pending = false;
259
+
260
+ if (completion->waiting == NULL) {
261
+ IO_Event_Selector_URing_Completion_recycle(selector, completion);
262
+ }
263
+ }
264
+
217
265
  inline static
218
266
  void IO_Event_Selector_URing_Waiting_cancel(struct IO_Event_Selector_URing_Waiting *waiting)
219
267
  {
@@ -234,6 +282,8 @@ void IO_Event_Selector_URing_Completion_initialize(void *element)
234
282
  struct IO_Event_Selector_URing_Completion *completion = element;
235
283
  IO_Event_List_initialize(&completion->list);
236
284
  completion->list.type = &IO_Event_Selector_URing_Completion_Type;
285
+ completion->waiting = NULL;
286
+ completion->cancellation_pending = false;
237
287
  }
238
288
 
239
289
  void IO_Event_Selector_URing_Completion_free(void *element)
@@ -500,6 +550,36 @@ struct io_uring_sqe * io_get_sqe(struct IO_Event_Selector_URing *selector) {
500
550
  return sqe;
501
551
  }
502
552
 
553
+ static
554
+ void IO_Event_Selector_URing_Completion_cancel_async(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Completion *completion)
555
+ {
556
+ if (completion->cancellation_pending) return;
557
+
558
+ completion->cancellation_pending = true;
559
+
560
+ struct io_uring_sqe *sqe = io_get_sqe(selector);
561
+ io_uring_prep_cancel(sqe, completion, 0);
562
+ io_uring_sqe_set_data64(sqe, IO_Event_Selector_URing_Completion_cancellation_data(completion));
563
+ io_uring_submit_pending(selector);
564
+ }
565
+
566
+ static
567
+ void IO_Event_Selector_URing_Waiting_cancel_and_wait(struct IO_Event_Selector_URing *selector, struct IO_Event_Selector_URing_Waiting *waiting)
568
+ {
569
+ if (waiting->completion) {
570
+ IO_Event_Selector_URing_Completion_cancel_async(selector, waiting->completion);
571
+
572
+ // The kernel may still be reading from or writing to the supplied buffer.
573
+ // Keep the C frame, buffer lock and completion record alive until the
574
+ // original operation CQE confirms that it can no longer access memory.
575
+ while (waiting->completion) {
576
+ IO_Event_Selector_loop_yield(&selector->backend);
577
+ }
578
+ }
579
+
580
+ IO_Event_Selector_URing_Waiting_cancel(waiting);
581
+ }
582
+
503
583
  #pragma mark - Process.wait
504
584
 
505
585
  #ifdef IO_EVENT_SELECTOR_URING_USE_WAITID
@@ -604,6 +684,10 @@ static
604
684
  VALUE process_wait_ensure(VALUE _arguments) {
605
685
  struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments;
606
686
 
687
+ if (arguments->waiting->completion) {
688
+ IO_Event_Selector_URing_Completion_cancel_async(arguments->selector, arguments->waiting->completion);
689
+ }
690
+
607
691
  #ifndef IO_EVENT_SELECTOR_URING_USE_WAITID
608
692
  close(arguments->descriptor);
609
693
  #endif
@@ -714,13 +798,8 @@ static
714
798
  VALUE io_wait_ensure(VALUE _arguments) {
715
799
  struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments;
716
800
 
717
- // If the operation is still in progress, cancel it:
718
801
  if (arguments->waiting->completion) {
719
- if (DEBUG) fprintf(stderr, "io_wait_ensure:io_uring_prep_cancel(waiting=%p, completion=%p)\n", (void*)arguments->waiting, (void*)arguments->waiting->completion);
720
- struct io_uring_sqe *sqe = io_get_sqe(arguments->selector);
721
- io_uring_prep_cancel(sqe, (void*)arguments->waiting->completion, 0);
722
- io_uring_sqe_set_data(sqe, NULL);
723
- io_uring_submit_now(arguments->selector);
802
+ IO_Event_Selector_URing_Completion_cancel_async(arguments->selector, arguments->waiting->completion);
724
803
  }
725
804
 
726
805
  IO_Event_Selector_URing_Waiting_cancel(arguments->waiting);
@@ -824,7 +903,7 @@ io_read_submit(VALUE _arguments)
824
903
  struct io_uring_sqe *sqe = io_get_sqe(selector);
825
904
  io_uring_prep_read(sqe, arguments->descriptor, arguments->buffer, arguments->length, arguments->offset);
826
905
  io_uring_sqe_set_data(sqe, arguments->waiting->completion);
827
- io_uring_submit_now(selector);
906
+ io_uring_submit_pending(selector);
828
907
 
829
908
  IO_Event_Selector_loop_yield(&selector->backend);
830
909
 
@@ -837,16 +916,7 @@ io_read_ensure(VALUE _arguments)
837
916
  struct io_read_arguments *arguments = (struct io_read_arguments *)_arguments;
838
917
  struct IO_Event_Selector_URing *selector = arguments->selector;
839
918
 
840
- // If the operation is still in progress, cancel it:
841
- if (arguments->waiting->completion) {
842
- if (DEBUG) fprintf(stderr, "io_read_ensure:io_uring_prep_cancel(waiting=%p, completion=%p)\n", (void*)arguments->waiting, (void*)arguments->waiting->completion);
843
- struct io_uring_sqe *sqe = io_get_sqe(selector);
844
- io_uring_prep_cancel(sqe, (void*)arguments->waiting->completion, 0);
845
- io_uring_sqe_set_data(sqe, NULL);
846
- io_uring_submit_now(selector);
847
- }
848
-
849
- IO_Event_Selector_URing_Waiting_cancel(arguments->waiting);
919
+ IO_Event_Selector_URing_Waiting_cancel_and_wait(selector, arguments->waiting);
850
920
 
851
921
  return Qnil;
852
922
  }
@@ -876,16 +946,86 @@ io_read(struct IO_Event_Selector_URing *selector, VALUE fiber, int descriptor, c
876
946
  );
877
947
  }
878
948
 
879
- VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _length, VALUE _offset) {
949
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
950
+ struct io_read_locked_arguments {
951
+ struct IO_Event_Selector_URing *selector;
952
+ VALUE fiber;
953
+ VALUE io;
954
+ off_t from;
955
+ size_t offset;
956
+ size_t length;
957
+ bool positional;
958
+ };
959
+
960
+ static VALUE
961
+ io_read_locked(void *base, size_t size, VALUE _arguments)
962
+ {
963
+ struct io_read_locked_arguments *arguments = (struct io_read_locked_arguments *)_arguments;
964
+
965
+ if (!IO_Event_Selector_valid_buffer_range(size, arguments->offset, arguments->length)) {
966
+ return rb_fiber_scheduler_io_result(-1, EINVAL);
967
+ } else if (arguments->length == 0) {
968
+ return rb_fiber_scheduler_io_result(0, 0);
969
+ }
970
+
971
+ int descriptor = IO_Event_Selector_io_descriptor(arguments->io);
972
+ char *buffer = (char*)base + arguments->offset;
973
+
974
+ // Avoid the submission and suspension overhead when the operation can
975
+ // complete immediately. The descriptor must be non-blocking for this
976
+ // optimistic syscall; if it would block, restore its original mode and
977
+ // submit the operation to io_uring instead.
978
+ int flags = IO_Event_Selector_nonblock_set(descriptor);
979
+ ssize_t result;
980
+
981
+ if (arguments->positional) {
982
+ result = pread(descriptor, buffer, arguments->length, arguments->from);
983
+ } else {
984
+ result = read(descriptor, buffer, arguments->length);
985
+ }
986
+
987
+ int error = errno;
988
+ IO_Event_Selector_nonblock_restore(descriptor, flags);
989
+
990
+ if (result >= 0) {
991
+ return rb_fiber_scheduler_io_result(result, 0);
992
+ } else if (!IO_Event_try_again(error)) {
993
+ return rb_fiber_scheduler_io_result(-1, error);
994
+ }
995
+
996
+ off_t from = arguments->positional ? arguments->from : io_seekable(descriptor);
997
+
998
+ int completion = io_read(arguments->selector, arguments->fiber, descriptor, buffer, arguments->length, from);
999
+ if (completion < 0) {
1000
+ return rb_fiber_scheduler_io_result(-1, -completion);
1001
+ }
1002
+
1003
+ return rb_fiber_scheduler_io_result(completion, 0);
1004
+ }
1005
+ #endif
1006
+
1007
+ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _first, VALUE _second) {
880
1008
  struct IO_Event_Selector_URing *selector = NULL;
881
1009
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
882
1010
 
1011
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1012
+ struct io_read_locked_arguments arguments = {
1013
+ .selector = selector,
1014
+ .fiber = fiber,
1015
+ .io = io,
1016
+ .offset = NUM2SIZET(_first),
1017
+ .length = NUM2SIZET(_second),
1018
+ .positional = false,
1019
+ };
1020
+
1021
+ return rb_io_buffer_locked_for_writing(buffer, io_read_locked, (VALUE)&arguments);
1022
+ #else
883
1023
  void *base;
884
1024
  size_t size;
885
1025
  rb_io_buffer_get_bytes_for_writing(buffer, &base, &size);
886
1026
 
887
- size_t length = NUM2SIZET(_length);
888
- size_t offset = NUM2SIZET(_offset);
1027
+ size_t length = NUM2SIZET(_first);
1028
+ size_t offset = NUM2SIZET(_second);
889
1029
  size_t total = 0;
890
1030
 
891
1031
  // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
@@ -894,10 +1034,10 @@ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b
894
1034
  } else if (offset == size) {
895
1035
  return rb_fiber_scheduler_io_result(0, 0);
896
1036
  }
1037
+ #endif
897
1038
 
898
1039
  int descriptor = IO_Event_Selector_io_descriptor(io);
899
1040
  off_t from = io_seekable(descriptor);
900
-
901
1041
  size_t maximum_size = size - offset;
902
1042
 
903
1043
  // Are we performing a non-blocking read?
@@ -933,8 +1073,10 @@ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b
933
1073
  }
934
1074
 
935
1075
  return rb_fiber_scheduler_io_result(total, 0);
1076
+ #endif
936
1077
  }
937
1078
 
1079
+ #if RUBY_FIBER_SCHEDULER_VERSION < 4
938
1080
  static VALUE IO_Event_Selector_URing_io_read_compatible(int argc, VALUE *argv, VALUE self)
939
1081
  {
940
1082
  rb_check_arity(argc, 4, 5);
@@ -947,19 +1089,32 @@ static VALUE IO_Event_Selector_URing_io_read_compatible(int argc, VALUE *argv, V
947
1089
 
948
1090
  return IO_Event_Selector_URing_io_read(self, argv[0], argv[1], argv[2], argv[3], _offset);
949
1091
  }
1092
+ #endif
950
1093
 
951
- VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _from, VALUE _length, VALUE _offset) {
1094
+ VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _from, VALUE _first, VALUE _second) {
952
1095
  struct IO_Event_Selector_URing *selector = NULL;
953
1096
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
954
1097
 
1098
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1099
+ struct io_read_locked_arguments arguments = {
1100
+ .selector = selector,
1101
+ .fiber = fiber,
1102
+ .io = io,
1103
+ .from = NUM2OFFT(_from),
1104
+ .offset = NUM2SIZET(_first),
1105
+ .length = NUM2SIZET(_second),
1106
+ .positional = true,
1107
+ };
1108
+
1109
+ return rb_io_buffer_locked_for_writing(buffer, io_read_locked, (VALUE)&arguments);
1110
+ #else
955
1111
  void *base;
956
1112
  size_t size;
957
1113
  rb_io_buffer_get_bytes_for_writing(buffer, &base, &size);
958
1114
 
959
- size_t length = NUM2SIZET(_length);
960
- size_t offset = NUM2SIZET(_offset);
1115
+ size_t length = NUM2SIZET(_first);
1116
+ size_t offset = NUM2SIZET(_second);
961
1117
  size_t total = 0;
962
- off_t from = NUM2OFFT(_from);
963
1118
 
964
1119
  // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset.
965
1120
  if (offset > size) {
@@ -967,9 +1122,9 @@ VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE
967
1122
  } else if (offset == size) {
968
1123
  return rb_fiber_scheduler_io_result(0, 0);
969
1124
  }
970
-
1125
+ #endif
1126
+ off_t from = NUM2OFFT(_from);
971
1127
  int descriptor = IO_Event_Selector_io_descriptor(io);
972
-
973
1128
  size_t maximum_size = size - offset;
974
1129
  while (maximum_size) {
975
1130
  int result = io_read(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -992,6 +1147,7 @@ VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE
992
1147
  }
993
1148
 
994
1149
  return rb_fiber_scheduler_io_result(total, 0);
1150
+ #endif
995
1151
  }
996
1152
 
997
1153
  #pragma mark - IO#write
@@ -1029,16 +1185,7 @@ io_write_ensure(VALUE _argument)
1029
1185
  struct io_write_arguments *arguments = (struct io_write_arguments*)_argument;
1030
1186
  struct IO_Event_Selector_URing *selector = arguments->selector;
1031
1187
 
1032
- // If the operation is still in progress, cancel it:
1033
- if (arguments->waiting->completion) {
1034
- if (DEBUG) fprintf(stderr, "io_write_ensure:io_uring_prep_cancel(waiting=%p, completion=%p)\n", (void*)arguments->waiting, (void*)arguments->waiting->completion);
1035
- struct io_uring_sqe *sqe = io_get_sqe(selector);
1036
- io_uring_prep_cancel(sqe, (void*)arguments->waiting->completion, 0);
1037
- io_uring_sqe_set_data(sqe, NULL);
1038
- io_uring_submit_now(selector);
1039
- }
1040
-
1041
- IO_Event_Selector_URing_Waiting_cancel(arguments->waiting);
1188
+ IO_Event_Selector_URing_Waiting_cancel_and_wait(selector, arguments->waiting);
1042
1189
 
1043
1190
  return Qnil;
1044
1191
  }
@@ -1068,16 +1215,86 @@ io_write(struct IO_Event_Selector_URing *selector, VALUE fiber, int descriptor,
1068
1215
  );
1069
1216
  }
1070
1217
 
1071
- VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _length, VALUE _offset) {
1218
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1219
+ struct io_write_locked_arguments {
1220
+ struct IO_Event_Selector_URing *selector;
1221
+ VALUE fiber;
1222
+ VALUE io;
1223
+ off_t from;
1224
+ size_t offset;
1225
+ size_t length;
1226
+ bool positional;
1227
+ };
1228
+
1229
+ static VALUE
1230
+ io_write_locked(const void *base, size_t size, VALUE _arguments)
1231
+ {
1232
+ struct io_write_locked_arguments *arguments = (struct io_write_locked_arguments *)_arguments;
1233
+
1234
+ if (!IO_Event_Selector_valid_buffer_range(size, arguments->offset, arguments->length)) {
1235
+ return rb_fiber_scheduler_io_result(-1, EINVAL);
1236
+ } else if (arguments->length == 0) {
1237
+ return rb_fiber_scheduler_io_result(0, 0);
1238
+ }
1239
+
1240
+ int descriptor = IO_Event_Selector_io_descriptor(arguments->io);
1241
+ const char *buffer = (const char*)base + arguments->offset;
1242
+
1243
+ // Avoid the submission and suspension overhead when the operation can
1244
+ // complete immediately. The descriptor must be non-blocking for this
1245
+ // optimistic syscall; if it would block, restore its original mode and
1246
+ // submit the operation to io_uring instead.
1247
+ int flags = IO_Event_Selector_nonblock_set(descriptor);
1248
+ ssize_t result;
1249
+
1250
+ if (arguments->positional) {
1251
+ result = pwrite(descriptor, buffer, arguments->length, arguments->from);
1252
+ } else {
1253
+ result = write(descriptor, buffer, arguments->length);
1254
+ }
1255
+
1256
+ int error = errno;
1257
+ IO_Event_Selector_nonblock_restore(descriptor, flags);
1258
+
1259
+ if (result >= 0) {
1260
+ return rb_fiber_scheduler_io_result(result, 0);
1261
+ } else if (!IO_Event_try_again(error)) {
1262
+ return rb_fiber_scheduler_io_result(-1, error);
1263
+ }
1264
+
1265
+ off_t from = arguments->positional ? arguments->from : io_seekable(descriptor);
1266
+
1267
+ int completion = io_write(arguments->selector, arguments->fiber, descriptor, (char*)buffer, arguments->length, from);
1268
+ if (completion < 0) {
1269
+ return rb_fiber_scheduler_io_result(-1, -completion);
1270
+ }
1271
+
1272
+ return rb_fiber_scheduler_io_result(completion, 0);
1273
+ }
1274
+ #endif
1275
+
1276
+ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _first, VALUE _second) {
1072
1277
  struct IO_Event_Selector_URing *selector = NULL;
1073
1278
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
1074
1279
 
1280
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1281
+ struct io_write_locked_arguments arguments = {
1282
+ .selector = selector,
1283
+ .fiber = fiber,
1284
+ .io = io,
1285
+ .offset = NUM2SIZET(_first),
1286
+ .length = NUM2SIZET(_second),
1287
+ .positional = false,
1288
+ };
1289
+
1290
+ return rb_io_buffer_locked_for_reading(buffer, io_write_locked, (VALUE)&arguments);
1291
+ #else
1075
1292
  const void *base;
1076
1293
  size_t size;
1077
1294
  rb_io_buffer_get_bytes_for_reading(buffer, &base, &size);
1078
1295
 
1079
- size_t length = NUM2SIZET(_length);
1080
- size_t offset = NUM2SIZET(_offset);
1296
+ size_t length = NUM2SIZET(_first);
1297
+ size_t offset = NUM2SIZET(_second);
1081
1298
  size_t total = 0;
1082
1299
 
1083
1300
  if (length > size) {
@@ -1090,10 +1307,10 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
1090
1307
  } else if (offset == size) {
1091
1308
  return rb_fiber_scheduler_io_result(0, 0);
1092
1309
  }
1310
+ #endif
1093
1311
 
1094
1312
  int descriptor = IO_Event_Selector_io_descriptor(io);
1095
1313
  off_t from = io_seekable(descriptor);
1096
-
1097
1314
  size_t maximum_size = size - offset;
1098
1315
  while (maximum_size) {
1099
1316
  int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -1115,8 +1332,10 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE
1115
1332
  }
1116
1333
 
1117
1334
  return rb_fiber_scheduler_io_result(total, 0);
1335
+ #endif
1118
1336
  }
1119
1337
 
1338
+ #if RUBY_FIBER_SCHEDULER_VERSION < 4
1120
1339
  static VALUE IO_Event_Selector_URing_io_write_compatible(int argc, VALUE *argv, VALUE self)
1121
1340
  {
1122
1341
  rb_check_arity(argc, 4, 5);
@@ -1129,19 +1348,32 @@ static VALUE IO_Event_Selector_URing_io_write_compatible(int argc, VALUE *argv,
1129
1348
 
1130
1349
  return IO_Event_Selector_URing_io_write(self, argv[0], argv[1], argv[2], argv[3], _offset);
1131
1350
  }
1351
+ #endif
1132
1352
 
1133
- VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _from, VALUE _length, VALUE _offset) {
1353
+ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _from, VALUE _first, VALUE _second) {
1134
1354
  struct IO_Event_Selector_URing *selector = NULL;
1135
1355
  TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector);
1136
1356
 
1357
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1358
+ struct io_write_locked_arguments arguments = {
1359
+ .selector = selector,
1360
+ .fiber = fiber,
1361
+ .io = io,
1362
+ .from = NUM2OFFT(_from),
1363
+ .offset = NUM2SIZET(_first),
1364
+ .length = NUM2SIZET(_second),
1365
+ .positional = true,
1366
+ };
1367
+
1368
+ return rb_io_buffer_locked_for_reading(buffer, io_write_locked, (VALUE)&arguments);
1369
+ #else
1137
1370
  const void *base;
1138
1371
  size_t size;
1139
1372
  rb_io_buffer_get_bytes_for_reading(buffer, &base, &size);
1140
1373
 
1141
- size_t length = NUM2SIZET(_length);
1142
- size_t offset = NUM2SIZET(_offset);
1374
+ size_t length = NUM2SIZET(_first);
1375
+ size_t offset = NUM2SIZET(_second);
1143
1376
  size_t total = 0;
1144
- off_t from = NUM2OFFT(_from);
1145
1377
 
1146
1378
  if (length > size) {
1147
1379
  rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!");
@@ -1153,9 +1385,9 @@ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE
1153
1385
  } else if (offset == size) {
1154
1386
  return rb_fiber_scheduler_io_result(0, 0);
1155
1387
  }
1156
-
1388
+ #endif
1389
+ off_t from = NUM2OFFT(_from);
1157
1390
  int descriptor = IO_Event_Selector_io_descriptor(io);
1158
-
1159
1391
  size_t maximum_size = size - offset;
1160
1392
  while (maximum_size) {
1161
1393
  int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from);
@@ -1178,6 +1410,7 @@ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE
1178
1410
  }
1179
1411
 
1180
1412
  return rb_fiber_scheduler_io_result(total, 0);
1413
+ #endif
1181
1414
  }
1182
1415
 
1183
1416
  #endif
@@ -1329,6 +1562,13 @@ unsigned select_process_completions(struct IO_Event_Selector_URing *selector) {
1329
1562
  continue;
1330
1563
  }
1331
1564
 
1565
+ if (cqe->user_data & IO_EVENT_SELECTOR_URING_CANCELLATION_TAG) {
1566
+ struct IO_Event_Selector_URing_Completion *completion = IO_Event_Selector_URing_Completion_from_cancellation_data(cqe->user_data);
1567
+ IO_Event_Selector_URing_Completion_cancellation_complete(selector, completion);
1568
+ io_uring_cq_advance(ring, 1);
1569
+ continue;
1570
+ }
1571
+
1332
1572
  struct IO_Event_Selector_URing_Completion *completion = (void*)cqe->user_data;
1333
1573
  struct IO_Event_Selector_URing_Waiting *waiting = completion->waiting;
1334
1574
 
@@ -1343,13 +1583,11 @@ unsigned select_process_completions(struct IO_Event_Selector_URing *selector) {
1343
1583
 
1344
1584
  VALUE fiber = 0;
1345
1585
  if (waiting && waiting->fiber) {
1346
- assert(waiting->result != -ECANCELED);
1347
-
1348
1586
  fiber = waiting->fiber;
1349
1587
  }
1350
1588
 
1351
1589
  // This marks the waiting operation as "complete":
1352
- IO_Event_Selector_URing_Completion_release(selector, completion);
1590
+ IO_Event_Selector_URing_Completion_complete(selector, completion);
1353
1591
 
1354
1592
  if (fiber) {
1355
1593
  IO_Event_Selector_loop_resume(&selector->backend, fiber, 0, NULL);
@@ -1511,8 +1749,13 @@ void Init_IO_Event_Selector_URing(VALUE IO_Event_Selector) {
1511
1749
  rb_define_method(IO_Event_Selector_URing, "io_wait", IO_Event_Selector_URing_io_wait, 3);
1512
1750
 
1513
1751
  #ifdef HAVE_RUBY_IO_BUFFER_H
1752
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
1753
+ rb_define_method(IO_Event_Selector_URing, "io_read", IO_Event_Selector_URing_io_read, 5);
1754
+ rb_define_method(IO_Event_Selector_URing, "io_write", IO_Event_Selector_URing_io_write, 5);
1755
+ #else
1514
1756
  rb_define_method(IO_Event_Selector_URing, "io_read", IO_Event_Selector_URing_io_read_compatible, -1);
1515
1757
  rb_define_method(IO_Event_Selector_URing, "io_write", IO_Event_Selector_URing_io_write_compatible, -1);
1758
+ #endif
1516
1759
  rb_define_method(IO_Event_Selector_URing, "io_pread", IO_Event_Selector_URing_io_pread, 6);
1517
1760
  rb_define_method(IO_Event_Selector_URing, "io_pwrite", IO_Event_Selector_URing_io_pwrite, 6);
1518
1761
  #endif
@@ -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 worker_pool_work_begin(VALUE _work) {
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, "worker_pool_work_begin:rb_fiber_scheduler_block work=%p\n", work);
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
- int state = 0;
362
- while (true) {
363
- int current_state = 0;
364
- rb_protect(worker_pool_work_begin, (VALUE)&work, &current_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) {
@@ -177,15 +177,15 @@ module IO::Event
177
177
  end
178
178
 
179
179
  # Read from the given IO, forwarded to the underlying selector.
180
- def io_read(fiber, io, buffer, length, offset = 0)
181
- log("Reading from IO #{io.inspect} with buffer #{buffer}; length #{length} offset #{offset}")
182
- @selector.io_read(fiber, io, buffer, length, offset)
180
+ def io_read(fiber, io, buffer, *arguments)
181
+ log("Reading from IO #{io.inspect} with buffer #{buffer}; arguments #{arguments.inspect}")
182
+ @selector.io_read(fiber, io, buffer, *arguments)
183
183
  end
184
184
 
185
185
  # Write to the given IO, forwarded to the underlying selector.
186
- def io_write(fiber, io, buffer, length, offset = 0)
187
- log("Writing to IO #{io.inspect} with buffer #{buffer}; length #{length} offset #{offset}")
188
- @selector.io_write(fiber, io, buffer, length, offset)
186
+ def io_write(fiber, io, buffer, *arguments)
187
+ log("Writing to IO #{io.inspect} with buffer #{buffer}; arguments #{arguments.inspect}")
188
+ @selector.io_write(fiber, io, buffer, *arguments)
189
189
  end
190
190
 
191
191
  # Forward the given method to the underlying selector.