uringmachine 0.18 → 0.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18144dad22716ba83fde0f2ec1d3bffbb1a13684044f99f279eba183a70e7181
4
- data.tar.gz: 0e742fa4e8ac06475fa7bfab120239863b76f47f5ca33c2a9cc3d0c26ef27bc8
3
+ metadata.gz: ff89f34d541e086f8016156d4b5290a6a2fdc94292f04e7a3104ef9e7cda256d
4
+ data.tar.gz: 01dbe57b83effbab744fbd07461dcd803246a88bbf3ce1aeaf1e4eace32fd0ad
5
5
  SHA512:
6
- metadata.gz: fe981430bc6dace32cb166e3e8177ab5b4cf90f86021ee14990fa01b573459fbace2f17484e2307a9cf8ee83b59b547ae5e2c051774fcb8a444297293214d1e4
7
- data.tar.gz: 8cc7d29d54de95ab70d197db9c1a035d6302750ca86d2ecfb94599e9bed5e3b4f1119eefc8e4804f89f09a20242cd35c0fe277b1a214f2742fce7642b6b83c97
6
+ metadata.gz: ca0e44f1121384634b4234b2cd49643f87e8255e77eb250f64fb62be8bcdcb18e6a82480e5da003322c941c9ae2def119a048797d53bc064c4e1f77796b82c4d
7
+ data.tar.gz: 322090fd12498525d303b63f66ec1c559ed9933e89e74d3b01c9cc5c0f3f1dcee951c767625690c288478d5e460248418bbd693fbd7c31146c7aff76cf545a5d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 0.19.1 2025-01-03
2
+
3
+ - Add `RB_GC_GUARD` in `process_runqueue_op`
4
+
5
+ # 0.19 2025-10-27
6
+
7
+ - Fix usage of `RAISE_IF_EXCEPTION` after `RB_GC_GUARD`
8
+
1
9
  # 0.18 2025-08-30
2
10
 
3
11
  - Fix `#write_async` to properly mark the given string
data/TODO.md CHANGED
@@ -1,3 +1,44 @@
1
+ ## immediate
2
+
3
+ - make a reproducer for segfault on timeout, spin lots of fibers where a timeout
4
+ wraps a #shift call (from an empty queue).
5
+ - see also: https://mensfeld.pl/2025/11/ruby-ffi-gc-bug-hash-becomes-string/
6
+
7
+ Analysis:
8
+
9
+ - The segfault is related to timeouts
10
+ - Looking at process_runqueue_op (um.c):
11
+
12
+ ```c
13
+ inline VALUE process_runqueue_op(struct um *machine, struct um_op *op) {
14
+ VALUE fiber = op->fiber;
15
+ VALUE value = op->value;
16
+
17
+ // on timeout, the op flags are changed to turn on OP_F_TRANSIENT
18
+ if (unlikely(op->flags & OP_F_TRANSIENT))
19
+ // here the op is freed, so the value is not visible to the GC anymoore
20
+ um_op_free(machine, op);
21
+
22
+ // if a GC occurs here, we risk a segfault
23
+
24
+ // value is used
25
+ return rb_fiber_transfer(fiber, 1, &value);
26
+ }
27
+ ```
28
+
29
+ - So, a possible solution is to put a `RB_GC_GUARD` after the `return`.
30
+ - But first, I want to be able to reproduce it. We can start by setting
31
+ `GC.stress = true` on tests and see if we segfault.
32
+
33
+ ## FiberScheduler implementation
34
+
35
+ Some resources:
36
+
37
+ - https://github.com/socketry/async/blob/main/context/getting-started.md
38
+ - https://github.com/socketry/async/blob/main/context/scheduler.md
39
+ - https://github.com/socketry/async/blob/main/lib/async/scheduler.rb#L28
40
+ -
41
+
1
42
  ## useful concurrency tools
2
43
 
3
44
  - debounce
@@ -16,7 +57,7 @@
16
57
  - splice / - tee
17
58
  - sendto
18
59
  - recvfrom
19
- - poll_add / poll_remove / poll_multishot / poll_update
60
+ - poll_multishot
20
61
  - fsync
21
62
  - mkdir / mkdirat
22
63
  - link / linkat / unlink / unlinkat / symlink
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/uringmachine'
4
+ require_relative '../lib/uringmachine/fiber_scheduler'
5
+ require 'net/http'
6
+
7
+ machine = UringMachine.new
8
+ scheduler = UM::FiberScheduler.new(machine)
9
+ Fiber.set_scheduler scheduler
10
+
11
+ i, o = IO.pipe
12
+
13
+ f1 = Fiber.schedule do
14
+ # sleep 0.4
15
+ # o.write 'Hello, world!'
16
+ # o.close
17
+ end
18
+
19
+ f2 = Fiber.schedule do
20
+ # puts "hi"
21
+ # 10.times do
22
+ # sleep 0.1
23
+ # puts "."
24
+ # end
25
+ end
26
+
27
+ # Fiber.schedule do
28
+ # scheduler.block(:wait)
29
+ # end
30
+
31
+ f3 = Fiber.schedule do
32
+ # message = i.read
33
+ # puts message
34
+ # rescue => e
35
+ # scheduler.p e
36
+ # scheduler.p e.backtrace
37
+ end
38
+
39
+ f4 = Fiber.schedule do
40
+ puts '*' * 40
41
+ # tcp = TCPSocket.new('noteflakes.com', 80)
42
+ # tcp.timeout = 10
43
+ # scheduler.p tcp
44
+ # tcp << "GET / HTTP/1.1\r\nHost: noteflakes.com\r\n\r\n"
45
+ # s = tcp.read
46
+ # scheduler.p s: s
47
+ ret = Net::HTTP.get('noteflakes.com', '/ping')
48
+ scheduler.p ret: ret
49
+ rescue => e
50
+ scheduler.p e
51
+ scheduler.p e.backtrace
52
+ end
53
+
54
+ scheduler.join(f1, f2, f3, f4)
55
+
56
+ __END__
57
+
58
+ stdout_fd = STDOUT.fileno
59
+ stdin_fd = STDIN.fileno
60
+ machine.write(stdout_fd, "Hello, world!\n")
61
+
62
+ loop do
63
+ machine.write(stdout_fd, "Say something: ")
64
+ buf = +''
65
+ res = machine.read(stdin_fd, buf, 8192)
66
+ if res > 0
67
+ machine.write(stdout_fd, "You said: #{buf}")
68
+ else
69
+ break
70
+ end
71
+ end
data/ext/um/um.c CHANGED
@@ -194,7 +194,10 @@ inline VALUE process_runqueue_op(struct um *machine, struct um_op *op) {
194
194
  if (unlikely(op->flags & OP_F_TRANSIENT))
195
195
  um_op_free(machine, op);
196
196
 
197
- return rb_fiber_transfer(fiber, 1, &value);
197
+ VALUE ret = rb_fiber_transfer(fiber, 1, &value);
198
+ RB_GC_GUARD(value);
199
+ RB_GC_GUARD(ret);
200
+ return ret;
198
201
  }
199
202
 
200
203
  inline VALUE um_fiber_switch(struct um *machine) {
@@ -252,8 +255,10 @@ inline int um_check_completion(struct um *machine, struct um_op *op) {
252
255
  }
253
256
 
254
257
  inline VALUE um_await(struct um *machine) {
255
- VALUE v = um_fiber_switch(machine);
256
- return raise_if_exception(v);
258
+ VALUE ret = um_fiber_switch(machine);
259
+ RAISE_IF_EXCEPTION(ret);
260
+ RB_GC_GUARD(ret);
261
+ return ret;
257
262
  }
258
263
 
259
264
  inline void um_prep_op(struct um *machine, struct um_op *op, enum op_kind kind, unsigned flags) {
@@ -264,6 +269,7 @@ inline void um_prep_op(struct um *machine, struct um_op *op, enum op_kind kind,
264
269
  VALUE fiber = (flags & OP_F_FREE_ON_COMPLETE) ? Qnil : rb_fiber_current();
265
270
  RB_OBJ_WRITE(machine->self, &op->fiber, fiber);
266
271
  RB_OBJ_WRITE(machine->self, &op->value, Qnil);
272
+ RB_OBJ_WRITE(machine->self, &op->async_op, Qnil);
267
273
  }
268
274
 
269
275
  inline void um_schedule(struct um *machine, VALUE fiber, VALUE value) {
@@ -273,6 +279,7 @@ inline void um_schedule(struct um *machine, VALUE fiber, VALUE value) {
273
279
  op->flags = OP_F_TRANSIENT;
274
280
  RB_OBJ_WRITE(machine->self, &op->fiber, fiber);
275
281
  RB_OBJ_WRITE(machine->self, &op->value, value);
282
+ RB_OBJ_WRITE(machine->self, &op->async_op, Qnil);
276
283
  um_runqueue_push(machine, op);
277
284
  }
278
285
 
@@ -309,6 +316,7 @@ VALUE um_timeout(struct um *machine, VALUE interval, VALUE class) {
309
316
  op->ts = um_double_to_timespec(NUM2DBL(interval));
310
317
  RB_OBJ_WRITE(machine->self, &op->fiber, rb_fiber_current());
311
318
  RB_OBJ_WRITE(machine->self, &op->value, rb_funcall(class, ID_new, 0));
319
+ RB_OBJ_WRITE(machine->self, &op->async_op, Qnil);
312
320
 
313
321
  struct io_uring_sqe *sqe = um_get_sqe(machine, op);
314
322
  io_uring_prep_timeout(sqe, &op->ts, 0, 0);
@@ -340,8 +348,9 @@ VALUE um_sleep(struct um *machine, double duration) {
340
348
  ret = DBL2NUM(duration);
341
349
  }
342
350
 
351
+ RAISE_IF_EXCEPTION(ret);
343
352
  RB_GC_GUARD(ret);
344
- return raise_if_exception(ret);
353
+ return ret;
345
354
  }
346
355
 
347
356
  inline VALUE um_read(struct um *machine, int fd, VALUE buffer, int maxlen, int buffer_offset) {
@@ -357,10 +366,11 @@ inline VALUE um_read(struct um *machine, int fd, VALUE buffer, int maxlen, int b
357
366
  ret = INT2NUM(op.result.res);
358
367
 
359
368
  }
360
-
361
369
  RB_GC_GUARD(buffer);
370
+
371
+ RAISE_IF_EXCEPTION(ret);
362
372
  RB_GC_GUARD(ret);
363
- return raise_if_exception(ret);
373
+ return ret;
364
374
  }
365
375
 
366
376
  inline size_t um_read_raw(struct um *machine, int fd, char *buffer, int maxlen) {
@@ -375,7 +385,8 @@ inline size_t um_read_raw(struct um *machine, int fd, char *buffer, int maxlen)
375
385
 
376
386
  }
377
387
 
378
- raise_if_exception(ret);
388
+ RAISE_IF_EXCEPTION(ret);
389
+ RB_GC_GUARD(ret);
379
390
  return 0;
380
391
  }
381
392
 
@@ -393,14 +404,18 @@ VALUE um_write(struct um *machine, int fd, VALUE str, int len) {
393
404
  ret = INT2NUM(op.result.res);
394
405
 
395
406
  RB_GC_GUARD(str);
407
+
408
+ RAISE_IF_EXCEPTION(ret);
396
409
  RB_GC_GUARD(ret);
397
- return raise_if_exception(ret);
410
+ return ret;
398
411
  }
399
412
 
400
413
  VALUE um_write_async(struct um *machine, int fd, VALUE str) {
401
414
  struct um_op *op = um_op_alloc(machine);
402
415
  um_prep_op(machine, op, OP_WRITE_ASYNC, OP_F_TRANSIENT | OP_F_FREE_ON_COMPLETE);
416
+ RB_OBJ_WRITE(machine->self, &op->fiber, Qnil);
403
417
  RB_OBJ_WRITE(machine->self, &op->value, str);
418
+ RB_OBJ_WRITE(machine->self, &op->async_op, Qnil);
404
419
 
405
420
  struct io_uring_sqe *sqe = um_get_sqe(machine, op);
406
421
  io_uring_prep_write(sqe, fd, RSTRING_PTR(str), RSTRING_LEN(str), -1);
@@ -419,13 +434,17 @@ VALUE um_close(struct um *machine, int fd) {
419
434
  if (um_check_completion(machine, &op))
420
435
  ret = INT2NUM(fd);
421
436
 
437
+ RAISE_IF_EXCEPTION(ret);
422
438
  RB_GC_GUARD(ret);
423
- return raise_if_exception(ret);
439
+ return ret;
424
440
  }
425
441
 
426
442
  VALUE um_close_async(struct um *machine, int fd) {
427
443
  struct um_op *op = um_op_alloc(machine);
428
444
  um_prep_op(machine, op, OP_CLOSE_ASYNC, OP_F_FREE_ON_COMPLETE);
445
+ RB_OBJ_WRITE(machine->self, &op->fiber, Qnil);
446
+ RB_OBJ_WRITE(machine->self, &op->value, Qnil);
447
+ RB_OBJ_WRITE(machine->self, &op->async_op, Qnil);
429
448
 
430
449
  struct io_uring_sqe *sqe = um_get_sqe(machine, op);
431
450
  io_uring_prep_close(sqe, fd);
@@ -443,8 +462,9 @@ VALUE um_accept(struct um *machine, int fd) {
443
462
  if (um_check_completion(machine, &op))
444
463
  ret = INT2NUM(op.result.res);
445
464
 
465
+ RAISE_IF_EXCEPTION(ret);
446
466
  RB_GC_GUARD(ret);
447
- return raise_if_exception(ret);
467
+ return ret;
448
468
  }
449
469
 
450
470
  VALUE um_socket(struct um *machine, int domain, int type, int protocol, uint flags) {
@@ -457,8 +477,9 @@ VALUE um_socket(struct um *machine, int domain, int type, int protocol, uint fla
457
477
  if (um_check_completion(machine, &op))
458
478
  ret = INT2NUM(op.result.res);
459
479
 
480
+ RAISE_IF_EXCEPTION(ret);
460
481
  RB_GC_GUARD(ret);
461
- return raise_if_exception(ret);
482
+ return ret;
462
483
  }
463
484
 
464
485
  VALUE um_connect(struct um *machine, int fd, const struct sockaddr *addr, socklen_t addrlen) {
@@ -471,8 +492,9 @@ VALUE um_connect(struct um *machine, int fd, const struct sockaddr *addr, sockle
471
492
  if (um_check_completion(machine, &op))
472
493
  ret = INT2NUM(op.result.res);
473
494
 
495
+ RAISE_IF_EXCEPTION(ret);
474
496
  RB_GC_GUARD(ret);
475
- return raise_if_exception(ret);
497
+ return ret;
476
498
  }
477
499
 
478
500
  VALUE um_send(struct um *machine, int fd, VALUE buffer, int len, int flags) {
@@ -486,8 +508,10 @@ VALUE um_send(struct um *machine, int fd, VALUE buffer, int len, int flags) {
486
508
  ret = INT2NUM(op.result.res);
487
509
 
488
510
  RB_GC_GUARD(buffer);
511
+
512
+ RAISE_IF_EXCEPTION(ret);
489
513
  RB_GC_GUARD(ret);
490
- return raise_if_exception(ret);
514
+ return ret;
491
515
  }
492
516
 
493
517
  VALUE um_send_bundle(struct um *machine, int fd, int bgid, VALUE strings) {
@@ -505,10 +529,8 @@ VALUE um_send_bundle(struct um *machine, int fd, int bgid, VALUE strings) {
505
529
  if (um_check_completion(machine, &op))
506
530
  ret = INT2NUM(op.result.res);
507
531
 
532
+ RAISE_IF_EXCEPTION(ret);
508
533
  RB_GC_GUARD(ret);
509
- return raise_if_exception(ret);
510
-
511
-
512
534
  return ret;
513
535
  }
514
536
 
@@ -526,8 +548,10 @@ VALUE um_recv(struct um *machine, int fd, VALUE buffer, int maxlen, int flags) {
526
548
  }
527
549
 
528
550
  RB_GC_GUARD(buffer);
551
+
552
+ RAISE_IF_EXCEPTION(ret);
529
553
  RB_GC_GUARD(ret);
530
- return raise_if_exception(ret);
554
+ return ret;
531
555
  }
532
556
 
533
557
  VALUE um_bind(struct um *machine, int fd, struct sockaddr *addr, socklen_t addrlen) {
@@ -540,8 +564,9 @@ VALUE um_bind(struct um *machine, int fd, struct sockaddr *addr, socklen_t addrl
540
564
  if (um_check_completion(machine, &op))
541
565
  ret = INT2NUM(op.result.res);
542
566
 
567
+ RAISE_IF_EXCEPTION(ret);
543
568
  RB_GC_GUARD(ret);
544
- return raise_if_exception(ret);
569
+ return ret;
545
570
  }
546
571
 
547
572
  VALUE um_listen(struct um *machine, int fd, int backlog) {
@@ -554,8 +579,9 @@ VALUE um_listen(struct um *machine, int fd, int backlog) {
554
579
  if (um_check_completion(machine, &op))
555
580
  ret = INT2NUM(op.result.res);
556
581
 
582
+ RAISE_IF_EXCEPTION(ret);
557
583
  RB_GC_GUARD(ret);
558
- return raise_if_exception(ret);
584
+ return ret;
559
585
  }
560
586
 
561
587
  VALUE um_getsockopt(struct um *machine, int fd, int level, int opt) {
@@ -579,8 +605,9 @@ VALUE um_getsockopt(struct um *machine, int fd, int level, int opt) {
579
605
  ret = INT2NUM(value);
580
606
  #endif
581
607
 
608
+ RAISE_IF_EXCEPTION(ret);
582
609
  RB_GC_GUARD(ret);
583
- return raise_if_exception(ret);
610
+ return ret;
584
611
  }
585
612
 
586
613
  VALUE um_setsockopt(struct um *machine, int fd, int level, int opt, int value) {
@@ -602,8 +629,9 @@ VALUE um_setsockopt(struct um *machine, int fd, int level, int opt, int value) {
602
629
  ret = INT2NUM(0);
603
630
  #endif
604
631
 
632
+ RAISE_IF_EXCEPTION(ret);
605
633
  RB_GC_GUARD(ret);
606
- return raise_if_exception(ret);
634
+ return ret;
607
635
  }
608
636
 
609
637
  VALUE um_shutdown(struct um *machine, int fd, int how) {
@@ -618,13 +646,17 @@ VALUE um_shutdown(struct um *machine, int fd, int how) {
618
646
  if (um_check_completion(machine, &op))
619
647
  ret = INT2NUM(op.result.res);
620
648
 
649
+ RAISE_IF_EXCEPTION(ret);
621
650
  RB_GC_GUARD(ret);
622
- return raise_if_exception(ret);
651
+ return ret;
623
652
  }
624
653
 
625
654
  VALUE um_shutdown_async(struct um *machine, int fd, int how) {
626
655
  struct um_op *op = um_op_alloc(machine);
627
656
  um_prep_op(machine, op, OP_SHUTDOWN_ASYNC, OP_F_FREE_ON_COMPLETE);
657
+ RB_OBJ_WRITE(machine->self, &op->fiber, Qnil);
658
+ RB_OBJ_WRITE(machine->self, &op->value, Qnil);
659
+ RB_OBJ_WRITE(machine->self, &op->async_op, Qnil);
628
660
 
629
661
  struct io_uring_sqe *sqe = um_get_sqe(machine, op);
630
662
  io_uring_prep_shutdown(sqe, fd, how);
@@ -642,8 +674,24 @@ VALUE um_open(struct um *machine, VALUE pathname, int flags, int mode) {
642
674
  if (um_check_completion(machine, &op))
643
675
  ret = INT2NUM(op.result.res);
644
676
 
677
+ RAISE_IF_EXCEPTION(ret);
678
+ RB_GC_GUARD(ret);
679
+ return ret;
680
+ }
681
+
682
+ VALUE um_poll(struct um *machine, int fd, unsigned mask) {
683
+ struct um_op op;
684
+ um_prep_op(machine, &op, OP_POLL, 0);
685
+ struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
686
+ io_uring_prep_poll_add(sqe, fd, mask);
687
+
688
+ VALUE ret = um_fiber_switch(machine);
689
+ if (um_check_completion(machine, &op))
690
+ ret = INT2NUM(op.result.res);
691
+
692
+ RAISE_IF_EXCEPTION(ret);
645
693
  RB_GC_GUARD(ret);
646
- return raise_if_exception(ret);
694
+ return ret;
647
695
  }
648
696
 
649
697
  VALUE um_waitpid(struct um *machine, int pid, int options) {
@@ -658,8 +706,8 @@ VALUE um_waitpid(struct um *machine, int pid, int options) {
658
706
  if (um_check_completion(machine, &op))
659
707
  ret = INT2NUM(op.result.res);
660
708
 
709
+ RAISE_IF_EXCEPTION(ret);
661
710
  RB_GC_GUARD(ret);
662
- raise_if_exception(ret);
663
711
 
664
712
  return rb_ary_new_from_args(2, INT2NUM(infop.si_pid), INT2NUM(infop.si_status));
665
713
  }
@@ -688,24 +736,23 @@ VALUE statx_to_hash(struct statx *stat) {
688
736
  }
689
737
 
690
738
  VALUE um_statx(struct um *machine, int dirfd, VALUE path, int flags, unsigned int mask) {
739
+ static char empty_path[] = "";
740
+
691
741
  struct um_op op;
692
742
  um_prep_op(machine, &op, OP_STATX, 0);
693
743
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
694
744
 
745
+ char *path_ptr = NIL_P(path) ? empty_path : StringValueCStr(path);
695
746
  struct statx stat;
696
747
  memset(&stat, 0, sizeof(stat));
697
-
698
- if (NIL_P(path))
699
- path = rb_str_new_literal("");
700
-
701
- io_uring_prep_statx(sqe, dirfd, StringValueCStr(path), flags, mask, &stat);
748
+ io_uring_prep_statx(sqe, dirfd, path_ptr, flags, mask, &stat);
702
749
 
703
750
  VALUE ret = um_fiber_switch(machine);
704
751
  if (um_check_completion(machine, &op))
705
752
  ret = INT2NUM(op.result.res);
706
753
 
754
+ RAISE_IF_EXCEPTION(ret);
707
755
  RB_GC_GUARD(ret);
708
- raise_if_exception(ret);
709
756
 
710
757
  return statx_to_hash(&stat);
711
758
  }
@@ -721,8 +768,11 @@ VALUE accept_each_start(VALUE arg) {
721
768
 
722
769
  while (true) {
723
770
  VALUE ret = um_fiber_switch(ctx->machine);
724
- if (!um_op_completed_p(ctx->op))
725
- return raise_if_exception(ret);
771
+ if (!um_op_completed_p(ctx->op)) {
772
+ RAISE_IF_EXCEPTION(ret);
773
+ return ret;
774
+ }
775
+ RB_GC_GUARD(ret);
726
776
 
727
777
  int more = false;
728
778
  struct um_op_result *result = &ctx->op->result;
@@ -791,8 +841,11 @@ int um_read_each_singleshot_loop(struct op_ctx *ctx) {
791
841
  rb_yield(buf);
792
842
  RB_GC_GUARD(buf);
793
843
  }
794
- else
795
- return raise_if_exception(ret);
844
+ else {
845
+ RAISE_IF_EXCEPTION(ret);
846
+ return ret;
847
+ }
848
+ RB_GC_GUARD(ret);
796
849
  }
797
850
  }
798
851
 
@@ -840,8 +893,11 @@ VALUE read_recv_each_start(VALUE arg) {
840
893
 
841
894
  while (true) {
842
895
  VALUE ret = um_fiber_switch(ctx->machine);
843
- if (!um_op_completed_p(ctx->op))
844
- return raise_if_exception(ret);
896
+ if (!um_op_completed_p(ctx->op)) {
897
+ RAISE_IF_EXCEPTION(ret);
898
+ return ret;
899
+ }
900
+ RB_GC_GUARD(ret);
845
901
 
846
902
  int more = false;
847
903
  struct um_op_result *result = &ctx->op->result;
@@ -888,8 +944,11 @@ VALUE periodically_start(VALUE arg) {
888
944
 
889
945
  while (true) {
890
946
  VALUE ret = um_fiber_switch(ctx->machine);
891
- if (!um_op_completed_p(ctx->op))
892
- return raise_if_exception(ret);
947
+ if (!um_op_completed_p(ctx->op)) {
948
+ RAISE_IF_EXCEPTION(ret);
949
+ return ret;
950
+ }
951
+ RB_GC_GUARD(ret);
893
952
 
894
953
  int more = false;
895
954
  struct um_op_result *result = &ctx->op->result;
data/ext/um/um.h CHANGED
@@ -45,7 +45,8 @@ enum op_kind {
45
45
  OP_SETSOCKOPT,
46
46
  OP_SHUTDOWN,
47
47
  OP_SHUTDOWN_ASYNC,
48
-
48
+
49
+ OP_POLL,
49
50
  OP_WAITPID,
50
51
 
51
52
  OP_FUTEX_WAIT,
@@ -203,7 +204,7 @@ double um_timestamp_to_double(__s64 tv_sec, __u32 tv_nsec);
203
204
  int um_value_is_exception_p(VALUE v);
204
205
  VALUE um_raise_exception(VALUE v);
205
206
 
206
- #define raise_if_exception(v) (um_value_is_exception_p(v) ? um_raise_exception(v) : v)
207
+ #define RAISE_IF_EXCEPTION(v) if (um_value_is_exception_p(v)) { um_raise_exception(v); }
207
208
 
208
209
  void um_prep_op(struct um *machine, struct um_op *op, enum op_kind kind, unsigned flags);
209
210
  void um_raise_on_error_result(int result);
@@ -235,6 +236,7 @@ VALUE um_write(struct um *machine, int fd, VALUE str, int len);
235
236
  VALUE um_close(struct um *machine, int fd);
236
237
  VALUE um_close_async(struct um *machine, int fd);
237
238
  VALUE um_open(struct um *machine, VALUE pathname, int flags, int mode);
239
+ VALUE um_poll(struct um *machine, int fd, unsigned mask);
238
240
  VALUE um_waitpid(struct um *machine, int pid, int options);
239
241
  VALUE um_statx(struct um *machine, int dirfd, VALUE path, int flags, unsigned int mask);
240
242
  VALUE um_write_async(struct um *machine, int fd, VALUE str);
data/ext/um/um_async_op.c CHANGED
@@ -30,7 +30,8 @@ VALUE um_async_op_await(struct um_async_op *async_op) {
30
30
  if (!um_op_completed_p(async_op->op))
31
31
  um_cancel_and_wait(async_op->machine, async_op->op);
32
32
 
33
- raise_if_exception(ret);
33
+ RAISE_IF_EXCEPTION(ret);
34
+ RB_GC_GUARD(ret);
34
35
  return INT2NUM(async_op->op->result.res);
35
36
  }
36
37
 
data/ext/um/um_class.c CHANGED
@@ -327,6 +327,11 @@ VALUE UM_open(VALUE self, VALUE pathname, VALUE flags) {
327
327
  return fd;
328
328
  }
329
329
 
330
+ VALUE UM_poll(VALUE self, VALUE fd, VALUE mask) {
331
+ struct um *machine = um_get_machine(self);
332
+ return um_poll(machine, NUM2INT(fd), NUM2UINT(mask));
333
+ }
334
+
330
335
  VALUE UM_waitpid(VALUE self, VALUE pid, VALUE options) {
331
336
  struct um *machine = um_get_machine(self);
332
337
  return um_waitpid(machine, NUM2INT(pid), NUM2INT(options));
@@ -382,6 +387,7 @@ void Init_UM(void) {
382
387
  rb_define_method(cUM, "write_async", UM_write_async, 2);
383
388
  rb_define_method(cUM, "statx", UM_statx, 4);
384
389
 
390
+ rb_define_method(cUM, "poll", UM_poll, 2);
385
391
  rb_define_method(cUM, "waitpid", UM_waitpid, 2);
386
392
 
387
393
  rb_define_method(cUM, "accept", UM_accept, 1);
data/ext/um/um_const.c CHANGED
@@ -12,7 +12,7 @@
12
12
  #include <netinet/udp.h>
13
13
  #include <netdb.h>
14
14
  #include <net/if.h>
15
-
15
+ #include <poll.h>
16
16
 
17
17
  #define DEF_CONST_INT(mod, v) rb_define_const(mod, #v, INT2NUM(v))
18
18
 
@@ -258,6 +258,10 @@ void um_define_net_constants(VALUE mod) {
258
258
 
259
259
  DEF_CONST_INT(mod, SOMAXCONN);
260
260
 
261
+ DEF_CONST_INT(mod, POLLIN);
262
+ DEF_CONST_INT(mod, POLLOUT);
263
+ DEF_CONST_INT(mod, POLLERR);
264
+
261
265
  DEF_CONST_INT(mod, EPERM);
262
266
  DEF_CONST_INT(mod, ENOENT);
263
267
  DEF_CONST_INT(mod, ESRCH);
@@ -387,5 +391,4 @@ void um_define_net_constants(VALUE mod) {
387
391
  DEF_CONST_INT(mod, EKEYREJECTED);
388
392
  DEF_CONST_INT(mod, EOWNERDEAD);
389
393
  DEF_CONST_INT(mod, ENOTRECOVERABLE);
390
-
391
394
  }
data/ext/um/um_stream.c CHANGED
@@ -26,7 +26,7 @@ int stream_read_more(struct um_stream *stream) {
26
26
  size_t capa = rb_str_capacity(stream->buffer);
27
27
  if (capa - stream->pos < maxlen)
28
28
  rb_str_modify_expand(stream->buffer, maxlen - (capa - stream->pos));
29
-
29
+
30
30
  rb_str_modify(stream->buffer);
31
31
  char *ptr = RSTRING_PTR(stream->buffer) + stream->pos;
32
32
  size_t ret = um_read_raw(stream->machine, stream->fd, ptr, maxlen);
@@ -94,7 +94,7 @@ VALUE stream_get_string(struct um_stream *stream, VALUE buf, ssize_t len) {
94
94
 
95
95
  abslen = stream->len - stream->pos;
96
96
  }
97
-
97
+
98
98
  char *start = RSTRING_PTR(stream->buffer) + stream->pos;
99
99
  stream->pos += abslen;
100
100
 
@@ -137,7 +137,7 @@ VALUE resp_get_string(struct um_stream *stream, ulong len, VALUE out_buffer) {
137
137
 
138
138
  while (stream->len - stream->pos < read_len)
139
139
  if (!stream_read_more(stream)) return Qnil;
140
-
140
+
141
141
  char *start = RSTRING_PTR(stream->buffer) + stream->pos;
142
142
  stream->pos += read_len;
143
143
 
@@ -198,7 +198,7 @@ static inline VALUE resp_decode_string_with_encoding(struct um_stream *stream, V
198
198
 
199
199
  static inline VALUE resp_decode_integer(char *ptr) {
200
200
  long value = strtol(ptr + 1, NULL, 10);
201
- return LONG2NUM(value);
201
+ return LONG2NUM(value);
202
202
  }
203
203
 
204
204
  static inline VALUE resp_decode_float(char *ptr) {
@@ -234,13 +234,13 @@ VALUE resp_decode(struct um_stream *stream, VALUE out_buffer) {
234
234
  ulong len = RSTRING_LEN(msg);
235
235
  ulong data_len;
236
236
  if (len == 0) return Qnil;
237
-
237
+
238
238
  switch (ptr[0]) {
239
239
  case '%': // hash
240
240
  case '|': // attributes hash
241
241
  data_len = resp_parse_length_field(ptr, len);
242
242
  return resp_decode_hash(stream, out_buffer, data_len);
243
-
243
+
244
244
  case '*': // array
245
245
  case '~': // set
246
246
  case '>': // pub/sub push
@@ -276,7 +276,7 @@ VALUE resp_decode(struct um_stream *stream, VALUE out_buffer) {
276
276
  default:
277
277
  rb_raise(rb_eRuntimeError, "Invalid character encountered");
278
278
  }
279
-
279
+
280
280
  RB_GC_GUARD(msg);
281
281
  }
282
282
 
@@ -284,7 +284,7 @@ void write_buffer_init(struct um_write_buffer *buf, VALUE str) {
284
284
  size_t capa = 1 << 12;
285
285
  size_t len = RSTRING_LEN(str);
286
286
  while (capa < len) capa += 1 << 12;
287
-
287
+
288
288
  rb_str_resize(str, capa);
289
289
  rb_str_set_len(str, len);
290
290
  buf->str = str;
@@ -88,7 +88,7 @@ void Init_Stream(void) {
88
88
  rb_define_alloc_func(cStream, Stream_allocate);
89
89
 
90
90
  rb_define_method(cStream, "initialize", Stream_initialize, 2);
91
-
91
+
92
92
  rb_define_method(cStream, "get_line", Stream_get_line, 2);
93
93
  rb_define_method(cStream, "get_string", Stream_get_string, 2);
94
94
 
data/ext/um/um_sync.c CHANGED
@@ -21,8 +21,8 @@ void um_futex_wait(struct um *machine, uint32_t *futex, uint32_t expect) {
21
21
  um_raise_on_error_result(op.result.res);
22
22
  }
23
23
 
24
+ RAISE_IF_EXCEPTION(ret);
24
25
  RB_GC_GUARD(ret);
25
- raise_if_exception(ret);
26
26
  }
27
27
 
28
28
  void um_futex_wake(struct um *machine, uint32_t *futex, uint32_t num_waiters) {
@@ -38,8 +38,8 @@ void um_futex_wake(struct um *machine, uint32_t *futex, uint32_t num_waiters) {
38
38
  VALUE ret = um_fiber_switch(machine);
39
39
  um_check_completion(machine, &op);
40
40
 
41
+ RAISE_IF_EXCEPTION(ret);
41
42
  RB_GC_GUARD(ret);
42
- raise_if_exception(ret);
43
43
  }
44
44
 
45
45
  void um_futex_wake_transient(struct um *machine, uint32_t *futex, uint32_t num_waiters) {
data/ext/um/um_utils.c CHANGED
@@ -129,7 +129,7 @@ inline void um_add_strings_to_buffer_ring(struct um *machine, int bgid, VALUE st
129
129
  ulong count = RARRAY_LEN(strings);
130
130
  VALUE str = Qnil;
131
131
  VALUE converted = Qnil;
132
-
132
+
133
133
  for (ulong i = 0; i < count; i++) {
134
134
  str = rb_ary_entry(strings, i);
135
135
  if (TYPE(str) != T_STRING) {
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UringMachine
4
+ class FiberScheduler
5
+ def initialize(machine)
6
+ @machine = machine
7
+ end
8
+
9
+ def p(o)
10
+ @machine.write(1, "#{o.inspect}\n")
11
+ rescue Errno::EINTR
12
+ retry
13
+ end
14
+
15
+ def join(*)
16
+ @machine.join(*)
17
+ end
18
+
19
+ def block(blocker, timeout)
20
+ p block: [blocker, timeout]
21
+
22
+ end
23
+
24
+ def unblock(blocker, fiber)
25
+ p unblock: [blocker, fiber]
26
+ end
27
+
28
+ def kernel_sleep(duration = nil)
29
+ # p sleep: [duration]
30
+ if duration
31
+ @machine.sleep(duration)
32
+ else
33
+ @machine.yield
34
+ end
35
+ end
36
+
37
+ def io_wait(io, events, timeout = nil)
38
+ timeout ||= io.timeout
39
+ p timeout: timeout
40
+ if timeout
41
+ p 1
42
+ @machine.timeout(timeout, Timeout::Error) {
43
+ p 2
44
+ @machine.poll(io.fileno, events).tap { p 3 }
45
+ }.tap { p 4 }
46
+ else
47
+ p 5
48
+ @machine.poll(io.fileno, events).tap { p 6 }
49
+
50
+ end
51
+ rescue => e
52
+ p e: e
53
+ raise
54
+ end
55
+
56
+ def fiber(&block)
57
+ f = @machine.spin(&block)
58
+ @machine.snooze
59
+ f
60
+ end
61
+
62
+ def io_write(io, buffer, length, offset)
63
+ p io_write: [io, buffer.get_string, length, offset]
64
+ @machine.write(io.fileno, buffer.get_string)
65
+ end
66
+
67
+ def io_read(io, buffer, length, offset)
68
+ # p io_read: [io, buffer, length, offset]
69
+ s = +''
70
+ length = buffer.size if length == 0
71
+ bytes = @machine.read(io.fileno, s, length)
72
+ buffer.set_string(s)
73
+ bytes
74
+ rescue SystemCallError => e
75
+ -e.errno
76
+ end
77
+
78
+ def io_pwrite(io, buffer, from, length, offset)
79
+ p io_pwrite: [io, buffer, from, length, offset]
80
+ end
81
+
82
+ def io_pread(io, buffer, from, length, offset)
83
+ p io_pread: [io, buffer, from, length, offset]
84
+ end
85
+
86
+ # def fiber(&block)
87
+ # fiber = Fiber.new(blocking: false, &block)
88
+ # unblock(nil, fiber)
89
+ # # fiber.resume
90
+ # return fiber
91
+ # end
92
+
93
+ # def kernel_sleep(duration = nil)
94
+ # block(:sleep, duration)
95
+ # end
96
+
97
+ # def process_wait(pid, flags)
98
+ # # This is a very simple way to implement a non-blocking wait:
99
+ # Thread.new do
100
+ # Process::Status.wait(pid, flags)
101
+ # end.value
102
+ # end
103
+ end
104
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UringMachine
4
- VERSION = '0.18'
4
+ VERSION = '0.19.1'
5
5
  end
data/test/test_stream.rb CHANGED
@@ -7,7 +7,7 @@ class StreamBaseTest < UMBaseTest
7
7
  super
8
8
  @rfd, @wfd = UM.pipe
9
9
  @stream = UM::Stream.new(@machine, @rfd)
10
- end
10
+ end
11
11
  end
12
12
 
13
13
  class StreamTest < StreamBaseTest
@@ -166,7 +166,7 @@ class StreamRespTest < StreamBaseTest
166
166
  end
167
167
 
168
168
  def test_resp_encode
169
- s = UM::Stream
169
+ s = UM::Stream
170
170
  assert_equal "_\r\n", s.resp_encode(+'', nil)
171
171
  assert_equal "#t\r\n", s.resp_encode(+'', true)
172
172
  assert_equal "#f\r\n", s.resp_encode(+'', false)
@@ -174,7 +174,7 @@ class StreamRespTest < StreamBaseTest
174
174
  assert_equal ",42.1\r\n", s.resp_encode(+'', 42.1)
175
175
  assert_equal "$6\r\nfoobar\r\n", s.resp_encode(+'', 'foobar')
176
176
  assert_equal "$10\r\nפובאר\r\n", s.resp_encode(+'', 'פובאר')
177
-
177
+
178
178
  assert_equal "*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",
179
179
  s.resp_encode(+'', ['foo', 'bar'])
180
180
 
data/test/test_um.rb CHANGED
@@ -142,6 +142,31 @@ class ScheduleTest < UMBaseTest
142
142
  assert_kind_of TOError, e
143
143
  end
144
144
 
145
+ def test_timeout_stress
146
+ skip
147
+ # GC.stress = true
148
+ c = 0
149
+ fs = 100.times.map {
150
+ machine.spin {
151
+ q = UM::Queue.new
152
+ 1000.times {
153
+ machine.sleep rand(0.001..0.005)
154
+ begin
155
+ machine.timeout(rand(0.001..0.06), TOError) do
156
+ machine.shift(q)
157
+ end
158
+ rescue => _e
159
+ c += 1
160
+ STDOUT << '*' if c % 1000 == 0
161
+ end
162
+ }
163
+ }
164
+ }
165
+ machine.join(*fs)
166
+ ensure
167
+ GC.stress = false
168
+ end
169
+
145
170
  def test_timeout_with_raising_block
146
171
  e = nil
147
172
  begin
@@ -169,6 +194,17 @@ class ScheduleTest < UMBaseTest
169
194
  assert_equal 0, machine.pending_count
170
195
  end
171
196
 
197
+ def test_timeout_with_no_timeout
198
+ _r, w = UM.pipe
199
+ v = machine.timeout(0.1, TOError) { machine.write(w, 'foo') }
200
+
201
+ assert_equal 3, v
202
+
203
+ assert_equal 1, machine.pending_count
204
+ machine.sleep 0.01 # wait for cancelled CQE
205
+ assert_equal 0, machine.pending_count
206
+ end
207
+
172
208
  class TO2Error < RuntimeError; end
173
209
  class TO3Error < RuntimeError; end
174
210
 
@@ -1299,6 +1335,37 @@ class PipeTest < UMBaseTest
1299
1335
  end
1300
1336
  end
1301
1337
 
1338
+ class PollTest < UMBaseTest
1339
+ def test_poll
1340
+ rfd, wfd = UM.pipe
1341
+
1342
+ events = []
1343
+ f1 = machine.spin do
1344
+ events << :pre
1345
+ events << machine.poll(rfd, UM::POLLIN)
1346
+ events << :post
1347
+ end
1348
+
1349
+ machine.snooze
1350
+ assert_equal [:pre], events
1351
+
1352
+ machine.write(wfd, 'foo')
1353
+ machine.snooze
1354
+ assert_equal [:pre, UM::POLLIN, :post], events
1355
+
1356
+ ret = machine.poll(wfd, UM::POLLOUT)
1357
+ assert_equal UM::POLLOUT, ret
1358
+
1359
+ machine.close(rfd)
1360
+ ret = machine.poll(wfd, UM::POLLOUT | UM::POLLERR)
1361
+ assert_equal UM::POLLOUT | UM::POLLERR, ret
1362
+ end
1363
+
1364
+ def test_poll_bad_fd
1365
+ assert_raises(Errno::EBADF) { machine.poll(9876, POLLIN) }
1366
+ end
1367
+ end
1368
+
1302
1369
  class WaitTest < UMBaseTest
1303
1370
  def test_waitpid
1304
1371
  skip if UM.kernel_version < 607
@@ -1396,6 +1463,17 @@ class StatxTest < UMBaseTest
1396
1463
  io.close
1397
1464
  end
1398
1465
 
1466
+ def test_statx_mask
1467
+ fd = @machine.open(__FILE__, UM::O_RDONLY)
1468
+ ustat = machine.statx(fd, nil, UM::AT_EMPTY_PATH, UM::STATX_MTIME | UM::STATX_SIZE)
1469
+ rstat = File.stat(__FILE__)
1470
+
1471
+ assert_equal rstat.size, ustat[:size]
1472
+ assert_equal rstat.mtime.to_i, ustat[:mtime].to_i
1473
+ ensure
1474
+ @machine.close_async(fd)
1475
+ end
1476
+
1399
1477
  def test_statx_bad_path
1400
1478
  assert_raises(Errno::ENOENT) { machine.statx(UM::AT_FDCWD, 'foobar', 0, UM::STATX_ALL) }
1401
1479
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uringmachine
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.18'
4
+ version: 0.19.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -92,6 +92,7 @@ files:
92
92
  - examples/bm_write.rb
93
93
  - examples/dns_client.rb
94
94
  - examples/echo_server.rb
95
+ - examples/fiber_scheduler_demo.rb
95
96
  - examples/http_server.rb
96
97
  - examples/inout.rb
97
98
  - examples/nc.rb
@@ -119,6 +120,7 @@ files:
119
120
  - lib/uringmachine.rb
120
121
  - lib/uringmachine/actor.rb
121
122
  - lib/uringmachine/dns_resolver.rb
123
+ - lib/uringmachine/fiber_scheduler.rb
122
124
  - lib/uringmachine/version.rb
123
125
  - supressions/ruby.supp
124
126
  - test/helper.rb
@@ -673,7 +675,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
673
675
  - !ruby/object:Gem::Version
674
676
  version: '0'
675
677
  requirements: []
676
- rubygems_version: 3.6.9
678
+ rubygems_version: 3.7.0.dev
677
679
  specification_version: 4
678
680
  summary: A lean, mean io_uring machine
679
681
  test_files: []