polyphony 0.58 → 0.61

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/Gemfile.lock +15 -29
  4. data/examples/core/message_based_supervision.rb +51 -0
  5. data/examples/io/echo_server.rb +16 -7
  6. data/ext/polyphony/backend_common.c +160 -7
  7. data/ext/polyphony/backend_common.h +34 -2
  8. data/ext/polyphony/backend_io_uring.c +119 -40
  9. data/ext/polyphony/backend_io_uring_context.c +10 -1
  10. data/ext/polyphony/backend_io_uring_context.h +5 -3
  11. data/ext/polyphony/backend_libev.c +109 -31
  12. data/ext/polyphony/extconf.rb +2 -2
  13. data/ext/polyphony/fiber.c +1 -34
  14. data/ext/polyphony/polyphony.c +12 -19
  15. data/ext/polyphony/polyphony.h +9 -20
  16. data/ext/polyphony/polyphony_ext.c +0 -4
  17. data/ext/polyphony/queue.c +12 -12
  18. data/ext/polyphony/runqueue.c +21 -98
  19. data/ext/polyphony/runqueue.h +26 -0
  20. data/ext/polyphony/thread.c +6 -113
  21. data/lib/polyphony/core/timer.rb +2 -2
  22. data/lib/polyphony/extensions/fiber.rb +102 -82
  23. data/lib/polyphony/extensions/io.rb +10 -9
  24. data/lib/polyphony/extensions/openssl.rb +14 -4
  25. data/lib/polyphony/extensions/socket.rb +15 -15
  26. data/lib/polyphony/extensions/thread.rb +1 -1
  27. data/lib/polyphony/version.rb +1 -1
  28. data/polyphony.gemspec +0 -7
  29. data/test/test_backend.rb +46 -9
  30. data/test/test_ext.rb +1 -1
  31. data/test/test_fiber.rb +106 -18
  32. data/test/test_global_api.rb +1 -1
  33. data/test/test_io.rb +29 -0
  34. data/test/test_supervise.rb +100 -100
  35. data/test/test_thread.rb +5 -11
  36. data/test/test_thread_pool.rb +1 -1
  37. data/test/test_trace.rb +28 -49
  38. metadata +5 -109
  39. data/ext/polyphony/tracing.c +0 -11
  40. data/lib/polyphony/adapters/trace.rb +0 -138
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e8002b8c0e03afa8e915b5ed67d64c1fc288a5fa220dfb2c32d3966a78046eee
4
- data.tar.gz: c3a46eeae1f048f4adee9d3130f5dd3593936e843c1874d928f742d1fa83053f
3
+ metadata.gz: b7056ec954b2264a15f88934d2b1e65f53c76c301453851ceadf60715570e474
4
+ data.tar.gz: 1cd5835b7a5a3d4036e1eabf45ded2e09efdc7cacb207fc0a1a60ac67da02d90
5
5
  SHA512:
6
- metadata.gz: e6681155761daee35b73c9674e69aa505786e576814db0c909028cb576d8609f99bccc79f80b7d06d90c7658747bc2589c9837d3bdb3419782147e573d39f278
7
- data.tar.gz: 90197a8db54405ca37492a54a20adf00c85f142af606cad26ad823161825ccf6bd9a414adcbbf3e27eaf93eea1d94a7cbc50a2fb5982ee4ce48969d4378400cd
6
+ metadata.gz: b5e32edc6bc22ba580e7c6d7d42a73982803080e9a9782326cb7b8796aa62ad6b75bccb1e6607879f0d3e5a5c4db8e5597d8407b76014f08f3945d1d8ad0097a
7
+ data.tar.gz: b8fe0f9419061295f830ed12ae9c8ea089a966686388cff580ddb6e1f7cd8c17a04928f9af41083019a5a11e8cccce25af404959db30a730a2ade2359a7e07ef
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## 0.61 2021-07-20
2
+
3
+ - Add more statistics, move stats to `Backend#stats`
4
+
5
+ ## 0.60 2021-07-15
6
+
7
+
8
+ - Fix linux version detection (for kernel version > 5.9)
9
+ - Fix op ctx leak in io_uring backend (when polling for I/O readiness)
10
+ - Add support for appending to buffer in `Backend#read`, `Backend#recv` methods
11
+ - Improve anti-event starvation mechanism
12
+ - Redesign fiber monitoring mechanism
13
+ - Implement `Fiber#attach`
14
+ - Add optional maxlen argument to `IO#read_loop`, `Socket#recv_loop` (#60)
15
+ - Implement `Fiber#detach` (#52)
16
+
17
+ ## 0.59.1 2021-06-28
18
+
19
+ - Accept fiber tag in `Polyphony::Timer.new`
20
+
21
+ ## 0.59 2021-06-28
22
+
23
+ - Redesign tracing mechanism and API - now completely separated from Ruby core
24
+ trace API
25
+ - Refactor C code - move run queue into backend
26
+
1
27
  ## 0.58 2021-06-25
2
28
 
3
29
  - Implement `Thread#idle_gc_period`, `#on_idle` (#56)
data/Gemfile.lock CHANGED
@@ -1,27 +1,25 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.58)
4
+ polyphony (0.61)
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
@@ -5,12 +5,127 @@
5
5
  #include "polyphony.h"
6
6
  #include "backend_common.h"
7
7
 
8
- inline void initialize_backend_base(struct Backend_base *base) {
8
+ inline void backend_base_initialize(struct Backend_base *base) {
9
+ runqueue_initialize(&base->runqueue);
9
10
  base->currently_polling = 0;
11
+ base->op_count = 0;
12
+ base->switch_count = 0;
13
+ base->poll_count = 0;
10
14
  base->pending_count = 0;
11
15
  base->idle_gc_period = 0;
12
16
  base->idle_gc_last_time = 0;
13
- base->idle_block = Qnil;
17
+ base->idle_proc = Qnil;
18
+ base->trace_proc = Qnil;
19
+ }
20
+
21
+ inline void backend_base_finalize(struct Backend_base *base) {
22
+ runqueue_finalize(&base->runqueue);
23
+ }
24
+
25
+ inline void backend_base_mark(struct Backend_base *base) {
26
+ if (base->idle_proc != Qnil) rb_gc_mark(base->idle_proc);
27
+ if (base->trace_proc != Qnil) rb_gc_mark(base->trace_proc);
28
+ runqueue_mark(&base->runqueue);
29
+ }
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
+
38
+ VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base) {
39
+ VALUE current_fiber = rb_fiber_current();
40
+ runqueue_entry next;
41
+ unsigned int pending_ops_count = base->pending_count;
42
+ unsigned int backend_was_polled = 0;
43
+ unsigned int idle_tasks_run_count = 0;
44
+
45
+ base->switch_count++;
46
+ COND_TRACE(base, 2, SYM_fiber_switchpoint, current_fiber);
47
+
48
+ while (1) {
49
+ next = runqueue_shift(&base->runqueue);
50
+ if (next.fiber != Qnil) {
51
+ // Polling for I/O op completion is normally done when the run queue is
52
+ // empty, but if the runqueue never empties, we'll never get to process
53
+ // any event completions. In order to prevent this, an anti-starvation
54
+ // mechanism is employed, under the following conditions:
55
+ // - a blocking poll was not yet performed
56
+ // - there are pending blocking operations
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
+
62
+ break;
63
+ }
64
+
65
+ if (!idle_tasks_run_count) {
66
+ idle_tasks_run_count++;
67
+ backend_run_idle_tasks(base);
68
+ }
69
+ if (pending_ops_count == 0) break;
70
+ Backend_poll(backend, Qtrue);
71
+ backend_was_polled = 1;
72
+ }
73
+
74
+ if (next.fiber == Qnil) return Qnil;
75
+
76
+ // run next fiber
77
+ COND_TRACE(base, 3, SYM_fiber_run, next.fiber, next.value);
78
+
79
+ rb_ivar_set(next.fiber, ID_ivar_runnable, Qnil);
80
+ RB_GC_GUARD(next.fiber);
81
+ RB_GC_GUARD(next.value);
82
+ return (next.fiber == current_fiber) ?
83
+ next.value : FIBER_TRANSFER(next.fiber, next.value);
84
+ }
85
+
86
+ void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_base *base, VALUE fiber, VALUE value, int prioritize) {
87
+ int already_runnable;
88
+
89
+ if (rb_fiber_alive_p(fiber) != Qtrue) return;
90
+ already_runnable = rb_ivar_get(fiber, ID_ivar_runnable) != Qnil;
91
+
92
+ COND_TRACE(base, 4, SYM_fiber_schedule, fiber, value, prioritize ? Qtrue : Qfalse);
93
+
94
+ (prioritize ? runqueue_unshift : runqueue_push)(&base->runqueue, fiber, value, already_runnable);
95
+ if (!already_runnable) {
96
+ rb_ivar_set(fiber, ID_ivar_runnable, Qtrue);
97
+ if (rb_thread_current() != thread) {
98
+ // If the fiber scheduling is done across threads, we need to make sure the
99
+ // target thread is woken up in case it is in the middle of running its
100
+ // event selector. Otherwise it's gonna be stuck waiting for an event to
101
+ // happen, not knowing that it there's already a fiber ready to run in its
102
+ // run queue.
103
+ Backend_wakeup(backend);
104
+ }
105
+ }
106
+ }
107
+
108
+
109
+ inline void backend_trace(struct Backend_base *base, int argc, VALUE *argv) {
110
+ if (base->trace_proc == Qnil) return;
111
+
112
+ rb_funcallv(base->trace_proc, ID_call, argc, argv);
113
+ }
114
+
115
+ inline struct backend_stats backend_base_stats(struct Backend_base *base) {
116
+ struct backend_stats stats = {
117
+ .runqueue_length = runqueue_len(&base->runqueue),
118
+ .runqueue_max_length = runqueue_max_len(&base->runqueue),
119
+ .op_count = base->op_count,
120
+ .switch_count = base->switch_count,
121
+ .poll_count = base->poll_count,
122
+ .pending_ops = base->pending_count
123
+ };
124
+
125
+ base->op_count = 0;
126
+ base->switch_count = 0;
127
+ base->poll_count = 0;
128
+ return stats;
14
129
  }
15
130
 
16
131
  #ifdef POLYPHONY_USE_PIDFD_OPEN
@@ -80,7 +195,7 @@ inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
80
195
  //////////////////////////////////////////////////////////////////////
81
196
  //////////////////////////////////////////////////////////////////////
82
197
 
83
- VALUE backend_await(struct Backend_base *backend) {
198
+ inline VALUE backend_await(struct Backend_base *backend) {
84
199
  VALUE ret;
85
200
  backend->pending_count++;
86
201
  ret = Thread_switch_fiber(rb_thread_current());
@@ -89,9 +204,10 @@ VALUE backend_await(struct Backend_base *backend) {
89
204
  return ret;
90
205
  }
91
206
 
92
- VALUE backend_snooze() {
207
+ inline VALUE backend_snooze() {
208
+ VALUE ret;
93
209
  Fiber_make_runnable(rb_fiber_current(), Qnil);
94
- VALUE ret = Thread_switch_fiber(rb_thread_current());
210
+ ret = Thread_switch_fiber(rb_thread_current());
95
211
  return ret;
96
212
  }
97
213
 
@@ -182,8 +298,8 @@ inline void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking) {
182
298
  }
183
299
 
184
300
  inline void backend_run_idle_tasks(struct Backend_base *base) {
185
- if (base->idle_block != Qnil)
186
- rb_funcall(base->idle_block, ID_call, 0);
301
+ if (base->idle_proc != Qnil)
302
+ rb_funcall(base->idle_proc, ID_call, 0);
187
303
 
188
304
  if (base->idle_gc_period == 0) return;
189
305
 
@@ -195,3 +311,40 @@ inline void backend_run_idle_tasks(struct Backend_base *base) {
195
311
  rb_gc_start();
196
312
  rb_gc_disable();
197
313
  }
314
+
315
+ VALUE SYM_runqueue_length;
316
+ VALUE SYM_runqueue_max_length;
317
+ VALUE SYM_op_count;
318
+ VALUE SYM_switch_count;
319
+ VALUE SYM_poll_count;
320
+ VALUE SYM_pending_ops;
321
+
322
+ VALUE Backend_stats(VALUE self) {
323
+ struct backend_stats backend_stats = backend_get_stats(self);
324
+
325
+ VALUE stats = rb_hash_new();
326
+ rb_hash_aset(stats, SYM_runqueue_length, INT2NUM(backend_stats.runqueue_length));
327
+ rb_hash_aset(stats, SYM_runqueue_max_length, INT2NUM(backend_stats.runqueue_max_length));
328
+ rb_hash_aset(stats, SYM_op_count, INT2NUM(backend_stats.op_count));
329
+ rb_hash_aset(stats, SYM_switch_count, INT2NUM(backend_stats.switch_count));
330
+ rb_hash_aset(stats, SYM_poll_count, INT2NUM(backend_stats.poll_count));
331
+ rb_hash_aset(stats, SYM_pending_ops, INT2NUM(backend_stats.pending_ops));
332
+ RB_GC_GUARD(stats);
333
+ return stats;
334
+ }
335
+
336
+ void backend_setup_stats_symbols() {
337
+ SYM_runqueue_length = ID2SYM(rb_intern("runqueue_length"));
338
+ SYM_runqueue_max_length = ID2SYM(rb_intern("runqueue_max_length"));
339
+ SYM_op_count = ID2SYM(rb_intern("op_count"));
340
+ SYM_switch_count = ID2SYM(rb_intern("switch_count"));
341
+ SYM_poll_count = ID2SYM(rb_intern("poll_count"));
342
+ SYM_pending_ops = ID2SYM(rb_intern("pending_ops"));
343
+
344
+ rb_global_variable(&SYM_runqueue_length);
345
+ rb_global_variable(&SYM_runqueue_max_length);
346
+ rb_global_variable(&SYM_op_count);
347
+ rb_global_variable(&SYM_switch_count);
348
+ rb_global_variable(&SYM_poll_count);
349
+ rb_global_variable(&SYM_pending_ops);
350
+ }
@@ -3,16 +3,44 @@
3
3
 
4
4
  #include "ruby.h"
5
5
  #include "ruby/io.h"
6
+ #include "runqueue.h"
7
+
8
+ struct backend_stats {
9
+ unsigned int runqueue_length;
10
+ unsigned int runqueue_max_length;
11
+ unsigned int op_count;
12
+ unsigned int switch_count;
13
+ unsigned int poll_count;
14
+ unsigned int pending_ops;
15
+ };
6
16
 
7
17
  struct Backend_base {
18
+ runqueue_t runqueue;
8
19
  unsigned int currently_polling;
20
+ unsigned int op_count;
21
+ unsigned int switch_count;
22
+ unsigned int poll_count;
9
23
  unsigned int pending_count;
10
24
  double idle_gc_period;
11
25
  double idle_gc_last_time;
12
- VALUE idle_block;
26
+ VALUE idle_proc;
27
+ VALUE trace_proc;
13
28
  };
14
29
 
15
- void initialize_backend_base(struct Backend_base *base);
30
+ void backend_base_initialize(struct Backend_base *base);
31
+ void backend_base_finalize(struct Backend_base *base);
32
+ void backend_base_mark(struct Backend_base *base);
33
+ VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base);
34
+ void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_base *base, VALUE fiber, VALUE value, int prioritize);
35
+ void backend_trace(struct Backend_base *base, int argc, VALUE *argv);
36
+ struct backend_stats backend_base_stats(struct Backend_base *base);
37
+
38
+ // tracing
39
+ #define SHOULD_TRACE(base) ((base)->trace_proc != Qnil)
40
+ #define TRACE(base, ...) rb_funcall((base)->trace_proc, ID_call, __VA_ARGS__)
41
+ #define COND_TRACE(base, ...) if (SHOULD_TRACE(base)) { TRACE(base, __VA_ARGS__); }
42
+
43
+
16
44
 
17
45
  #ifdef POLYPHONY_USE_PIDFD_OPEN
18
46
  int pidfd_open(pid_t pid, unsigned int flags);
@@ -39,6 +67,7 @@ VALUE io_enc_str(VALUE str, rb_io_t *fptr);
39
67
  //////////////////////////////////////////////////////////////////////
40
68
  //////////////////////////////////////////////////////////////////////
41
69
 
70
+ struct backend_stats backend_get_stats(VALUE self);
42
71
  VALUE backend_await(struct Backend_base *backend);
43
72
  VALUE backend_snooze();
44
73
 
@@ -71,7 +100,10 @@ VALUE backend_timeout_exception(VALUE exception);
71
100
  VALUE Backend_timeout_ensure_safe(VALUE arg);
72
101
  VALUE Backend_timeout_ensure_safe(VALUE arg);
73
102
  VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags);
103
+ VALUE Backend_stats(VALUE self);
74
104
  void backend_run_idle_tasks(struct Backend_base *base);
75
105
  void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking);
76
106
 
107
+ void backend_setup_stats_symbols();
108
+
77
109
  #endif /* BACKEND_COMMON_H */