polyphony 0.59 → 0.62

Sign up to get free protection for your applications and to get access to all the features.
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