polyphony 0.55.0 → 0.56.0

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: 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