polyphony 0.59 → 0.62

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: 313e33321df0e375d128a79fccfab98e06183adbdbde7916347f6795f56ca369
4
- data.tar.gz: 6428c0516544bdb955fa465bf36c40b9b2f162306f3e5689848ff487f24c4440
3
+ metadata.gz: 2cea172818871812ebbafcbee0a4b98d130a351f904d7732a02400ef363eac62
4
+ data.tar.gz: f4ef85515436a463d3732f04bbef457426f267fac01f5c5c28619e42334cd745
5
5
  SHA512:
6
- metadata.gz: 40c88f5d1c2aa4307dea78dcf1a2ba8d347ef252e7e56e67d466c291066d39544d051864ccd0138dd94ca0beef7af71f5257052b3556ab0c7294b1b94b517d47
7
- data.tar.gz: 109410d6b222846b0c2b3f54061358ef922b57c9b6559c37aec36f27116e7ca389ff51762e988e2e9293a7a1358d97b80ada74daa4e44d7d25c1e5335351d8c6
6
+ metadata.gz: 489b813f1bb2d7d97f87a60024b0665761571c4227e4fab6c733dcdb62b6ee98760473202c9154eb39a4b544663f759e4da6e70603d483e9f2ec2a64363cd4e1
7
+ data.tar.gz: 05b232d7d67120e0983e60855c928beac6e91271fafc87586da7cc7543128fe326be04d126154ac7e0b0c0b249bf41ca90ab902f79f4a5fbf00cad498061f7e7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## 0.62 2021-07-21
2
+
3
+ - Add `runqueue_size` to backend stats
4
+
5
+ ## 0.61 2021-07-20
6
+
7
+ - Add more statistics, move stats to `Backend#stats`
8
+
9
+ ## 0.60 2021-07-15
10
+
11
+
12
+ - Fix linux version detection (for kernel version > 5.9)
13
+ - Fix op ctx leak in io_uring backend (when polling for I/O readiness)
14
+ - Add support for appending to buffer in `Backend#read`, `Backend#recv` methods
15
+ - Improve anti-event starvation mechanism
16
+ - Redesign fiber monitoring mechanism
17
+ - Implement `Fiber#attach`
18
+ - Add optional maxlen argument to `IO#read_loop`, `Socket#recv_loop` (#60)
19
+ - Implement `Fiber#detach` (#52)
20
+
21
+ ## 0.59.1 2021-06-28
22
+
23
+ - Accept fiber tag in `Polyphony::Timer.new`
24
+
1
25
  ## 0.59 2021-06-28
2
26
 
3
27
  - Redesign tracing mechanism and API - now completely separated from Ruby core
data/Gemfile.lock CHANGED
@@ -1,27 +1,25 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.59)
4
+ polyphony (0.62)
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))
@@ -3,16 +3,25 @@
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
5
 
6
+ spin_loop(interval: 5) { p Thread.backend.stats }
7
+
6
8
  server = TCPServer.open('127.0.0.1', 1234)
7
9
  puts "Pid: #{Process.pid}"
8
10
  puts 'Echoing on port 1234...'
9
- while (client = server.accept)
10
- spin do
11
- while (data = client.gets)
12
- # client.send("you said: #{data.chomp}!\n", 0)
13
- client.write('you said: ', data.chomp, "!\n")
11
+ begin
12
+ while (client = server.accept)
13
+ spin do
14
+ while (data = client.gets)
15
+ # client.send("you said: #{data.chomp}!\n", 0)
16
+ client.write('you said: ', data.chomp, "!\n")
17
+ end
18
+ rescue Errno::ECONNRESET
19
+ 'Connection reset...'
20
+ ensure
21
+ client.shutdown
22
+ client.close
14
23
  end
15
- rescue Errno::ECONNRESET
16
- 'Connection reset...'
17
24
  end
25
+ ensure
26
+ server.close
18
27
  end
@@ -8,6 +8,9 @@
8
8
  inline void backend_base_initialize(struct Backend_base *base) {
9
9
  runqueue_initialize(&base->runqueue);
10
10
  base->currently_polling = 0;
11
+ base->op_count = 0;
12
+ base->switch_count = 0;
13
+ base->poll_count = 0;
11
14
  base->pending_count = 0;
12
15
  base->idle_gc_period = 0;
13
16
  base->idle_gc_last_time = 0;
@@ -25,33 +28,37 @@ inline void backend_base_mark(struct Backend_base *base) {
25
28
  runqueue_mark(&base->runqueue);
26
29
  }
27
30
 
31
+ const unsigned int ANTI_STARVE_SWITCH_COUNT_THRESHOLD = 64;
32
+
33
+ inline void conditional_nonblocking_poll(VALUE backend, struct Backend_base *base, VALUE current, VALUE next) {
34
+ if ((base->switch_count % ANTI_STARVE_SWITCH_COUNT_THRESHOLD) == 0 || next == current)
35
+ Backend_poll(backend, Qnil);
36
+ }
37
+
28
38
  VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base) {
29
39
  VALUE current_fiber = rb_fiber_current();
30
40
  runqueue_entry next;
31
41
  unsigned int pending_ops_count = base->pending_count;
32
42
  unsigned int backend_was_polled = 0;
33
43
  unsigned int idle_tasks_run_count = 0;
34
-
35
- if (SHOULD_TRACE(base) && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
36
- TRACE(base, 2, SYM_fiber_switchpoint, current_fiber);
44
+
45
+ base->switch_count++;
46
+ COND_TRACE(base, 2, SYM_fiber_switchpoint, current_fiber);
37
47
 
38
48
  while (1) {
39
49
  next = runqueue_shift(&base->runqueue);
40
50
  if (next.fiber != Qnil) {
41
51
  // Polling for I/O op completion is normally done when the run queue is
42
52
  // empty, but if the runqueue never empties, we'll never get to process
43
- // any event completions. In order to prevent this, an anti-starve
53
+ // any event completions. In order to prevent this, an anti-starvation
44
54
  // mechanism is employed, under the following conditions:
45
55
  // - a blocking poll was not yet performed
46
56
  // - there are pending blocking operations
47
- // - the runqueue has signalled that a non-blocking poll should be
48
- // performed
49
- // - the run queue length high watermark has reached its threshold (currently 128)
50
- // - the run queue switch counter has reached its threshold (currently 64)
51
- if (!backend_was_polled && pending_ops_count && runqueue_should_poll_nonblocking(&base->runqueue)) {
52
- // this prevents event starvation in case the run queue never empties
53
- Backend_poll(backend, Qnil);
54
- }
57
+ // - the runqueue shift count has reached a fixed threshold (currently 64), or
58
+ // - the next fiber is the same as the current fiber (a single fiber is snoozing)
59
+ if (!backend_was_polled && pending_ops_count)
60
+ conditional_nonblocking_poll(backend, base, current_fiber, next.fiber);
61
+
55
62
  break;
56
63
  }
57
64
 
@@ -82,7 +89,8 @@ void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_bas
82
89
  if (rb_fiber_alive_p(fiber) != Qtrue) return;
83
90
  already_runnable = rb_ivar_get(fiber, ID_ivar_runnable) != Qnil;
84
91
 
85
- COND_TRACE(base, 3, SYM_fiber_schedule, fiber, value);
92
+ COND_TRACE(base, 4, SYM_fiber_schedule, fiber, value, prioritize ? Qtrue : Qfalse);
93
+
86
94
  (prioritize ? runqueue_unshift : runqueue_push)(&base->runqueue, fiber, value, already_runnable);
87
95
  if (!already_runnable) {
88
96
  rb_ivar_set(fiber, ID_ivar_runnable, Qtrue);
@@ -171,7 +179,7 @@ inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
171
179
  //////////////////////////////////////////////////////////////////////
172
180
  //////////////////////////////////////////////////////////////////////
173
181
 
174
- VALUE backend_await(struct Backend_base *backend) {
182
+ inline VALUE backend_await(struct Backend_base *backend) {
175
183
  VALUE ret;
176
184
  backend->pending_count++;
177
185
  ret = Thread_switch_fiber(rb_thread_current());
@@ -180,9 +188,10 @@ VALUE backend_await(struct Backend_base *backend) {
180
188
  return ret;
181
189
  }
182
190
 
183
- VALUE backend_snooze() {
191
+ inline VALUE backend_snooze() {
192
+ VALUE ret;
184
193
  Fiber_make_runnable(rb_fiber_current(), Qnil);
185
- VALUE ret = Thread_switch_fiber(rb_thread_current());
194
+ ret = Thread_switch_fiber(rb_thread_current());
186
195
  return ret;
187
196
  }
188
197
 
@@ -286,3 +295,61 @@ inline void backend_run_idle_tasks(struct Backend_base *base) {
286
295
  rb_gc_start();
287
296
  rb_gc_disable();
288
297
  }
298
+
299
+ inline struct backend_stats backend_base_stats(struct Backend_base *base) {
300
+ struct backend_stats stats = {
301
+ .runqueue_size = runqueue_size(&base->runqueue),
302
+ .runqueue_length = runqueue_len(&base->runqueue),
303
+ .runqueue_max_length = runqueue_max_len(&base->runqueue),
304
+ .op_count = base->op_count,
305
+ .switch_count = base->switch_count,
306
+ .poll_count = base->poll_count,
307
+ .pending_ops = base->pending_count
308
+ };
309
+
310
+ base->op_count = 0;
311
+ base->switch_count = 0;
312
+ base->poll_count = 0;
313
+ return stats;
314
+ }
315
+
316
+ VALUE SYM_runqueue_size;
317
+ VALUE SYM_runqueue_length;
318
+ VALUE SYM_runqueue_max_length;
319
+ VALUE SYM_op_count;
320
+ VALUE SYM_switch_count;
321
+ VALUE SYM_poll_count;
322
+ VALUE SYM_pending_ops;
323
+
324
+ VALUE Backend_stats(VALUE self) {
325
+ struct backend_stats backend_stats = backend_get_stats(self);
326
+
327
+ VALUE stats = rb_hash_new();
328
+ rb_hash_aset(stats, SYM_runqueue_size, INT2NUM(backend_stats.runqueue_size));
329
+ rb_hash_aset(stats, SYM_runqueue_length, INT2NUM(backend_stats.runqueue_length));
330
+ rb_hash_aset(stats, SYM_runqueue_max_length, INT2NUM(backend_stats.runqueue_max_length));
331
+ rb_hash_aset(stats, SYM_op_count, INT2NUM(backend_stats.op_count));
332
+ rb_hash_aset(stats, SYM_switch_count, INT2NUM(backend_stats.switch_count));
333
+ rb_hash_aset(stats, SYM_poll_count, INT2NUM(backend_stats.poll_count));
334
+ rb_hash_aset(stats, SYM_pending_ops, INT2NUM(backend_stats.pending_ops));
335
+ RB_GC_GUARD(stats);
336
+ return stats;
337
+ }
338
+
339
+ void backend_setup_stats_symbols() {
340
+ SYM_runqueue_size = ID2SYM(rb_intern("runqueue_size"));
341
+ SYM_runqueue_length = ID2SYM(rb_intern("runqueue_length"));
342
+ SYM_runqueue_max_length = ID2SYM(rb_intern("runqueue_max_length"));
343
+ SYM_op_count = ID2SYM(rb_intern("op_count"));
344
+ SYM_switch_count = ID2SYM(rb_intern("switch_count"));
345
+ SYM_poll_count = ID2SYM(rb_intern("poll_count"));
346
+ SYM_pending_ops = ID2SYM(rb_intern("pending_ops"));
347
+
348
+ rb_global_variable(&SYM_runqueue_size);
349
+ rb_global_variable(&SYM_runqueue_length);
350
+ rb_global_variable(&SYM_runqueue_max_length);
351
+ rb_global_variable(&SYM_op_count);
352
+ rb_global_variable(&SYM_switch_count);
353
+ rb_global_variable(&SYM_poll_count);
354
+ rb_global_variable(&SYM_pending_ops);
355
+ }
@@ -6,14 +6,21 @@
6
6
  #include "runqueue.h"
7
7
 
8
8
  struct backend_stats {
9
- int scheduled_fibers;
10
- int waiting_fibers;
11
- int pending_ops;
9
+ unsigned int runqueue_size;
10
+ unsigned int runqueue_length;
11
+ unsigned int runqueue_max_length;
12
+ unsigned int op_count;
13
+ unsigned int switch_count;
14
+ unsigned int poll_count;
15
+ unsigned int pending_ops;
12
16
  };
13
17
 
14
18
  struct Backend_base {
15
19
  runqueue_t runqueue;
16
20
  unsigned int currently_polling;
21
+ unsigned int op_count;
22
+ unsigned int switch_count;
23
+ unsigned int poll_count;
17
24
  unsigned int pending_count;
18
25
  double idle_gc_period;
19
26
  double idle_gc_last_time;
@@ -27,6 +34,7 @@ void backend_base_mark(struct Backend_base *base);
27
34
  VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base);
28
35
  void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_base *base, VALUE fiber, VALUE value, int prioritize);
29
36
  void backend_trace(struct Backend_base *base, int argc, VALUE *argv);
37
+ struct backend_stats backend_base_stats(struct Backend_base *base);
30
38
 
31
39
  // tracing
32
40
  #define SHOULD_TRACE(base) ((base)->trace_proc != Qnil)
@@ -60,6 +68,7 @@ VALUE io_enc_str(VALUE str, rb_io_t *fptr);
60
68
  //////////////////////////////////////////////////////////////////////
61
69
  //////////////////////////////////////////////////////////////////////
62
70
 
71
+ struct backend_stats backend_get_stats(VALUE self);
63
72
  VALUE backend_await(struct Backend_base *backend);
64
73
  VALUE backend_snooze();
65
74
 
@@ -92,7 +101,10 @@ VALUE backend_timeout_exception(VALUE exception);
92
101
  VALUE Backend_timeout_ensure_safe(VALUE arg);
93
102
  VALUE Backend_timeout_ensure_safe(VALUE arg);
94
103
  VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags);
104
+ VALUE Backend_stats(VALUE self);
95
105
  void backend_run_idle_tasks(struct Backend_base *base);
96
106
  void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking);
97
107
 
108
+ void backend_setup_stats_symbols();
109
+
98
110
  #endif /* BACKEND_COMMON_H */
@@ -191,12 +191,23 @@ inline VALUE Backend_poll(VALUE self, VALUE blocking) {
191
191
  Backend_t *backend;
192
192
  GetBackend(self, backend);
193
193
 
194
+ backend->base.poll_count++;
195
+
194
196
  if (!is_blocking && backend->pending_sqes) {
195
197
  backend->pending_sqes = 0;
196
198
  io_uring_submit(&backend->ring);
197
199
  }
198
200
 
199
201
  COND_TRACE(&backend->base, 2, SYM_fiber_event_poll_enter, rb_fiber_current());
202
+ // if (SHOULD_TRACE(&backend->base))
203
+ // printf(
204
+ // "io_uring_poll(blocking_mode: %d, pending: %d, taken: %d, available: %d, runqueue: %d\n",
205
+ // is_blocking,
206
+ // backend->base.pending_count,
207
+ // backend->store.taken_count,
208
+ // backend->store.available_count,
209
+ // backend->base.runqueue.entries.count
210
+ // );
200
211
  if (is_blocking) io_uring_backend_poll(backend);
201
212
  io_uring_backend_handle_ready_cqes(backend);
202
213
  COND_TRACE(&backend->base, 2, SYM_fiber_event_poll_leave, rb_fiber_current());
@@ -225,15 +236,11 @@ inline VALUE Backend_switch_fiber(VALUE self) {
225
236
  return backend_base_switch_fiber(self, &backend->base);
226
237
  }
227
238
 
228
- inline struct backend_stats Backend_stats(VALUE self) {
239
+ inline struct backend_stats backend_get_stats(VALUE self) {
229
240
  Backend_t *backend;
230
241
  GetBackend(self, backend);
231
242
 
232
- return (struct backend_stats){
233
- .scheduled_fibers = runqueue_len(&backend->base.runqueue),
234
- .waiting_fibers = 0,
235
- .pending_ops = backend->base.pending_count
236
- };
243
+ return backend_base_stats(&backend->base);
237
244
  }
238
245
 
239
246
  VALUE Backend_wakeup(VALUE self) {
@@ -271,6 +278,7 @@ int io_uring_backend_defer_submit_and_await(
271
278
  {
272
279
  VALUE switchpoint_result = Qnil;
273
280
 
281
+ backend->base.op_count++;
274
282
  if (sqe) {
275
283
  io_uring_sqe_set_data(sqe, ctx);
276
284
  io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
@@ -302,17 +310,25 @@ VALUE io_uring_backend_wait_fd(Backend_t *backend, int fd, int write) {
302
310
  io_uring_prep_poll_add(sqe, fd, write ? POLLOUT : POLLIN);
303
311
 
304
312
  io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resumed_value);
313
+ context_store_release(&backend->store, ctx);
314
+
305
315
  RB_GC_GUARD(resumed_value);
306
316
  return resumed_value;
307
317
  }
308
318
 
309
- 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) {
310
320
  Backend_t *backend;
311
321
  rb_io_t *fptr;
312
322
  long dynamic_len = length == Qnil;
313
323
  long buffer_size = dynamic_len ? 4096 : NUM2INT(length);
314
- int shrinkable = io_setstrbuf(&str, buffer_size);
315
- 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;
316
332
  long total = 0;
317
333
  int read_to_eof = RTEST(to_eof);
318
334
  VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
@@ -349,9 +365,9 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
349
365
  if (!dynamic_len) break;
350
366
 
351
367
  // resize buffer
352
- rb_str_resize(str, total);
368
+ rb_str_resize(str, buf_pos + total);
353
369
  rb_str_modify_expand(str, buffer_size);
354
- buf = RSTRING_PTR(str) + total;
370
+ buf = RSTRING_PTR(str) + buf_pos + total;
355
371
  shrinkable = 0;
356
372
  buffer_size += buffer_size;
357
373
  }
@@ -359,7 +375,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
359
375
  }
360
376
  }
361
377
 
362
- io_set_read_length(str, total, shrinkable);
378
+ io_set_read_length(str, buf_pos + total, shrinkable);
363
379
  io_enc_str(str, fptr);
364
380
 
365
381
  if (!total) return Qnil;
@@ -367,12 +383,12 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
367
383
  return str;
368
384
  }
369
385
 
370
- VALUE Backend_read_loop(VALUE self, VALUE io) {
386
+ VALUE Backend_read_loop(VALUE self, VALUE io, VALUE maxlen) {
371
387
  Backend_t *backend;
372
388
  rb_io_t *fptr;
373
389
  VALUE str;
374
390
  long total;
375
- long len = 8192;
391
+ long len = NUM2INT(maxlen);
376
392
  int shrinkable;
377
393
  char *buf;
378
394
  VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
@@ -580,13 +596,19 @@ VALUE Backend_write_m(int argc, VALUE *argv, VALUE self) {
580
596
  Backend_writev(self, argv[0], argc - 1, argv + 1);
581
597
  }
582
598
 
583
- 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) {
584
600
  Backend_t *backend;
585
601
  rb_io_t *fptr;
586
602
  long dynamic_len = length == Qnil;
587
603
  long len = dynamic_len ? 4096 : NUM2INT(length);
588
- int shrinkable = io_setstrbuf(&str, len);
589
- 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;
590
612
  long total = 0;
591
613
  VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
592
614
 
@@ -618,7 +640,7 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
618
640
  }
619
641
  }
620
642
 
621
- io_set_read_length(str, total, shrinkable);
643
+ io_set_read_length(str, buf_pos + total, shrinkable);
622
644
  io_enc_str(str, fptr);
623
645
 
624
646
  if (!total) return Qnil;
@@ -626,12 +648,12 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
626
648
  return str;
627
649
  }
628
650
 
629
- VALUE Backend_recv_loop(VALUE self, VALUE io) {
651
+ VALUE Backend_recv_loop(VALUE self, VALUE io, VALUE maxlen) {
630
652
  Backend_t *backend;
631
653
  rb_io_t *fptr;
632
654
  VALUE str;
633
655
  long total;
634
- long len = 8192;
656
+ long len = NUM2INT(maxlen);
635
657
  int shrinkable;
636
658
  char *buf;
637
659
  VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
@@ -1021,6 +1043,7 @@ VALUE Backend_timeout(int argc, VALUE *argv, VALUE self) {
1021
1043
  io_uring_prep_timeout(sqe, &ts, 0, 0);
1022
1044
  io_uring_sqe_set_data(sqe, ctx);
1023
1045
  io_uring_backend_defer_submit(backend);
1046
+ backend->base.op_count++;
1024
1047
 
1025
1048
  struct Backend_timeout_ctx timeout_ctx = {backend, ctx};
1026
1049
  result = rb_ensure(Backend_timeout_ensure_safe, Qnil, Backend_timeout_ensure, (VALUE)&timeout_ctx);
@@ -1188,6 +1211,7 @@ VALUE Backend_chain(int argc,VALUE *argv, VALUE self) {
1188
1211
  sqe_count++;
1189
1212
  }
1190
1213
 
1214
+ backend->base.op_count += sqe_count;
1191
1215
  ctx->ref_count = sqe_count + 1;
1192
1216
  io_uring_backend_defer_submit(backend);
1193
1217
  resume_value = backend_await((struct Backend_base *)backend);
@@ -1324,6 +1348,7 @@ VALUE Backend_splice_chunks(VALUE self, VALUE src, VALUE dest, VALUE prefix, VAL
1324
1348
  if (prefix != Qnil) {
1325
1349
  splice_chunks_get_sqe(backend, &ctx, &sqe, OP_WRITE);
1326
1350
  splice_chunks_prep_write(ctx, sqe, dest_fptr->fd, prefix);
1351
+ backend->base.op_count++;
1327
1352
  }
1328
1353
 
1329
1354
  while (1) {
@@ -1333,7 +1358,8 @@ VALUE Backend_splice_chunks(VALUE self, VALUE src, VALUE dest, VALUE prefix, VAL
1333
1358
 
1334
1359
  splice_chunks_get_sqe(backend, &ctx, &sqe, OP_SPLICE);
1335
1360
  splice_chunks_prep_splice(ctx, sqe, src_fptr->fd, pipefd[1], maxlen);
1336
-
1361
+ backend->base.op_count++;
1362
+
1337
1363
  SPLICE_CHUNKS_AWAIT_OPS(backend, &ctx, &chunk_len, &switchpoint_result);
1338
1364
  if (chunk_len == 0) break;
1339
1365
 
@@ -1345,15 +1371,18 @@ VALUE Backend_splice_chunks(VALUE self, VALUE src, VALUE dest, VALUE prefix, VAL
1345
1371
  chunk_prefix_str = (TYPE(chunk_prefix) == T_STRING) ? chunk_prefix : rb_funcall(chunk_prefix, ID_call, 1, chunk_len_value);
1346
1372
  splice_chunks_get_sqe(backend, &ctx, &sqe, OP_WRITE);
1347
1373
  splice_chunks_prep_write(ctx, sqe, dest_fptr->fd, chunk_prefix_str);
1374
+ backend->base.op_count++;
1348
1375
  }
1349
1376
 
1350
1377
  splice_chunks_get_sqe(backend, &ctx, &sqe, OP_SPLICE);
1351
1378
  splice_chunks_prep_splice(ctx, sqe, pipefd[0], dest_fptr->fd, chunk_len);
1379
+ backend->base.op_count++;
1352
1380
 
1353
1381
  if (chunk_postfix != Qnil) {
1354
1382
  chunk_postfix_str = (TYPE(chunk_postfix) == T_STRING) ? chunk_postfix : rb_funcall(chunk_postfix, ID_call, 1, chunk_len_value);
1355
1383
  splice_chunks_get_sqe(backend, &ctx, &sqe, OP_WRITE);
1356
1384
  splice_chunks_prep_write(ctx, sqe, dest_fptr->fd, chunk_postfix_str);
1385
+ backend->base.op_count++;
1357
1386
  }
1358
1387
 
1359
1388
  RB_GC_GUARD(chunk_prefix_str);
@@ -1363,6 +1392,7 @@ VALUE Backend_splice_chunks(VALUE self, VALUE src, VALUE dest, VALUE prefix, VAL
1363
1392
  if (postfix != Qnil) {
1364
1393
  splice_chunks_get_sqe(backend, &ctx, &sqe, OP_WRITE);
1365
1394
  splice_chunks_prep_write(ctx, sqe, dest_fptr->fd, postfix);
1395
+ backend->base.op_count++;
1366
1396
  }
1367
1397
  if (ctx) {
1368
1398
  SPLICE_CHUNKS_AWAIT_OPS(backend, &ctx, 0, &switchpoint_result);
@@ -1408,6 +1438,7 @@ void Init_Backend() {
1408
1438
  rb_define_method(cBackend, "post_fork", Backend_post_fork, 0);
1409
1439
  rb_define_method(cBackend, "trace", Backend_trace, -1);
1410
1440
  rb_define_method(cBackend, "trace_proc=", Backend_trace_proc_set, 1);
1441
+ rb_define_method(cBackend, "stats", Backend_stats, 0);
1411
1442
 
1412
1443
  rb_define_method(cBackend, "poll", Backend_poll, 1);
1413
1444
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
@@ -1421,11 +1452,11 @@ void Init_Backend() {
1421
1452
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
1422
1453
  rb_define_method(cBackend, "connect", Backend_connect, 3);
1423
1454
  rb_define_method(cBackend, "feed_loop", Backend_feed_loop, 3);
1424
- rb_define_method(cBackend, "read", Backend_read, 4);
1425
- rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
1426
- rb_define_method(cBackend, "recv", Backend_recv, 3);
1455
+ rb_define_method(cBackend, "read", Backend_read, 5);
1456
+ rb_define_method(cBackend, "read_loop", Backend_read_loop, 2);
1457
+ rb_define_method(cBackend, "recv", Backend_recv, 4);
1427
1458
  rb_define_method(cBackend, "recv_feed_loop", Backend_recv_feed_loop, 3);
1428
- rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 1);
1459
+ rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 2);
1429
1460
  rb_define_method(cBackend, "send", Backend_send, 3);
1430
1461
  rb_define_method(cBackend, "sendv", Backend_sendv, 3);
1431
1462
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
@@ -1447,6 +1478,8 @@ void Init_Backend() {
1447
1478
  SYM_send = ID2SYM(rb_intern("send"));
1448
1479
  SYM_splice = ID2SYM(rb_intern("splice"));
1449
1480
  SYM_write = ID2SYM(rb_intern("write"));
1481
+
1482
+ backend_setup_stats_symbols();
1450
1483
  }
1451
1484
 
1452
1485
  #endif // POLYPHONY_BACKEND_LIBURING