polyphony 0.55.0 → 0.56.0

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: 2f0f6698a072e4ba2913477d20f5884d5d7579b99cca1db0f2ffa3aeca42ebcc
4
- data.tar.gz: 97fec9986f6693d685bfb0c1745995c8a85413a9b6cc1a1ff69393d55a5f3b74
3
+ metadata.gz: c6c9247de0e5049128fffb31879fd0a79ba6fe304e3802a5b7fa69baa4445ecf
4
+ data.tar.gz: f2e432fb0f9d6362b7d38834a0e37c7907328c6a27d6544e1dad5363ba4f715c
5
5
  SHA512:
6
- metadata.gz: 053beffc7c27658d129002b5efc29f64c7fcf1ff7fbf3f2eea1d588b2a9e40e89251640bf9bb82ce478837f38c32863987d8ac0af987d374d4ac242d012363dd
7
- data.tar.gz: e89df4044f09f4df64fc57622f22e70d932f2f09ea3b2a5c2090698784c286367df43d352231068b8ef96d6a7ba89e5492b4c237c9960aeb5fa79af9ee738768
6
+ metadata.gz: 3d69bdb3f12cb65cf580cc243ea0a4f479b5306cf087bcfc5e9d8def1fee49b998528f881844649bc01b501ea22371632fd1f4f08d1f8118692bdf2331f698e6
7
+ data.tar.gz: d49152bd9c9be94835a020f829c7ddc7f3766fcac9fca8e652bd8df43daab7a6eda90c94b40e57b3cef064d1ff2a957f42030aedefae1b2d1ad6a6587cf6fa86
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.56.0 2021-06-22
2
+
3
+ - Implement fake `Backend#splice`, `Backend#splice_to_eof` methods for non-Linux
4
+ OS
5
+
1
6
  ## 0.55.0 2021-06-17
2
7
 
3
8
  - Finish io_uring implementation of Backend#chain
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.55.0)
4
+ polyphony (0.56.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ require 'polyphony'
6
+
7
+ GC.disable
8
+
9
+ p count: GC.count
10
+ snooze
11
+ p count_after_snooze: GC.count
12
+ sleep 0.1
13
+ p count_after_sleep: GC.count
14
+
15
+ Thread.current.backend.idle_gc_period = 60
16
+
17
+ p count: GC.count
18
+ snooze
19
+ p count_after_snooze: GC.count
20
+ sleep 0.1
21
+ p count_after_sleep: GC.count
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ i, o = IO.pipe
7
+ f = spin { p i.read }
8
+
9
+ o << 'hello'
10
+ o.close
11
+ f.await
@@ -0,0 +1,186 @@
1
+ #include <time.h>
2
+ #include <fcntl.h>
3
+ #include "ruby.h"
4
+ #include "ruby/io.h"
5
+ #include "polyphony.h"
6
+ #include "backend_common.h"
7
+
8
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
9
+ #ifndef __NR_pidfd_open
10
+ #define __NR_pidfd_open 434 /* System call # on most architectures */
11
+ #endif
12
+
13
+ inline int pidfd_open(pid_t pid, unsigned int flags) {
14
+ return syscall(__NR_pidfd_open, pid, flags);
15
+ }
16
+ #endif
17
+
18
+ //////////////////////////////////////////////////////////////////////
19
+ //////////////////////////////////////////////////////////////////////
20
+ // the following is copied verbatim from the Ruby source code (io.c)
21
+
22
+ inline int io_setstrbuf(VALUE *str, long len) {
23
+ #ifdef _WIN32
24
+ len = (len + 1) & ~1L; /* round up for wide char */
25
+ #endif
26
+ if (*str == Qnil) {
27
+ *str = rb_str_new(0, len);
28
+ return 1;
29
+ }
30
+ else {
31
+ VALUE s = StringValue(*str);
32
+ long clen = RSTRING_LEN(s);
33
+ if (clen >= len) {
34
+ rb_str_modify(s);
35
+ return 0;
36
+ }
37
+ len -= clen;
38
+ }
39
+ rb_str_modify_expand(*str, len);
40
+ return 0;
41
+ }
42
+
43
+ #define MAX_REALLOC_GAP 4096
44
+
45
+ inline void io_shrink_read_string(VALUE str, long n) {
46
+ if (rb_str_capacity(str) - n > MAX_REALLOC_GAP) {
47
+ rb_str_resize(str, n);
48
+ }
49
+ }
50
+
51
+ inline void io_set_read_length(VALUE str, long n, int shrinkable) {
52
+ if (RSTRING_LEN(str) != n) {
53
+ rb_str_modify(str);
54
+ rb_str_set_len(str, n);
55
+ if (shrinkable) io_shrink_read_string(str, n);
56
+ }
57
+ }
58
+
59
+ inline rb_encoding* io_read_encoding(rb_io_t *fptr) {
60
+ if (fptr->encs.enc) {
61
+ return fptr->encs.enc;
62
+ }
63
+ return rb_default_external_encoding();
64
+ }
65
+
66
+ inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
67
+ OBJ_TAINT(str);
68
+ rb_enc_associate(str, io_read_encoding(fptr));
69
+ return str;
70
+ }
71
+
72
+ //////////////////////////////////////////////////////////////////////
73
+ //////////////////////////////////////////////////////////////////////
74
+
75
+ VALUE backend_await(struct Backend_base *backend) {
76
+ VALUE ret;
77
+ backend->pending_count++;
78
+ ret = Thread_switch_fiber(rb_thread_current());
79
+ backend->pending_count--;
80
+ RB_GC_GUARD(ret);
81
+ return ret;
82
+ }
83
+
84
+ VALUE backend_snooze() {
85
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
86
+ VALUE ret = Thread_switch_fiber(rb_thread_current());
87
+ return ret;
88
+ }
89
+
90
+ inline void rectify_io_file_pos(rb_io_t *fptr) {
91
+ // Apparently after reopening a closed file, the file position is not reset,
92
+ // which causes the read to fail. Fortunately we can use fptr->rbuf.len to
93
+ // find out if that's the case.
94
+ // See: https://github.com/digital-fabric/polyphony/issues/30
95
+ if (fptr->rbuf.len > 0) {
96
+ lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
97
+ fptr->rbuf.len = 0;
98
+ }
99
+ }
100
+
101
+ inline double current_time() {
102
+ struct timespec ts;
103
+ clock_gettime(CLOCK_MONOTONIC, &ts);
104
+ long long ns = ts.tv_sec;
105
+ ns = ns * 1e9 + ts.tv_nsec;
106
+ double t = ns;
107
+ return t / 1e9;
108
+ }
109
+
110
+ inline VALUE backend_timeout_exception(VALUE exception) {
111
+ if (rb_obj_is_kind_of(exception, rb_cArray) == Qtrue)
112
+ return rb_funcall(rb_ary_entry(exception, 0), ID_new, 1, rb_ary_entry(exception, 1));
113
+ else if (rb_obj_is_kind_of(exception, rb_cClass) == Qtrue)
114
+ return rb_funcall(exception, ID_new, 0);
115
+ else
116
+ return rb_funcall(rb_eRuntimeError, ID_new, 1, exception);
117
+ }
118
+
119
+ VALUE Backend_timeout_safe(VALUE arg) {
120
+ return rb_yield(arg);
121
+ }
122
+
123
+ VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
124
+ return exception;
125
+ }
126
+
127
+ VALUE Backend_timeout_ensure_safe(VALUE arg) {
128
+ return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
129
+ }
130
+
131
+ static VALUE empty_string = Qnil;
132
+
133
+ VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags) {
134
+ switch (RARRAY_LEN(ary)) {
135
+ case 0:
136
+ return Qnil;
137
+ case 1:
138
+ return Backend_send(self, io, RARRAY_AREF(ary, 0), flags);
139
+ default:
140
+ if (empty_string == Qnil) {
141
+ empty_string = rb_str_new_literal("");
142
+ rb_global_variable(&empty_string);
143
+ }
144
+ VALUE joined = rb_ary_join(ary, empty_string);
145
+ VALUE result = Backend_send(self, io, joined, flags);
146
+ RB_GC_GUARD(joined);
147
+ return result;
148
+ }
149
+ }
150
+
151
+ inline void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking) {
152
+ VALUE blocking_mode = rb_ivar_get(io, ID_ivar_blocking_mode);
153
+ if (blocking == blocking_mode) return;
154
+
155
+ rb_ivar_set(io, ID_ivar_blocking_mode, blocking);
156
+
157
+ #ifdef _WIN32
158
+ if (blocking != Qtrue)
159
+ rb_w32_set_nonblock(fptr->fd);
160
+ #elif defined(F_GETFL)
161
+ int flags = fcntl(fptr->fd, F_GETFL);
162
+ if (flags == -1) return;
163
+ int is_nonblocking = flags & O_NONBLOCK;
164
+
165
+ if (blocking == Qtrue) {
166
+ if (!is_nonblocking) return;
167
+ flags &= ~O_NONBLOCK;
168
+ } else {
169
+ if (is_nonblocking) return;
170
+ flags |= O_NONBLOCK;
171
+ }
172
+ fcntl(fptr->fd, F_SETFL, flags);
173
+ #endif
174
+ }
175
+
176
+ inline void backend_run_idle_tasks(struct Backend_base *base) {
177
+ if (base->idle_gc_period == 0) return;
178
+
179
+ double now = current_time();
180
+ if (now - base->idle_gc_last_time < base->idle_gc_period) return;
181
+
182
+ base->idle_gc_last_time = now;
183
+ rb_gc_enable();
184
+ rb_gc_start();
185
+ rb_gc_disable();
186
+ }
@@ -1,17 +1,18 @@
1
- #include <time.h>
1
+ #ifndef BACKEND_COMMON_H
2
+ #define BACKEND_COMMON_H
2
3
 
3
4
  #include "ruby.h"
4
5
  #include "ruby/io.h"
5
6
 
7
+ struct Backend_base {
8
+ unsigned int currently_polling;
9
+ unsigned int pending_count;
10
+ double idle_gc_period;
11
+ double idle_gc_last_time;
12
+ };
6
13
 
7
14
  #ifdef POLYPHONY_USE_PIDFD_OPEN
8
- #ifndef __NR_pidfd_open
9
- #define __NR_pidfd_open 434 /* System call # on most architectures */
10
- #endif
11
-
12
- static int pidfd_open(pid_t pid, unsigned int flags) {
13
- return syscall(__NR_pidfd_open, pid, flags);
14
- }
15
+ int pidfd_open(pid_t pid, unsigned int flags);
15
16
  #endif
16
17
 
17
18
  //////////////////////////////////////////////////////////////////////
@@ -26,75 +27,19 @@ struct io_internal_read_struct {
26
27
 
27
28
  #define StringValue(v) rb_string_value(&(v))
28
29
 
29
- inline int io_setstrbuf(VALUE *str, long len) {
30
- #ifdef _WIN32
31
- len = (len + 1) & ~1L; /* round up for wide char */
32
- #endif
33
- if (*str == Qnil) {
34
- *str = rb_str_new(0, len);
35
- return 1;
36
- }
37
- else {
38
- VALUE s = StringValue(*str);
39
- long clen = RSTRING_LEN(s);
40
- if (clen >= len) {
41
- rb_str_modify(s);
42
- return 0;
43
- }
44
- len -= clen;
45
- }
46
- rb_str_modify_expand(*str, len);
47
- return 0;
48
- }
49
-
50
- #define MAX_REALLOC_GAP 4096
51
-
52
- inline void io_shrink_read_string(VALUE str, long n) {
53
- if (rb_str_capacity(str) - n > MAX_REALLOC_GAP) {
54
- rb_str_resize(str, n);
55
- }
56
- }
57
-
58
- inline void io_set_read_length(VALUE str, long n, int shrinkable) {
59
- if (RSTRING_LEN(str) != n) {
60
- rb_str_modify(str);
61
- rb_str_set_len(str, n);
62
- if (shrinkable) io_shrink_read_string(str, n);
63
- }
64
- }
65
-
66
- inline rb_encoding* io_read_encoding(rb_io_t *fptr) {
67
- if (fptr->encs.enc) {
68
- return fptr->encs.enc;
69
- }
70
- return rb_default_external_encoding();
71
- }
72
-
73
- inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
74
- OBJ_TAINT(str);
75
- rb_enc_associate(str, io_read_encoding(fptr));
76
- return str;
77
- }
30
+ int io_setstrbuf(VALUE *str, long len);
31
+ void io_shrink_read_string(VALUE str, long n);
32
+ void io_set_read_length(VALUE str, long n, int shrinkable);
33
+ rb_encoding* io_read_encoding(rb_io_t *fptr);
34
+ VALUE io_enc_str(VALUE str, rb_io_t *fptr);
78
35
 
79
36
  //////////////////////////////////////////////////////////////////////
80
37
  //////////////////////////////////////////////////////////////////////
81
38
 
82
- inline VALUE backend_await(Backend_t *backend) {
83
- VALUE ret;
84
- backend->pending_count++;
85
- ret = Thread_switch_fiber(rb_thread_current());
86
- backend->pending_count--;
87
- RB_GC_GUARD(ret);
88
- return ret;
89
- }
90
-
91
- inline VALUE backend_snooze() {
92
- Fiber_make_runnable(rb_fiber_current(), Qnil);
93
- return Thread_switch_fiber(rb_thread_current());
94
- }
39
+ VALUE backend_await(struct Backend_base *backend);
40
+ VALUE backend_snooze();
95
41
 
96
42
  // macros for doing read loops
97
-
98
43
  #define READ_LOOP_PREPARE_STR() { \
99
44
  str = Qnil; \
100
45
  shrinkable = io_setstrbuf(&str, len); \
@@ -117,63 +62,13 @@ inline VALUE backend_snooze() {
117
62
  READ_LOOP_PREPARE_STR(); \
118
63
  }
119
64
 
120
- inline void rectify_io_file_pos(rb_io_t *fptr) {
121
- // Apparently after reopening a closed file, the file position is not reset,
122
- // which causes the read to fail. Fortunately we can use fptr->rbuf.len to
123
- // find out if that's the case.
124
- // See: https://github.com/digital-fabric/polyphony/issues/30
125
- if (fptr->rbuf.len > 0) {
126
- lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
127
- fptr->rbuf.len = 0;
128
- }
129
- }
130
-
131
- inline double current_time() {
132
- struct timespec ts;
133
- clock_gettime(CLOCK_MONOTONIC, &ts);
134
- long long ns = ts.tv_sec;
135
- ns = ns * 1e9 + ts.tv_nsec;
136
- double t = ns;
137
- return t / 1e9;
138
- }
139
-
140
- inline VALUE backend_timeout_exception(VALUE exception) {
141
- if (rb_obj_is_kind_of(exception, rb_cArray) == Qtrue)
142
- return rb_funcall(rb_ary_entry(exception, 0), ID_new, 1, rb_ary_entry(exception, 1));
143
- else if (rb_obj_is_kind_of(exception, rb_cClass) == Qtrue)
144
- return rb_funcall(exception, ID_new, 0);
145
- else
146
- return rb_funcall(rb_eRuntimeError, ID_new, 1, exception);
147
- }
65
+ void rectify_io_file_pos(rb_io_t *fptr);
66
+ double current_time();
67
+ VALUE backend_timeout_exception(VALUE exception);
68
+ VALUE Backend_timeout_ensure_safe(VALUE arg);
69
+ VALUE Backend_timeout_ensure_safe(VALUE arg);
70
+ VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags);
71
+ void backend_run_idle_tasks(struct Backend_base *base);
72
+ void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking);
148
73
 
149
- VALUE Backend_timeout_safe(VALUE arg) {
150
- return rb_yield(arg);
151
- }
152
-
153
- VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
154
- return exception;
155
- }
156
-
157
- VALUE Backend_timeout_ensure_safe(VALUE arg) {
158
- return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
159
- }
160
-
161
- static VALUE empty_string = Qnil;
162
-
163
- VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags) {
164
- switch (RARRAY_LEN(ary)) {
165
- case 0:
166
- return Qnil;
167
- case 1:
168
- return Backend_send(self, io, RARRAY_AREF(ary, 0), flags);
169
- default:
170
- if (empty_string == Qnil) {
171
- empty_string = rb_str_new_literal("");
172
- rb_global_variable(&empty_string);
173
- }
174
- VALUE joined = rb_ary_join(ary, empty_string);
175
- VALUE result = Backend_send(self, io, joined, flags);
176
- RB_GC_GUARD(joined);
177
- return result;
178
- }
179
- }
74
+ #endif /* BACKEND_COMMON_H */
@@ -4,7 +4,6 @@
4
4
  #include <sys/socket.h>
5
5
  #include <sys/uio.h>
6
6
  #include <unistd.h>
7
- #include <fcntl.h>
8
7
  #include <netinet/in.h>
9
8
  #include <arpa/inet.h>
10
9
  #include <stdnoreturn.h>
@@ -19,6 +18,7 @@
19
18
  #include "backend_io_uring_context.h"
20
19
  #include "ruby/thread.h"
21
20
  #include "ruby/io.h"
21
+ #include "backend_common.h"
22
22
 
23
23
  VALUE SYM_io_uring;
24
24
  VALUE SYM_send;
@@ -26,34 +26,13 @@ VALUE SYM_splice;
26
26
  VALUE SYM_write;
27
27
 
28
28
  #ifdef POLYPHONY_UNSET_NONBLOCK
29
- ID ID_ivar_is_nonblocking;
30
-
31
- // One of the changes introduced in Ruby 3.0 as part of the work on the
32
- // FiberScheduler interface is that all created sockets are marked as
33
- // non-blocking. This prevents the io_uring backend from working correctly,
34
- // since it will return an EAGAIN error just like a normal syscall. So here
35
- // instead of setting O_NONBLOCK (which is required for the libev backend), we
36
- // unset it.
37
- inline void io_unset_nonblock(rb_io_t *fptr, VALUE io) {
38
- VALUE is_nonblocking = rb_ivar_get(io, ID_ivar_is_nonblocking);
39
- if (is_nonblocking == Qfalse) return;
40
-
41
- rb_ivar_set(io, ID_ivar_is_nonblocking, Qfalse);
42
-
43
- int oflags = fcntl(fptr->fd, F_GETFL);
44
- if ((oflags == -1) && (oflags & O_NONBLOCK)) return;
45
- oflags &= !O_NONBLOCK;
46
- fcntl(fptr->fd, F_SETFL, oflags);
47
- }
29
+ #define io_unset_nonblock(fptr, io) io_verify_blocking_mode(fptr, io, Qtrue)
48
30
  #else
49
- // NOP
50
31
  #define io_unset_nonblock(fptr, io)
51
32
  #endif
52
33
 
53
34
  typedef struct Backend_t {
54
- // common fields
55
- unsigned int currently_polling;
56
- unsigned int pending_count;
35
+ struct Backend_base base;
57
36
 
58
37
  // implementation-specific fields
59
38
  struct io_uring ring;
@@ -63,8 +42,6 @@ typedef struct Backend_t {
63
42
  int event_fd;
64
43
  } Backend_t;
65
44
 
66
- #include "backend_common.h"
67
-
68
45
  static size_t Backend_size(const void *ptr) {
69
46
  return sizeof(Backend_t);
70
47
  }
@@ -88,8 +65,11 @@ static VALUE Backend_initialize(VALUE self) {
88
65
  Backend_t *backend;
89
66
  GetBackend(self, backend);
90
67
 
91
- backend->currently_polling = 0;
92
- backend->pending_count = 0;
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
+
93
73
  backend->pending_sqes = 0;
94
74
  backend->prepared_limit = 2048;
95
75
 
@@ -117,8 +97,8 @@ VALUE Backend_post_fork(VALUE self) {
117
97
  io_uring_queue_exit(&backend->ring);
118
98
  io_uring_queue_init(backend->prepared_limit, &backend->ring, 0);
119
99
  context_store_free(&backend->store);
120
- backend->currently_polling = 0;
121
- backend->pending_count = 0;
100
+ backend->base.currently_polling = 0;
101
+ backend->base.pending_count = 0;
122
102
  backend->pending_sqes = 0;
123
103
 
124
104
  return self;
@@ -128,7 +108,7 @@ unsigned int Backend_pending_count(VALUE self) {
128
108
  Backend_t *backend;
129
109
  GetBackend(self, backend);
130
110
 
131
- return backend->pending_count;
111
+ return backend->base.pending_count;
132
112
  }
133
113
 
134
114
  typedef struct poll_context {
@@ -197,9 +177,9 @@ void io_uring_backend_poll(Backend_t *backend) {
197
177
  io_uring_submit(&backend->ring);
198
178
  }
199
179
 
200
- backend->currently_polling = 1;
180
+ backend->base.currently_polling = 1;
201
181
  rb_thread_call_without_gvl(io_uring_backend_poll_without_gvl, (void *)&poll_ctx, RUBY_UBF_IO, 0);
202
- backend->currently_polling = 0;
182
+ backend->base.currently_polling = 0;
203
183
  if (poll_ctx.result < 0) return;
204
184
 
205
185
  io_uring_backend_handle_completion(poll_ctx.cqe, backend);
@@ -228,7 +208,7 @@ VALUE Backend_wakeup(VALUE self) {
228
208
  Backend_t *backend;
229
209
  GetBackend(self, backend);
230
210
 
231
- if (backend->currently_polling) {
211
+ if (backend->base.currently_polling) {
232
212
  // Since we're currently blocking while waiting for a completion, we add a
233
213
  // NOP which would cause the io_uring_enter syscall to return
234
214
  struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
@@ -263,7 +243,7 @@ int io_uring_backend_defer_submit_and_await(
263
243
  io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
264
244
  io_uring_backend_defer_submit(backend);
265
245
 
266
- switchpoint_result = backend_await(backend);
246
+ switchpoint_result = backend_await((struct Backend_base *)backend);
267
247
 
268
248
  if (ctx->ref_count > 1) {
269
249
  // op was not completed (an exception was raised), so we need to cancel it
@@ -1177,7 +1157,7 @@ VALUE Backend_chain(int argc,VALUE *argv, VALUE self) {
1177
1157
 
1178
1158
  ctx->ref_count = sqe_count + 1;
1179
1159
  io_uring_backend_defer_submit(backend);
1180
- resume_value = backend_await(backend);
1160
+ resume_value = backend_await((struct Backend_base *)backend);
1181
1161
  int result = ctx->result;
1182
1162
  int completed = context_store_release(&backend->store, ctx);
1183
1163
  if (!completed) {
@@ -1195,6 +1175,21 @@ VALUE Backend_chain(int argc,VALUE *argv, VALUE self) {
1195
1175
  return INT2NUM(result);
1196
1176
  }
1197
1177
 
1178
+ VALUE Backend_idle_gc_period_set(VALUE self, VALUE period) {
1179
+ Backend_t *backend;
1180
+ GetBackend(self, backend);
1181
+ backend->base.idle_gc_period = NUM2DBL(period);
1182
+ backend->base.idle_gc_last_time = current_time();
1183
+ return self;
1184
+ }
1185
+
1186
+ inline VALUE Backend_run_idle_tasks(VALUE self) {
1187
+ Backend_t *backend;
1188
+ GetBackend(self, backend);
1189
+ backend_run_idle_tasks(&backend->base);
1190
+ return self;
1191
+ }
1192
+
1198
1193
  void Init_Backend() {
1199
1194
  VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1200
1195
  rb_define_alloc_func(cBackend, Backend_allocate);
@@ -1207,6 +1202,7 @@ void Init_Backend() {
1207
1202
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
1208
1203
  rb_define_method(cBackend, "kind", Backend_kind, 0);
1209
1204
  rb_define_method(cBackend, "chain", Backend_chain, -1);
1205
+ rb_define_method(cBackend, "idle_gc_period=", Backend_idle_gc_period_set, 1);
1210
1206
 
1211
1207
  rb_define_method(cBackend, "accept", Backend_accept, 2);
1212
1208
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
@@ -42,7 +42,6 @@ thread.
42
42
  #define _GNU_SOURCE 1
43
43
  #endif
44
44
 
45
- #include <fcntl.h>
46
45
  #include <netdb.h>
47
46
  #include <sys/socket.h>
48
47
  #include <sys/uio.h>
@@ -52,44 +51,22 @@ thread.
52
51
  #include <stdnoreturn.h>
53
52
  #include <sys/types.h>
54
53
  #include <sys/wait.h>
54
+ #include <fcntl.h>
55
55
 
56
56
  #include "polyphony.h"
57
57
  #include "../libev/ev.h"
58
58
  #include "ruby/io.h"
59
59
 
60
+ #include "../libev/ev.h"
61
+ #include "backend_common.h"
62
+
60
63
  VALUE SYM_libev;
61
64
  VALUE SYM_send;
62
65
  VALUE SYM_splice;
63
66
  VALUE SYM_write;
64
67
 
65
- ID ID_ivar_is_nonblocking;
66
-
67
- // Since we need to ensure that fd's are non-blocking before every I/O
68
- // operation, here we improve upon Ruby's rb_io_set_nonblock by caching the
69
- // "nonblock" state in an instance variable. Calling rb_ivar_get on every read
70
- // is still much cheaper than doing a fcntl syscall on every read! Preliminary
71
- // benchmarks (with a "hello world" HTTP server) show throughput is improved
72
- // by 10-13%.
73
- inline void io_set_nonblock(rb_io_t *fptr, VALUE io) {
74
- VALUE is_nonblocking = rb_ivar_get(io, ID_ivar_is_nonblocking);
75
- if (is_nonblocking == Qtrue) return;
76
-
77
- rb_ivar_set(io, ID_ivar_is_nonblocking, Qtrue);
78
-
79
- #ifdef _WIN32
80
- rb_w32_set_nonblock(fptr->fd);
81
- #elif defined(F_GETFL)
82
- int oflags = fcntl(fptr->fd, F_GETFL);
83
- if ((oflags == -1) && (oflags & O_NONBLOCK)) return;
84
- oflags |= O_NONBLOCK;
85
- fcntl(fptr->fd, F_SETFL, oflags);
86
- #endif
87
- }
88
-
89
68
  typedef struct Backend_t {
90
- // common fields
91
- unsigned int currently_polling;
92
- unsigned int pending_count;
69
+ struct Backend_base base;
93
70
 
94
71
  // implementation-specific fields
95
72
  struct ev_loop *ev_loop;
@@ -142,8 +119,10 @@ static VALUE Backend_initialize(VALUE self) {
142
119
  // block when no other watcher is active
143
120
  ev_unref(backend->ev_loop);
144
121
 
145
- backend->currently_polling = 0;
146
- backend->pending_count = 0;
122
+ backend->base.currently_polling = 0;
123
+ backend->base.pending_count = 0;
124
+ backend->base.idle_gc_period = 0;
125
+ backend->base.idle_gc_last_time = 0;
147
126
 
148
127
  return Qnil;
149
128
  }
@@ -174,11 +153,11 @@ VALUE Backend_post_fork(VALUE self) {
174
153
  return self;
175
154
  }
176
155
 
177
- unsigned int Backend_pending_count(VALUE self) {
156
+ inline unsigned int Backend_pending_count(VALUE self) {
178
157
  Backend_t *backend;
179
158
  GetBackend(self, backend);
180
159
 
181
- return backend->pending_count;
160
+ return backend->base.pending_count;
182
161
  }
183
162
 
184
163
  VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue) {
@@ -186,9 +165,9 @@ VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue
186
165
  GetBackend(self, backend);
187
166
 
188
167
  COND_TRACE(2, SYM_fiber_event_poll_enter, current_fiber);
189
- backend->currently_polling = 1;
168
+ backend->base.currently_polling = 1;
190
169
  ev_run(backend->ev_loop, nowait == Qtrue ? EVRUN_NOWAIT : EVRUN_ONCE);
191
- backend->currently_polling = 0;
170
+ backend->base.currently_polling = 0;
192
171
  COND_TRACE(2, SYM_fiber_event_poll_leave, current_fiber);
193
172
 
194
173
  return self;
@@ -198,7 +177,7 @@ VALUE Backend_wakeup(VALUE self) {
198
177
  Backend_t *backend;
199
178
  GetBackend(self, backend);
200
179
 
201
- if (backend->currently_polling) {
180
+ if (backend->base.currently_polling) {
202
181
  // Since the loop will run until at least one event has occurred, we signal
203
182
  // the selector's associated async watcher, which will cause the ev loop to
204
183
  // return. In contrast to using `ev_break` to break out of the loop, which
@@ -211,10 +190,6 @@ VALUE Backend_wakeup(VALUE self) {
211
190
  return Qnil;
212
191
  }
213
192
 
214
- #include "../libev/ev.h"
215
-
216
- #include "backend_common.h"
217
-
218
193
  struct libev_io {
219
194
  struct ev_io io;
220
195
  VALUE fiber;
@@ -235,7 +210,7 @@ VALUE libev_wait_fd_with_watcher(Backend_t *backend, int fd, struct libev_io *wa
235
210
  }
236
211
  ev_io_start(backend->ev_loop, &watcher->io);
237
212
 
238
- switchpoint_result = backend_await(backend);
213
+ switchpoint_result = backend_await((struct Backend_base *)backend);
239
214
 
240
215
  ev_io_stop(backend->ev_loop, &watcher->io);
241
216
  RB_GC_GUARD(switchpoint_result);
@@ -271,7 +246,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
271
246
  if (underlying_io != Qnil) io = underlying_io;
272
247
  GetOpenFile(io, fptr);
273
248
  rb_io_check_byte_readable(fptr);
274
- io_set_nonblock(fptr, io);
249
+ io_verify_blocking_mode(fptr, io, Qfalse);
275
250
  rectify_io_file_pos(fptr);
276
251
  watcher.fiber = Qnil;
277
252
  OBJ_TAINT(str);
@@ -288,7 +263,6 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
288
263
  }
289
264
  else {
290
265
  switchpoint_result = backend_snooze();
291
-
292
266
  if (TEST_EXCEPTION(switchpoint_result)) goto error;
293
267
 
294
268
  if (n == 0) break; // EOF
@@ -343,7 +317,7 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
343
317
  if (underlying_io != Qnil) io = underlying_io;
344
318
  GetOpenFile(io, fptr);
345
319
  rb_io_check_byte_readable(fptr);
346
- io_set_nonblock(fptr, io);
320
+ io_verify_blocking_mode(fptr, io, Qfalse);
347
321
  rectify_io_file_pos(fptr);
348
322
  watcher.fiber = Qnil;
349
323
 
@@ -395,7 +369,7 @@ VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
395
369
  if (underlying_io != Qnil) io = underlying_io;
396
370
  GetOpenFile(io, fptr);
397
371
  rb_io_check_byte_readable(fptr);
398
- io_set_nonblock(fptr, io);
372
+ io_verify_blocking_mode(fptr, io, Qfalse);
399
373
  rectify_io_file_pos(fptr);
400
374
  watcher.fiber = Qnil;
401
375
 
@@ -443,7 +417,7 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
443
417
  GetBackend(self, backend);
444
418
  io = rb_io_get_write_io(io);
445
419
  GetOpenFile(io, fptr);
446
- io_set_nonblock(fptr, io);
420
+ io_verify_blocking_mode(fptr, io, Qfalse);
447
421
  watcher.fiber = Qnil;
448
422
 
449
423
  while (left > 0) {
@@ -493,7 +467,7 @@ VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
493
467
  GetBackend(self, backend);
494
468
  io = rb_io_get_write_io(io);
495
469
  GetOpenFile(io, fptr);
496
- io_set_nonblock(fptr, io);
470
+ io_verify_blocking_mode(fptr, io, Qfalse);
497
471
  watcher.fiber = Qnil;
498
472
 
499
473
  iov = malloc(iov_count * sizeof(struct iovec));
@@ -574,7 +548,7 @@ VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class) {
574
548
 
575
549
  GetBackend(self, backend);
576
550
  GetOpenFile(server_socket, fptr);
577
- io_set_nonblock(fptr, server_socket);
551
+ io_verify_blocking_mode(fptr, server_socket, Qfalse);
578
552
  watcher.fiber = Qnil;
579
553
  while (1) {
580
554
  fd = accept(fptr->fd, &addr, &len);
@@ -602,7 +576,7 @@ VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class) {
602
576
  fp->fd = fd;
603
577
  fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
604
578
  rb_io_ascii8bit_binmode(socket);
605
- io_set_nonblock(fp, socket);
579
+ io_verify_blocking_mode(fp, socket, Qfalse);
606
580
  rb_io_synchronized(fp);
607
581
 
608
582
  // if (rsock_do_not_reverse_lookup) {
@@ -631,7 +605,7 @@ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
631
605
 
632
606
  GetBackend(self, backend);
633
607
  GetOpenFile(server_socket, fptr);
634
- io_set_nonblock(fptr, server_socket);
608
+ io_verify_blocking_mode(fptr, server_socket, Qfalse);
635
609
  watcher.fiber = Qnil;
636
610
 
637
611
  while (1) {
@@ -659,7 +633,7 @@ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
659
633
  fp->fd = fd;
660
634
  fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
661
635
  rb_io_ascii8bit_binmode(socket);
662
- io_set_nonblock(fp, socket);
636
+ io_verify_blocking_mode(fp, socket, Qfalse);
663
637
  rb_io_synchronized(fp);
664
638
 
665
639
  rb_yield(socket);
@@ -687,7 +661,7 @@ VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
687
661
 
688
662
  GetBackend(self, backend);
689
663
  GetOpenFile(sock, fptr);
690
- io_set_nonblock(fptr, sock);
664
+ io_verify_blocking_mode(fptr, sock, Qfalse);
691
665
  watcher.fiber = Qnil;
692
666
 
693
667
  addr.sin_family = AF_INET;
@@ -730,7 +704,7 @@ VALUE Backend_send(VALUE self, VALUE io, VALUE str, VALUE flags) {
730
704
  GetBackend(self, backend);
731
705
  io = rb_io_get_write_io(io);
732
706
  GetOpenFile(io, fptr);
733
- io_set_nonblock(fptr, io);
707
+ io_verify_blocking_mode(fptr, io, Qfalse);
734
708
  watcher.fiber = Qnil;
735
709
 
736
710
  while (left > 0) {
@@ -778,13 +752,13 @@ VALUE Backend_splice(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
778
752
  underlying_io = rb_ivar_get(src, ID_ivar_io);
779
753
  if (underlying_io != Qnil) src = underlying_io;
780
754
  GetOpenFile(src, src_fptr);
781
- io_set_nonblock(src_fptr, src);
755
+ io_verify_blocking_mode(src_fptr, src, Qfalse);
782
756
 
783
757
  underlying_io = rb_ivar_get(dest, ID_ivar_io);
784
758
  if (underlying_io != Qnil) dest = underlying_io;
785
759
  dest = rb_io_get_write_io(dest);
786
760
  GetOpenFile(dest, dest_fptr);
787
- io_set_nonblock(dest_fptr, dest);
761
+ io_verify_blocking_mode(dest_fptr, dest, Qfalse);
788
762
 
789
763
  watcher.fiber = Qnil;
790
764
  while (1) {
@@ -832,13 +806,13 @@ VALUE Backend_splice_to_eof(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
832
806
  underlying_io = rb_ivar_get(src, ID_ivar_io);
833
807
  if (underlying_io != Qnil) src = underlying_io;
834
808
  GetOpenFile(src, src_fptr);
835
- io_set_nonblock(src_fptr, src);
809
+ io_verify_blocking_mode(src_fptr, src, Qfalse);
836
810
 
837
811
  underlying_io = rb_ivar_get(dest, ID_ivar_io);
838
812
  if (underlying_io != Qnil) dest = underlying_io;
839
813
  dest = rb_io_get_write_io(dest);
840
814
  GetOpenFile(dest, dest_fptr);
841
- io_set_nonblock(dest_fptr, dest);
815
+ io_verify_blocking_mode(dest_fptr, dest, Qfalse);
842
816
 
843
817
  watcher.fiber = Qnil;
844
818
  while (1) {
@@ -875,6 +849,157 @@ error:
875
849
  }
876
850
  #endif
877
851
 
852
+ VALUE Backend_fake_splice(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
853
+ Backend_t *backend;
854
+ struct libev_io watcher;
855
+ VALUE switchpoint_result = Qnil;
856
+ VALUE underlying_io;
857
+ rb_io_t *src_fptr;
858
+ rb_io_t *dest_fptr;
859
+ int len = NUM2INT(maxlen);
860
+ VALUE str = rb_str_new(0, len);
861
+ char *buf = RSTRING_PTR(str);
862
+ int left = 0;
863
+ int total = 0;
864
+
865
+ GetBackend(self, backend);
866
+
867
+ underlying_io = rb_ivar_get(src, ID_ivar_io);
868
+ if (underlying_io != Qnil) src = underlying_io;
869
+ GetOpenFile(src, src_fptr);
870
+ io_verify_blocking_mode(src_fptr, src, Qfalse);
871
+
872
+ underlying_io = rb_ivar_get(dest, ID_ivar_io);
873
+ if (underlying_io != Qnil) dest = underlying_io;
874
+ dest = rb_io_get_write_io(dest);
875
+ GetOpenFile(dest, dest_fptr);
876
+ io_verify_blocking_mode(dest_fptr, dest, Qfalse);
877
+
878
+ watcher.fiber = Qnil;
879
+
880
+ while (1) {
881
+ ssize_t n = read(src_fptr->fd, buf, len);
882
+ if (n < 0) {
883
+ int e = errno;
884
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
885
+
886
+ switchpoint_result = libev_wait_fd_with_watcher(backend, src_fptr->fd, &watcher, EV_READ);
887
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
888
+ }
889
+ else {
890
+ total = left = n;
891
+ break;
892
+ }
893
+ }
894
+
895
+ while (left > 0) {
896
+ ssize_t n = write(dest_fptr->fd, buf, left);
897
+ if (n < 0) {
898
+ int e = errno;
899
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
900
+
901
+ switchpoint_result = libev_wait_fd_with_watcher(backend, dest_fptr->fd, &watcher, EV_WRITE);
902
+
903
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
904
+ }
905
+ else {
906
+ buf += n;
907
+ left -= n;
908
+ }
909
+ }
910
+
911
+ if (watcher.fiber == Qnil) {
912
+ switchpoint_result = backend_snooze();
913
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
914
+ }
915
+
916
+ RB_GC_GUARD(watcher.fiber);
917
+ RB_GC_GUARD(switchpoint_result);
918
+ RB_GC_GUARD(str);
919
+
920
+ return INT2NUM(total);
921
+ error:
922
+ return RAISE_EXCEPTION(switchpoint_result);
923
+ }
924
+
925
+ VALUE Backend_fake_splice_to_eof(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
926
+ Backend_t *backend;
927
+ struct libev_io watcher;
928
+ VALUE switchpoint_result = Qnil;
929
+ VALUE underlying_io;
930
+ rb_io_t *src_fptr;
931
+ rb_io_t *dest_fptr;
932
+ int len = NUM2INT(maxlen);
933
+ VALUE str = rb_str_new(0, len);
934
+ char *buf = RSTRING_PTR(str);
935
+ int left = 0;
936
+ int total = 0;
937
+
938
+ GetBackend(self, backend);
939
+
940
+ underlying_io = rb_ivar_get(src, ID_ivar_io);
941
+ if (underlying_io != Qnil) src = underlying_io;
942
+ GetOpenFile(src, src_fptr);
943
+ io_verify_blocking_mode(src_fptr, src, Qfalse);
944
+
945
+ underlying_io = rb_ivar_get(dest, ID_ivar_io);
946
+ if (underlying_io != Qnil) dest = underlying_io;
947
+ dest = rb_io_get_write_io(dest);
948
+ GetOpenFile(dest, dest_fptr);
949
+ io_verify_blocking_mode(dest_fptr, dest, Qfalse);
950
+
951
+ watcher.fiber = Qnil;
952
+
953
+ while (1) {
954
+ char *ptr = buf;
955
+ while (1) {
956
+ ssize_t n = read(src_fptr->fd, ptr, len);
957
+ if (n < 0) {
958
+ int e = errno;
959
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
960
+
961
+ switchpoint_result = libev_wait_fd_with_watcher(backend, src_fptr->fd, &watcher, EV_READ);
962
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
963
+ }
964
+ else if (n == 0) goto done;
965
+ else {
966
+ total += n;
967
+ left = n;
968
+ break;
969
+ }
970
+ }
971
+
972
+ while (left > 0) {
973
+ ssize_t n = write(dest_fptr->fd, ptr, left);
974
+ if (n < 0) {
975
+ int e = errno;
976
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
977
+
978
+ switchpoint_result = libev_wait_fd_with_watcher(backend, dest_fptr->fd, &watcher, EV_WRITE);
979
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
980
+ }
981
+ else {
982
+ ptr += n;
983
+ left -= n;
984
+ }
985
+ }
986
+ }
987
+
988
+ done:
989
+ if (watcher.fiber == Qnil) {
990
+ switchpoint_result = backend_snooze();
991
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
992
+ }
993
+
994
+ RB_GC_GUARD(watcher.fiber);
995
+ RB_GC_GUARD(switchpoint_result);
996
+ RB_GC_GUARD(str);
997
+
998
+ return INT2NUM(total);
999
+ error:
1000
+ return RAISE_EXCEPTION(switchpoint_result);
1001
+ }
1002
+
878
1003
  VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
879
1004
  Backend_t *backend;
880
1005
  rb_io_t *fptr;
@@ -908,7 +1033,7 @@ VALUE Backend_sleep(VALUE self, VALUE duration) {
908
1033
  ev_timer_init(&watcher.timer, Backend_timer_callback, NUM2DBL(duration), 0.);
909
1034
  ev_timer_start(backend->ev_loop, &watcher.timer);
910
1035
 
911
- switchpoint_result = backend_await(backend);
1036
+ switchpoint_result = backend_await((struct Backend_base *)backend);
912
1037
 
913
1038
  ev_timer_stop(backend->ev_loop, &watcher.timer);
914
1039
  RAISE_IF_EXCEPTION(switchpoint_result);
@@ -936,7 +1061,7 @@ noreturn VALUE Backend_timer_loop(VALUE self, VALUE interval) {
936
1061
  VALUE switchpoint_result = Qnil;
937
1062
  ev_timer_init(&watcher.timer, Backend_timer_callback, sleep_duration, 0.);
938
1063
  ev_timer_start(backend->ev_loop, &watcher.timer);
939
- switchpoint_result = backend_await(backend);
1064
+ switchpoint_result = backend_await((struct Backend_base *)backend);
940
1065
  ev_timer_stop(backend->ev_loop, &watcher.timer);
941
1066
  RAISE_IF_EXCEPTION(switchpoint_result);
942
1067
  RB_GC_GUARD(switchpoint_result);
@@ -1053,7 +1178,7 @@ VALUE Backend_waitpid(VALUE self, VALUE pid) {
1053
1178
  ev_child_init(&watcher.child, Backend_child_callback, NUM2INT(pid), 0);
1054
1179
  ev_child_start(backend->ev_loop, &watcher.child);
1055
1180
 
1056
- switchpoint_result = backend_await(backend);
1181
+ switchpoint_result = backend_await((struct Backend_base *)backend);
1057
1182
 
1058
1183
  ev_child_stop(backend->ev_loop, &watcher.child);
1059
1184
  RAISE_IF_EXCEPTION(switchpoint_result);
@@ -1075,7 +1200,7 @@ VALUE Backend_wait_event(VALUE self, VALUE raise) {
1075
1200
  ev_async_init(&async, Backend_async_callback);
1076
1201
  ev_async_start(backend->ev_loop, &async);
1077
1202
 
1078
- switchpoint_result = backend_await(backend);
1203
+ switchpoint_result = backend_await((struct Backend_base *)backend);
1079
1204
 
1080
1205
  ev_async_stop(backend->ev_loop, &async);
1081
1206
  if (RTEST(raise)) RAISE_IF_EXCEPTION(switchpoint_result);
@@ -1112,6 +1237,21 @@ VALUE Backend_chain(int argc,VALUE *argv, VALUE self) {
1112
1237
  return result;
1113
1238
  }
1114
1239
 
1240
+ VALUE Backend_idle_gc_period_set(VALUE self, VALUE period) {
1241
+ Backend_t *backend;
1242
+ GetBackend(self, backend);
1243
+ backend->base.idle_gc_period = NUM2DBL(period);
1244
+ backend->base.idle_gc_last_time = current_time();
1245
+ return self;
1246
+ }
1247
+
1248
+ inline VALUE Backend_run_idle_tasks(VALUE self) {
1249
+ Backend_t *backend;
1250
+ GetBackend(self, backend);
1251
+ backend_run_idle_tasks(&backend->base);
1252
+ return self;
1253
+ }
1254
+
1115
1255
  void Init_Backend() {
1116
1256
  ev_set_allocator(xrealloc);
1117
1257
 
@@ -1126,6 +1266,7 @@ void Init_Backend() {
1126
1266
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
1127
1267
  rb_define_method(cBackend, "kind", Backend_kind, 0);
1128
1268
  rb_define_method(cBackend, "chain", Backend_chain, -1);
1269
+ rb_define_method(cBackend, "idle_gc_period=", Backend_idle_gc_period_set, 1);
1129
1270
 
1130
1271
  rb_define_method(cBackend, "accept", Backend_accept, 2);
1131
1272
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
@@ -1143,6 +1284,9 @@ void Init_Backend() {
1143
1284
  #ifdef POLYPHONY_LINUX
1144
1285
  rb_define_method(cBackend, "splice", Backend_splice, 3);
1145
1286
  rb_define_method(cBackend, "splice_to_eof", Backend_splice_to_eof, 3);
1287
+ #else
1288
+ rb_define_method(cBackend, "splice", Backend_fake_splice, 3);
1289
+ rb_define_method(cBackend, "splice_to_eof", Backend_fake_splice_to_eof, 3);
1146
1290
  #endif
1147
1291
 
1148
1292
  rb_define_method(cBackend, "timeout", Backend_timeout, -1);
@@ -1152,7 +1296,6 @@ void Init_Backend() {
1152
1296
  rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1153
1297
  rb_define_method(cBackend, "write", Backend_write_m, -1);
1154
1298
 
1155
- ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
1156
1299
  SYM_libev = ID2SYM(rb_intern("libev"));
1157
1300
 
1158
1301
  SYM_send = ID2SYM(rb_intern("send"));
@@ -10,6 +10,7 @@ ID ID_each;
10
10
  ID ID_inspect;
11
11
  ID ID_invoke;
12
12
  ID ID_new;
13
+ ID ID_ivar_blocking_mode;
13
14
  ID ID_ivar_io;
14
15
  ID ID_ivar_runnable;
15
16
  ID ID_ivar_running;
@@ -158,19 +159,20 @@ void Init_Polyphony() {
158
159
 
159
160
  cTimeoutException = rb_define_class_under(mPolyphony, "TimeoutException", rb_eException);
160
161
 
161
- ID_call = rb_intern("call");
162
- ID_caller = rb_intern("caller");
163
- ID_clear = rb_intern("clear");
164
- ID_each = rb_intern("each");
165
- ID_inspect = rb_intern("inspect");
166
- ID_invoke = rb_intern("invoke");
167
- ID_ivar_io = rb_intern("@io");
168
- ID_ivar_runnable = rb_intern("@runnable");
169
- ID_ivar_running = rb_intern("@running");
170
- ID_ivar_thread = rb_intern("@thread");
171
- ID_new = rb_intern("new");
172
- ID_signal = rb_intern("signal");
173
- ID_size = rb_intern("size");
174
- ID_switch_fiber = rb_intern("switch_fiber");
175
- ID_transfer = rb_intern("transfer");
162
+ ID_call = rb_intern("call");
163
+ ID_caller = rb_intern("caller");
164
+ ID_clear = rb_intern("clear");
165
+ ID_each = rb_intern("each");
166
+ ID_inspect = rb_intern("inspect");
167
+ ID_invoke = rb_intern("invoke");
168
+ ID_ivar_blocking_mode = rb_intern("@blocking_mode");
169
+ ID_ivar_io = rb_intern("@io");
170
+ ID_ivar_runnable = rb_intern("@runnable");
171
+ ID_ivar_running = rb_intern("@running");
172
+ ID_ivar_thread = rb_intern("@thread");
173
+ ID_new = rb_intern("new");
174
+ ID_signal = rb_intern("signal");
175
+ ID_size = rb_intern("size");
176
+ ID_switch_fiber = rb_intern("switch_fiber");
177
+ ID_transfer = rb_intern("transfer");
176
178
  }
@@ -47,6 +47,7 @@ extern ID ID_fiber_trace;
47
47
  extern ID ID_inspect;
48
48
  extern ID ID_invoke;
49
49
  extern ID ID_ivar_backend;
50
+ extern ID ID_ivar_blocking_mode;
50
51
  extern ID ID_ivar_io;
51
52
  extern ID ID_ivar_runnable;
52
53
  extern ID ID_ivar_running;
@@ -124,6 +125,7 @@ unsigned int Backend_pending_count(VALUE self);
124
125
  VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue);
125
126
  VALUE Backend_wait_event(VALUE self, VALUE raise_on_exception);
126
127
  VALUE Backend_wakeup(VALUE self);
128
+ VALUE Backend_run_idle_tasks(VALUE self);
127
129
 
128
130
  VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
129
131
  VALUE Thread_schedule_fiber_with_priority(VALUE thread, VALUE fiber, VALUE value);
@@ -88,6 +88,7 @@ VALUE Thread_switch_fiber(VALUE self) {
88
88
  VALUE backend = rb_ivar_get(self, ID_ivar_backend);
89
89
  unsigned int pending_ops_count = Backend_pending_count(backend);
90
90
  unsigned int backend_was_polled = 0;
91
+ unsigned int idle_tasks_run_count = 0;
91
92
 
92
93
  if (__tracing_enabled__ && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
93
94
  TRACE(2, SYM_fiber_switchpoint, current_fiber);
@@ -112,6 +113,10 @@ VALUE Thread_switch_fiber(VALUE self) {
112
113
  break;
113
114
  }
114
115
 
116
+ if (!idle_tasks_run_count) {
117
+ idle_tasks_run_count++;
118
+ Backend_run_idle_tasks(backend);
119
+ }
115
120
  if (pending_ops_count == 0) break;
116
121
  Backend_poll(backend, Qnil, current_fiber, runqueue);
117
122
  backend_was_polled = 1;
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.55.0'
4
+ VERSION = '0.56.0'
5
5
  end
data/test/test_backend.rb CHANGED
@@ -281,6 +281,33 @@ class BackendTest < MiniTest::Test
281
281
  f.await
282
282
  end
283
283
  end
284
+
285
+ def test_idle_gc
286
+ GC.disable
287
+
288
+ count = GC.count
289
+ snooze
290
+ assert_equal count, GC.count
291
+ sleep 0.01
292
+ assert_equal count, GC.count
293
+
294
+ @backend.idle_gc_period = 0.1
295
+ snooze
296
+ assert_equal count, GC.count
297
+ sleep 0.05
298
+ assert_equal count, GC.count
299
+ # The idle tasks are ran at most once per fiber switch, before the backend
300
+ # is polled. Therefore, the second sleep will not have triggered a GC, since
301
+ # only 0.05s have passed since the gc period was set.
302
+ sleep 0.07
303
+ assert_equal count, GC.count
304
+ # Upon the third sleep the GC should be triggered, at 0.12s post setting the
305
+ # GC period.
306
+ sleep 0.05
307
+ assert_equal count + 1, GC.count
308
+ ensure
309
+ GC.enable
310
+ end
284
311
  end
285
312
 
286
313
  class BackendChainTest < MiniTest::Test
data/test/test_timer.rb CHANGED
@@ -48,7 +48,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
48
48
  t1 = Time.now
49
49
 
50
50
  assert_nil v
51
- assert_in_range 0.015..0.03, t1 - t0
51
+ assert_in_range 0.015..0.04, t1 - t0
52
52
  end
53
53
  end
54
54
 
@@ -76,17 +76,17 @@ class TimerCancelAfterTest < MiniTest::Test
76
76
 
77
77
  def test_timer_cancel_after_with_reset
78
78
  buf = []
79
- @timer.cancel_after(0.01) do
80
- sleep 0.005
79
+ @timer.cancel_after(0.13) do
80
+ sleep 0.05
81
81
  buf << 1
82
82
  @timer.reset
83
- sleep 0.005
83
+ sleep 0.05
84
84
  buf << 2
85
85
  @timer.reset
86
- sleep 0.005
86
+ sleep 0.05
87
87
  buf << 3
88
88
  @timer.reset
89
- sleep 0.005
89
+ sleep 0.05
90
90
  buf << 4
91
91
  end
92
92
  assert_equal [1, 2, 3, 4], buf
@@ -158,6 +158,6 @@ class TimerMiscTest < MiniTest::Test
158
158
  end
159
159
  sleep 0.05
160
160
  f.stop
161
- assert_in_range 4..6, buffer.size
161
+ assert_in_range 3..7, buffer.size
162
162
  end
163
163
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.55.0
4
+ version: 0.56.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-17 00:00:00.000000000 Z
11
+ date: 2021-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -324,6 +324,7 @@ files:
324
324
  - examples/core/erlang-style-genserver.rb
325
325
  - examples/core/forking.rb
326
326
  - examples/core/handling-signals.rb
327
+ - examples/core/idle_gc.rb
327
328
  - examples/core/interrupt.rb
328
329
  - examples/core/nested.rb
329
330
  - examples/core/pingpong.rb
@@ -355,6 +356,7 @@ files:
355
356
  - examples/io/irb.rb
356
357
  - examples/io/net-http.rb
357
358
  - examples/io/open.rb
359
+ - examples/io/pipe.rb
358
360
  - examples/io/pry.rb
359
361
  - examples/io/rack_server.rb
360
362
  - examples/io/raw.rb
@@ -414,6 +416,7 @@ files:
414
416
  - ext/liburing/setup.c
415
417
  - ext/liburing/syscall.c
416
418
  - ext/liburing/syscall.h
419
+ - ext/polyphony/backend_common.c
417
420
  - ext/polyphony/backend_common.h
418
421
  - ext/polyphony/backend_io_uring.c
419
422
  - ext/polyphony/backend_io_uring_context.c