polyphony 0.57.0 → 0.60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/Gemfile.lock +15 -29
  4. data/examples/core/message_based_supervision.rb +51 -0
  5. data/ext/polyphony/backend_common.c +108 -3
  6. data/ext/polyphony/backend_common.h +23 -0
  7. data/ext/polyphony/backend_io_uring.c +117 -39
  8. data/ext/polyphony/backend_io_uring_context.c +11 -3
  9. data/ext/polyphony/backend_io_uring_context.h +5 -3
  10. data/ext/polyphony/backend_libev.c +92 -30
  11. data/ext/polyphony/extconf.rb +2 -2
  12. data/ext/polyphony/fiber.c +1 -34
  13. data/ext/polyphony/polyphony.c +12 -19
  14. data/ext/polyphony/polyphony.h +10 -20
  15. data/ext/polyphony/polyphony_ext.c +0 -4
  16. data/ext/polyphony/queue.c +12 -12
  17. data/ext/polyphony/runqueue.c +17 -85
  18. data/ext/polyphony/runqueue.h +27 -0
  19. data/ext/polyphony/thread.c +10 -99
  20. data/lib/polyphony/core/timer.rb +2 -2
  21. data/lib/polyphony/extensions/fiber.rb +102 -82
  22. data/lib/polyphony/extensions/io.rb +10 -9
  23. data/lib/polyphony/extensions/openssl.rb +14 -4
  24. data/lib/polyphony/extensions/socket.rb +15 -15
  25. data/lib/polyphony/extensions/thread.rb +8 -0
  26. data/lib/polyphony/version.rb +1 -1
  27. data/polyphony.gemspec +0 -7
  28. data/test/test_backend.rb +71 -5
  29. data/test/test_ext.rb +1 -1
  30. data/test/test_fiber.rb +106 -18
  31. data/test/test_global_api.rb +1 -1
  32. data/test/test_io.rb +29 -0
  33. data/test/test_supervise.rb +100 -100
  34. data/test/test_thread.rb +57 -11
  35. data/test/test_thread_pool.rb +1 -1
  36. data/test/test_trace.rb +28 -49
  37. metadata +4 -108
  38. data/ext/polyphony/tracing.c +0 -11
  39. data/lib/polyphony/adapters/trace.rb +0 -138
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e350937221c476548465a2881e64689042137eab59571a0940f63236a249563f
4
- data.tar.gz: 14870d9e38e7aa9f300d09cdfa0693929edb59756e0d17e7b3e7ae2d4a9dab0d
3
+ metadata.gz: 515e9a5686bb0eedb02ad626e491b3e8acb350a765a468029e0b5357673f443c
4
+ data.tar.gz: 94fd7eaedd37c01f1ebc33ba78ffb00d3e8fc42f4a0266bf63ba8eedb83d008c
5
5
  SHA512:
6
- metadata.gz: 8087b84c7a583c8f3905c20d06aa4ad119fd2901be2594e66f56b577c1fa141f9e203971aedd27ad7c57b9c184adad1b97d0fbe4024702bfe3ef6bdaa861ac92
7
- data.tar.gz: 6943231ef2b29dac3e33cfee865dbc6b3b35549c62e04dd1ed08d40fbd90a5c179417baf3b27b965e80c3ee7e137cbe167be9e8187dff46d89892978beb8a40d
6
+ metadata.gz: a546bcf43f556dc7d6bbc3c04b9448141a539e91d2d893441a161eecf9246ff516a54cf94cae476ef9358470e5475c98577c807fd1cef1f712f342337d3c8cc8
7
+ data.tar.gz: e0bb07cc0028c3205f3c2d87a59699e35e3d4d0e797eff63258892475548086125ed40e63152c56253a1c596b680eacc7080bf08f7d152576c34dc57df0206ab
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ ## 0.60 2021-07-15
2
+
3
+
4
+ - Fix linux version detection (for kernel version > 5.9)
5
+ - Fix op ctx leak in io_uring backend (when polling for I/O readiness)
6
+ - Add support for appending to buffer in `Backend#read`, `Backend#recv` methods
7
+ - Improve anti-event starvation mechanism
8
+ - Redesign fiber monitoring mechanism
9
+ - Implement `Fiber#attach`
10
+ - Add optional maxlen argument to `IO#read_loop`, `Socket#recv_loop` (#60)
11
+ - Implement `Fiber#detach` (#52)
12
+
13
+ ## 0.59.1 2021-06-28
14
+
15
+ - Accept fiber tag in `Polyphony::Timer.new`
16
+
17
+ ## 0.59 2021-06-28
18
+
19
+ - Redesign tracing mechanism and API - now completely separated from Ruby core
20
+ trace API
21
+ - Refactor C code - move run queue into backend
22
+
23
+ ## 0.58 2021-06-25
24
+
25
+ - Implement `Thread#idle_gc_period`, `#on_idle` (#56)
26
+ - Implement `Backend#idle_block=` (#56)
27
+
1
28
  ## 0.57.0 2021-06-23
2
29
 
3
30
  - Implement `Backend#splice_chunks` method for both libev and io_uring backends
data/Gemfile.lock CHANGED
@@ -1,27 +1,25 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.57.0)
4
+ polyphony (0.59.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  ansi (1.5.0)
10
- ast (2.4.0)
10
+ ast (2.4.2)
11
11
  builder (3.2.4)
12
12
  coderay (1.1.3)
13
- docile (1.3.2)
14
- hiredis (0.6.3)
15
- http_parser.rb (0.6.0)
13
+ docile (1.4.0)
16
14
  httparty (0.17.1)
17
15
  mime-types (~> 3.0)
18
16
  multi_xml (>= 0.5.2)
19
- json (2.3.0)
17
+ json (2.5.1)
20
18
  localhost (1.1.8)
21
19
  method_source (1.0.0)
22
20
  mime-types (3.3.1)
23
21
  mime-types-data (~> 3.2015)
24
- mime-types-data (3.2020.0512)
22
+ mime-types-data (3.2021.0704)
25
23
  minitest (5.14.4)
26
24
  minitest-reporters (1.4.2)
27
25
  ansi
@@ -30,22 +28,18 @@ GEM
30
28
  ruby-progressbar
31
29
  msgpack (1.4.2)
32
30
  multi_xml (0.6.0)
33
- mysql2 (0.5.3)
34
- parallel (1.19.1)
35
- parser (2.7.0.2)
36
- ast (~> 2.4.0)
37
- pg (1.1.4)
31
+ parallel (1.20.1)
32
+ parser (3.0.2.0)
33
+ ast (~> 2.4.1)
38
34
  pry (0.13.1)
39
35
  coderay (~> 1.1)
40
36
  method_source (~> 1.0)
41
- rack (2.2.3)
42
37
  rainbow (3.0.0)
43
- rake (13.0.3)
38
+ rake (13.0.6)
44
39
  rake-compiler (1.1.1)
45
40
  rake
46
- redis (4.1.0)
47
- regexp_parser (1.7.1)
48
- rexml (3.2.4)
41
+ regexp_parser (2.1.1)
42
+ rexml (3.2.5)
49
43
  rubocop (0.85.1)
50
44
  parallel (~> 1.10)
51
45
  parser (>= 2.7.0.1)
@@ -55,37 +49,29 @@ GEM
55
49
  rubocop-ast (>= 0.0.3)
56
50
  ruby-progressbar (~> 1.7)
57
51
  unicode-display_width (>= 1.4.0, < 2.0)
58
- rubocop-ast (0.0.3)
59
- parser (>= 2.7.0.1)
60
- ruby-progressbar (1.10.1)
61
- sequel (5.34.0)
52
+ rubocop-ast (1.8.0)
53
+ parser (>= 3.0.1.1)
54
+ ruby-progressbar (1.11.0)
62
55
  simplecov (0.17.1)
63
56
  docile (~> 1.1)
64
57
  json (>= 1.8, < 3)
65
58
  simplecov-html (~> 0.10.0)
66
59
  simplecov-html (0.10.2)
67
- unicode-display_width (1.6.1)
60
+ unicode-display_width (1.7.0)
68
61
 
69
62
  PLATFORMS
70
63
  ruby
71
64
 
72
65
  DEPENDENCIES
73
- hiredis (= 0.6.3)
74
- http_parser.rb (~> 0.6.0)
75
66
  httparty (= 0.17.1)
76
67
  localhost (~> 1.1.4)
77
68
  minitest (= 5.14.4)
78
69
  minitest-reporters (= 1.4.2)
79
70
  msgpack (= 1.4.2)
80
- mysql2 (= 0.5.3)
81
- pg (= 1.1.4)
82
71
  polyphony!
83
72
  pry (= 0.13.1)
84
- rack (>= 2.0.8, < 2.3.0)
85
73
  rake-compiler (= 1.1.1)
86
- redis (= 4.1.0)
87
74
  rubocop (= 0.85.1)
88
- sequel (= 5.34.0)
89
75
  simplecov (= 0.17.1)
90
76
 
91
77
  BUNDLED WITH
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ class Supervisor
7
+ def initialize(*fibers)
8
+ @fiber = spin { do_supervise }
9
+ @fiber.message_on_child_termination = true
10
+ fibers.each { |f| add(f) }
11
+ end
12
+
13
+ def await
14
+ @fiber.await
15
+ end
16
+
17
+ def spin(tag = nil, &block)
18
+ @fiber.spin(tag, &block)
19
+ end
20
+
21
+ def add(fiber)
22
+ fiber.attach(@fiber)
23
+ end
24
+
25
+ def do_supervise
26
+ loop do
27
+ msg = receive
28
+ # puts "Supervisor received #{msg.inspect}"
29
+ f, r = msg
30
+ puts "Fiber #{f.tag} terminated with #{r.inspect}, restarting..."
31
+ f.restart
32
+ end
33
+ end
34
+ end
35
+
36
+ def supervise(*fibers)
37
+ supervisor = Supervisor.new(*fibers)
38
+ supervisor.await
39
+ end
40
+
41
+ def start_worker(id)
42
+ spin_loop(:"worker#{id}") do
43
+ duration = rand(0.5..1.0)
44
+ puts "Worker #{id} sleeping for #{duration} seconds"
45
+ sleep duration
46
+ raise 'foo' if rand > 0.7
47
+ break if rand > 0.6
48
+ end
49
+ end
50
+
51
+ supervise(start_worker(1), start_worker(2))
@@ -5,6 +5,107 @@
5
5
  #include "polyphony.h"
6
6
  #include "backend_common.h"
7
7
 
8
+ inline void backend_base_initialize(struct Backend_base *base) {
9
+ runqueue_initialize(&base->runqueue);
10
+ base->currently_polling = 0;
11
+ base->pending_count = 0;
12
+ base->idle_gc_period = 0;
13
+ base->idle_gc_last_time = 0;
14
+ base->idle_proc = Qnil;
15
+ base->trace_proc = Qnil;
16
+ }
17
+
18
+ inline void backend_base_finalize(struct Backend_base *base) {
19
+ runqueue_finalize(&base->runqueue);
20
+ }
21
+
22
+ inline void backend_base_mark(struct Backend_base *base) {
23
+ if (base->idle_proc != Qnil) rb_gc_mark(base->idle_proc);
24
+ if (base->trace_proc != Qnil) rb_gc_mark(base->trace_proc);
25
+ runqueue_mark(&base->runqueue);
26
+ }
27
+
28
+ inline void conditional_nonblocking_poll(VALUE backend, struct Backend_base *base, VALUE current, VALUE next) {
29
+ if (runqueue_should_poll_nonblocking(&base->runqueue) || next == current)
30
+ Backend_poll(backend, Qnil);
31
+ }
32
+
33
+ VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base) {
34
+ VALUE current_fiber = rb_fiber_current();
35
+ runqueue_entry next;
36
+ unsigned int pending_ops_count = base->pending_count;
37
+ unsigned int backend_was_polled = 0;
38
+ unsigned int idle_tasks_run_count = 0;
39
+
40
+ COND_TRACE(base, 2, SYM_fiber_switchpoint, current_fiber);
41
+
42
+ while (1) {
43
+ next = runqueue_shift(&base->runqueue);
44
+ if (next.fiber != Qnil) {
45
+ // Polling for I/O op completion is normally done when the run queue is
46
+ // empty, but if the runqueue never empties, we'll never get to process
47
+ // any event completions. In order to prevent this, an anti-starvation
48
+ // mechanism is employed, under the following conditions:
49
+ // - a blocking poll was not yet performed
50
+ // - there are pending blocking operations
51
+ // - the runqueue shift count has reached a fixed threshold (currently 64), or
52
+ // - the next fiber is the same as the current fiber (a single fiber is snoozing)
53
+ if (!backend_was_polled && pending_ops_count)
54
+ conditional_nonblocking_poll(backend, base, current_fiber, next.fiber);
55
+
56
+ break;
57
+ }
58
+
59
+ if (!idle_tasks_run_count) {
60
+ idle_tasks_run_count++;
61
+ backend_run_idle_tasks(base);
62
+ }
63
+ if (pending_ops_count == 0) break;
64
+ Backend_poll(backend, Qtrue);
65
+ backend_was_polled = 1;
66
+ }
67
+
68
+ if (next.fiber == Qnil) return Qnil;
69
+
70
+ // run next fiber
71
+ COND_TRACE(base, 3, SYM_fiber_run, next.fiber, next.value);
72
+
73
+ rb_ivar_set(next.fiber, ID_ivar_runnable, Qnil);
74
+ RB_GC_GUARD(next.fiber);
75
+ RB_GC_GUARD(next.value);
76
+ return (next.fiber == current_fiber) ?
77
+ next.value : FIBER_TRANSFER(next.fiber, next.value);
78
+ }
79
+
80
+ void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_base *base, VALUE fiber, VALUE value, int prioritize) {
81
+ int already_runnable;
82
+
83
+ if (rb_fiber_alive_p(fiber) != Qtrue) return;
84
+ already_runnable = rb_ivar_get(fiber, ID_ivar_runnable) != Qnil;
85
+
86
+ COND_TRACE(base, 4, SYM_fiber_schedule, fiber, value, prioritize ? Qtrue : Qfalse);
87
+
88
+ (prioritize ? runqueue_unshift : runqueue_push)(&base->runqueue, fiber, value, already_runnable);
89
+ if (!already_runnable) {
90
+ rb_ivar_set(fiber, ID_ivar_runnable, Qtrue);
91
+ if (rb_thread_current() != thread) {
92
+ // If the fiber scheduling is done across threads, we need to make sure the
93
+ // target thread is woken up in case it is in the middle of running its
94
+ // event selector. Otherwise it's gonna be stuck waiting for an event to
95
+ // happen, not knowing that it there's already a fiber ready to run in its
96
+ // run queue.
97
+ Backend_wakeup(backend);
98
+ }
99
+ }
100
+ }
101
+
102
+
103
+ inline void backend_trace(struct Backend_base *base, int argc, VALUE *argv) {
104
+ if (base->trace_proc == Qnil) return;
105
+
106
+ rb_funcallv(base->trace_proc, ID_call, argc, argv);
107
+ }
108
+
8
109
  #ifdef POLYPHONY_USE_PIDFD_OPEN
9
110
  #ifndef __NR_pidfd_open
10
111
  #define __NR_pidfd_open 434 /* System call # on most architectures */
@@ -72,7 +173,7 @@ inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
72
173
  //////////////////////////////////////////////////////////////////////
73
174
  //////////////////////////////////////////////////////////////////////
74
175
 
75
- VALUE backend_await(struct Backend_base *backend) {
176
+ inline VALUE backend_await(struct Backend_base *backend) {
76
177
  VALUE ret;
77
178
  backend->pending_count++;
78
179
  ret = Thread_switch_fiber(rb_thread_current());
@@ -81,9 +182,10 @@ VALUE backend_await(struct Backend_base *backend) {
81
182
  return ret;
82
183
  }
83
184
 
84
- VALUE backend_snooze() {
185
+ inline VALUE backend_snooze() {
186
+ VALUE ret;
85
187
  Fiber_make_runnable(rb_fiber_current(), Qnil);
86
- VALUE ret = Thread_switch_fiber(rb_thread_current());
188
+ ret = Thread_switch_fiber(rb_thread_current());
87
189
  return ret;
88
190
  }
89
191
 
@@ -174,6 +276,9 @@ inline void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking) {
174
276
  }
175
277
 
176
278
  inline void backend_run_idle_tasks(struct Backend_base *base) {
279
+ if (base->idle_proc != Qnil)
280
+ rb_funcall(base->idle_proc, ID_call, 0);
281
+
177
282
  if (base->idle_gc_period == 0) return;
178
283
 
179
284
  double now = current_time();
@@ -3,14 +3,37 @@
3
3
 
4
4
  #include "ruby.h"
5
5
  #include "ruby/io.h"
6
+ #include "runqueue.h"
7
+
8
+ struct backend_stats {
9
+ int scheduled_fibers;
10
+ int pending_ops;
11
+ };
6
12
 
7
13
  struct Backend_base {
14
+ runqueue_t runqueue;
8
15
  unsigned int currently_polling;
9
16
  unsigned int pending_count;
10
17
  double idle_gc_period;
11
18
  double idle_gc_last_time;
19
+ VALUE idle_proc;
20
+ VALUE trace_proc;
12
21
  };
13
22
 
23
+ void backend_base_initialize(struct Backend_base *base);
24
+ void backend_base_finalize(struct Backend_base *base);
25
+ void backend_base_mark(struct Backend_base *base);
26
+ VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base);
27
+ void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_base *base, VALUE fiber, VALUE value, int prioritize);
28
+ void backend_trace(struct Backend_base *base, int argc, VALUE *argv);
29
+
30
+ // tracing
31
+ #define SHOULD_TRACE(base) ((base)->trace_proc != Qnil)
32
+ #define TRACE(base, ...) rb_funcall((base)->trace_proc, ID_call, __VA_ARGS__)
33
+ #define COND_TRACE(base, ...) if (SHOULD_TRACE(base)) { TRACE(base, __VA_ARGS__); }
34
+
35
+
36
+
14
37
  #ifdef POLYPHONY_USE_PIDFD_OPEN
15
38
  int pidfd_open(pid_t pid, unsigned int flags);
16
39
  #endif
@@ -42,13 +42,23 @@ typedef struct Backend_t {
42
42
  int event_fd;
43
43
  } Backend_t;
44
44
 
45
+ static void Backend_mark(void *ptr) {
46
+ Backend_t *backend = ptr;
47
+ backend_base_mark(&backend->base);
48
+ }
49
+
50
+ static void Backend_free(void *ptr) {
51
+ Backend_t *backend = ptr;
52
+ backend_base_finalize(&backend->base);
53
+ }
54
+
45
55
  static size_t Backend_size(const void *ptr) {
46
56
  return sizeof(Backend_t);
47
57
  }
48
58
 
49
59
  static const rb_data_type_t Backend_type = {
50
60
  "IOUringBackend",
51
- {0, 0, Backend_size,},
61
+ {Backend_mark, Backend_free, Backend_size,},
52
62
  0, 0, RUBY_TYPED_FREE_IMMEDIATELY
53
63
  };
54
64
 
@@ -65,11 +75,7 @@ static VALUE Backend_initialize(VALUE self) {
65
75
  Backend_t *backend;
66
76
  GetBackend(self, backend);
67
77
 
68
- backend->base.currently_polling = 0;
69
- backend->base.pending_count = 0;
70
- backend->base.idle_gc_period = 0;
71
- backend->base.idle_gc_last_time = 0;
72
-
78
+ backend_base_initialize(&backend->base);
73
79
  backend->pending_sqes = 0;
74
80
  backend->prepared_limit = 2048;
75
81
 
@@ -104,13 +110,6 @@ VALUE Backend_post_fork(VALUE self) {
104
110
  return self;
105
111
  }
106
112
 
107
- unsigned int Backend_pending_count(VALUE self) {
108
- Backend_t *backend;
109
- GetBackend(self, backend);
110
-
111
- return backend->base.pending_count;
112
- }
113
-
114
113
  typedef struct poll_context {
115
114
  struct io_uring *ring;
116
115
  struct io_uring_cqe *cqe;
@@ -134,6 +133,7 @@ static inline void io_uring_backend_handle_completion(struct io_uring_cqe *cqe,
134
133
  op_context_t *ctx = io_uring_cqe_get_data(cqe);
135
134
  if (!ctx) return;
136
135
 
136
+ // printf("cqe ctx %p id: %d result: %d (%s, ref_count: %d)\n", ctx, ctx->id, cqe->res, op_type_to_str(ctx->type), ctx->ref_count);
137
137
  ctx->result = cqe->res;
138
138
  if (ctx->ref_count == 2 && ctx->result != -ECANCELED && ctx->fiber)
139
139
  Fiber_make_runnable(ctx->fiber, ctx->resume_value);
@@ -186,24 +186,64 @@ void io_uring_backend_poll(Backend_t *backend) {
186
186
  io_uring_cqe_seen(&backend->ring, poll_ctx.cqe);
187
187
  }
188
188
 
189
- VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue) {
190
- int is_nowait = nowait == Qtrue;
189
+ inline VALUE Backend_poll(VALUE self, VALUE blocking) {
190
+ int is_blocking = blocking == Qtrue;
191
191
  Backend_t *backend;
192
192
  GetBackend(self, backend);
193
193
 
194
- if (is_nowait && backend->pending_sqes) {
194
+ if (!is_blocking && backend->pending_sqes) {
195
195
  backend->pending_sqes = 0;
196
196
  io_uring_submit(&backend->ring);
197
197
  }
198
198
 
199
- COND_TRACE(2, SYM_fiber_event_poll_enter, current_fiber);
200
- if (!is_nowait) io_uring_backend_poll(backend);
199
+ COND_TRACE(&backend->base, 2, SYM_fiber_event_poll_enter, rb_fiber_current());
200
+ // if (SHOULD_TRACE(&backend->base))
201
+ // printf(
202
+ // "io_uring_poll(blocking_mode: %d, pending: %d, taken: %d, available: %d, runqueue: %d\n",
203
+ // is_blocking,
204
+ // backend->base.pending_count,
205
+ // backend->store.taken_count,
206
+ // backend->store.available_count,
207
+ // backend->base.runqueue.entries.count
208
+ // );
209
+ if (is_blocking) io_uring_backend_poll(backend);
201
210
  io_uring_backend_handle_ready_cqes(backend);
202
- COND_TRACE(2, SYM_fiber_event_poll_leave, current_fiber);
211
+ COND_TRACE(&backend->base, 2, SYM_fiber_event_poll_leave, rb_fiber_current());
203
212
 
204
213
  return self;
205
214
  }
206
215
 
216
+ inline void Backend_schedule_fiber(VALUE thread, VALUE self, VALUE fiber, VALUE value, int prioritize) {
217
+ Backend_t *backend;
218
+ GetBackend(self, backend);
219
+
220
+ backend_base_schedule_fiber(thread, self, &backend->base, fiber, value, prioritize);
221
+ }
222
+
223
+ inline void Backend_unschedule_fiber(VALUE self, VALUE fiber) {
224
+ Backend_t *backend;
225
+ GetBackend(self, backend);
226
+
227
+ runqueue_delete(&backend->base.runqueue, fiber);
228
+ }
229
+
230
+ inline VALUE Backend_switch_fiber(VALUE self) {
231
+ Backend_t *backend;
232
+ GetBackend(self, backend);
233
+
234
+ return backend_base_switch_fiber(self, &backend->base);
235
+ }
236
+
237
+ inline struct backend_stats Backend_stats(VALUE self) {
238
+ Backend_t *backend;
239
+ GetBackend(self, backend);
240
+
241
+ return (struct backend_stats){
242
+ .scheduled_fibers = runqueue_len(&backend->base.runqueue),
243
+ .pending_ops = backend->base.pending_count
244
+ };
245
+ }
246
+
207
247
  VALUE Backend_wakeup(VALUE self) {
208
248
  Backend_t *backend;
209
249
  GetBackend(self, backend);
@@ -270,17 +310,25 @@ VALUE io_uring_backend_wait_fd(Backend_t *backend, int fd, int write) {
270
310
  io_uring_prep_poll_add(sqe, fd, write ? POLLOUT : POLLIN);
271
311
 
272
312
  io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resumed_value);
313
+ context_store_release(&backend->store, ctx);
314
+
273
315
  RB_GC_GUARD(resumed_value);
274
316
  return resumed_value;
275
317
  }
276
318
 
277
- VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
319
+ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof, VALUE pos) {
278
320
  Backend_t *backend;
279
321
  rb_io_t *fptr;
280
322
  long dynamic_len = length == Qnil;
281
323
  long buffer_size = dynamic_len ? 4096 : NUM2INT(length);
282
- int shrinkable = io_setstrbuf(&str, buffer_size);
283
- char *buf = RSTRING_PTR(str);
324
+ long buf_pos = NUM2INT(pos);
325
+ if (str != Qnil) {
326
+ int current_len = RSTRING_LEN(str);
327
+ if (buf_pos < 0 || buf_pos > current_len) buf_pos = current_len;
328
+ }
329
+ else buf_pos = 0;
330
+ int shrinkable = io_setstrbuf(&str, buf_pos + buffer_size);
331
+ char *buf = RSTRING_PTR(str) + buf_pos;
284
332
  long total = 0;
285
333
  int read_to_eof = RTEST(to_eof);
286
334
  VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
@@ -317,9 +365,9 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
317
365
  if (!dynamic_len) break;
318
366
 
319
367
  // resize buffer
320
- rb_str_resize(str, total);
368
+ rb_str_resize(str, buf_pos + total);
321
369
  rb_str_modify_expand(str, buffer_size);
322
- buf = RSTRING_PTR(str) + total;
370
+ buf = RSTRING_PTR(str) + buf_pos + total;
323
371
  shrinkable = 0;
324
372
  buffer_size += buffer_size;
325
373
  }
@@ -327,7 +375,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
327
375
  }
328
376
  }
329
377
 
330
- io_set_read_length(str, total, shrinkable);
378
+ io_set_read_length(str, buf_pos + total, shrinkable);
331
379
  io_enc_str(str, fptr);
332
380
 
333
381
  if (!total) return Qnil;
@@ -335,12 +383,12 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
335
383
  return str;
336
384
  }
337
385
 
338
- VALUE Backend_read_loop(VALUE self, VALUE io) {
386
+ VALUE Backend_read_loop(VALUE self, VALUE io, VALUE maxlen) {
339
387
  Backend_t *backend;
340
388
  rb_io_t *fptr;
341
389
  VALUE str;
342
390
  long total;
343
- long len = 8192;
391
+ long len = NUM2INT(maxlen);
344
392
  int shrinkable;
345
393
  char *buf;
346
394
  VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
@@ -548,13 +596,19 @@ VALUE Backend_write_m(int argc, VALUE *argv, VALUE self) {
548
596
  Backend_writev(self, argv[0], argc - 1, argv + 1);
549
597
  }
550
598
 
551
- VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
599
+ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length, VALUE pos) {
552
600
  Backend_t *backend;
553
601
  rb_io_t *fptr;
554
602
  long dynamic_len = length == Qnil;
555
603
  long len = dynamic_len ? 4096 : NUM2INT(length);
556
- int shrinkable = io_setstrbuf(&str, len);
557
- char *buf = RSTRING_PTR(str);
604
+ long buf_pos = NUM2INT(pos);
605
+ if (str != Qnil) {
606
+ int current_len = RSTRING_LEN(str);
607
+ if (buf_pos < 0 || buf_pos > current_len) buf_pos = current_len;
608
+ }
609
+ else buf_pos = 0;
610
+ int shrinkable = io_setstrbuf(&str, buf_pos + len);
611
+ char *buf = RSTRING_PTR(str) + buf_pos;
558
612
  long total = 0;
559
613
  VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
560
614
 
@@ -586,7 +640,7 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
586
640
  }
587
641
  }
588
642
 
589
- io_set_read_length(str, total, shrinkable);
643
+ io_set_read_length(str, buf_pos + total, shrinkable);
590
644
  io_enc_str(str, fptr);
591
645
 
592
646
  if (!total) return Qnil;
@@ -594,12 +648,12 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
594
648
  return str;
595
649
  }
596
650
 
597
- VALUE Backend_recv_loop(VALUE self, VALUE io) {
651
+ VALUE Backend_recv_loop(VALUE self, VALUE io, VALUE maxlen) {
598
652
  Backend_t *backend;
599
653
  rb_io_t *fptr;
600
654
  VALUE str;
601
655
  long total;
602
- long len = 8192;
656
+ long len = NUM2INT(maxlen);
603
657
  int shrinkable;
604
658
  char *buf;
605
659
  VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
@@ -908,7 +962,6 @@ int io_uring_backend_submit_timeout_and_await(Backend_t *backend, double duratio
908
962
 
909
963
  op_context_t *ctx = context_store_acquire(&backend->store, OP_TIMEOUT);
910
964
  io_uring_prep_timeout(sqe, &ts, 0, 0);
911
-
912
965
  io_uring_backend_defer_submit_and_await(backend, sqe, ctx, resume_value);
913
966
  return context_store_release(&backend->store, ctx);
914
967
  }
@@ -1185,6 +1238,13 @@ VALUE Backend_idle_gc_period_set(VALUE self, VALUE period) {
1185
1238
  return self;
1186
1239
  }
1187
1240
 
1241
+ VALUE Backend_idle_proc_set(VALUE self, VALUE block) {
1242
+ Backend_t *backend;
1243
+ GetBackend(self, backend);
1244
+ backend->base.idle_proc = block;
1245
+ return self;
1246
+ }
1247
+
1188
1248
  inline VALUE Backend_run_idle_tasks(VALUE self) {
1189
1249
  Backend_t *backend;
1190
1250
  GetBackend(self, backend);
@@ -1346,6 +1406,21 @@ error:
1346
1406
  return RAISE_EXCEPTION(switchpoint_result);
1347
1407
  }
1348
1408
 
1409
+ VALUE Backend_trace(int argc, VALUE *argv, VALUE self) {
1410
+ Backend_t *backend;
1411
+ GetBackend(self, backend);
1412
+ backend_trace(&backend->base, argc, argv);
1413
+ return self;
1414
+ }
1415
+
1416
+ VALUE Backend_trace_proc_set(VALUE self, VALUE block) {
1417
+ Backend_t *backend;
1418
+ GetBackend(self, backend);
1419
+
1420
+ backend->base.trace_proc = block;
1421
+ return self;
1422
+ }
1423
+
1349
1424
  void Init_Backend() {
1350
1425
  VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1351
1426
  rb_define_alloc_func(cBackend, Backend_allocate);
@@ -1353,23 +1428,26 @@ void Init_Backend() {
1353
1428
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
1354
1429
  rb_define_method(cBackend, "finalize", Backend_finalize, 0);
1355
1430
  rb_define_method(cBackend, "post_fork", Backend_post_fork, 0);
1431
+ rb_define_method(cBackend, "trace", Backend_trace, -1);
1432
+ rb_define_method(cBackend, "trace_proc=", Backend_trace_proc_set, 1);
1356
1433
 
1357
- rb_define_method(cBackend, "poll", Backend_poll, 3);
1434
+ rb_define_method(cBackend, "poll", Backend_poll, 1);
1358
1435
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
1359
1436
  rb_define_method(cBackend, "kind", Backend_kind, 0);
1360
1437
  rb_define_method(cBackend, "chain", Backend_chain, -1);
1361
1438
  rb_define_method(cBackend, "idle_gc_period=", Backend_idle_gc_period_set, 1);
1439
+ rb_define_method(cBackend, "idle_proc=", Backend_idle_proc_set, 1);
1362
1440
  rb_define_method(cBackend, "splice_chunks", Backend_splice_chunks, 7);
1363
1441
 
1364
1442
  rb_define_method(cBackend, "accept", Backend_accept, 2);
1365
1443
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
1366
1444
  rb_define_method(cBackend, "connect", Backend_connect, 3);
1367
1445
  rb_define_method(cBackend, "feed_loop", Backend_feed_loop, 3);
1368
- rb_define_method(cBackend, "read", Backend_read, 4);
1369
- rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
1370
- rb_define_method(cBackend, "recv", Backend_recv, 3);
1446
+ rb_define_method(cBackend, "read", Backend_read, 5);
1447
+ rb_define_method(cBackend, "read_loop", Backend_read_loop, 2);
1448
+ rb_define_method(cBackend, "recv", Backend_recv, 4);
1371
1449
  rb_define_method(cBackend, "recv_feed_loop", Backend_recv_feed_loop, 3);
1372
- rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 1);
1450
+ rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 2);
1373
1451
  rb_define_method(cBackend, "send", Backend_send, 3);
1374
1452
  rb_define_method(cBackend, "sendv", Backend_sendv, 3);
1375
1453
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);