polyphony 0.54.0 → 0.59

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: 7c88e804739b62a55f7647c57e55582d2ae5cf6a73bca55160aa90138b80217a
4
- data.tar.gz: ecbeb0aeed96a7b6f21f8d41b67195574213b8b01b51bccd0f1bcf59165ab9f6
3
+ metadata.gz: 313e33321df0e375d128a79fccfab98e06183adbdbde7916347f6795f56ca369
4
+ data.tar.gz: 6428c0516544bdb955fa465bf36c40b9b2f162306f3e5689848ff487f24c4440
5
5
  SHA512:
6
- metadata.gz: 93ff227cab8891c79bc4447c25359df03cd675ada579d13c5dc09d1f18e42fd00ed1b3ce5e433bd5addf83076af3a45c29cdd49ae8cb2cb9f19892cb27a40476
7
- data.tar.gz: 1e1f39c19d2edbd49406c3a3917848951914c48b81316fff6ff78f381fb7db70b407dc5428b94a45292261e202df1c91da5d4e971ab19ba91e7c96e0bbbb91bc
6
+ metadata.gz: 40c88f5d1c2aa4307dea78dcf1a2ba8d347ef252e7e56e67d466c291066d39544d051864ccd0138dd94ca0beef7af71f5257052b3556ab0c7294b1b94b517d47
7
+ data.tar.gz: 109410d6b222846b0c2b3f54061358ef922b57c9b6559c37aec36f27116e7ca389ff51762e988e2e9293a7a1358d97b80ada74daa4e44d7d25c1e5335351d8c6
data/.gitignore CHANGED
@@ -56,4 +56,6 @@ lib/*.bundle
56
56
  lib/*.so
57
57
 
58
58
  _site
59
- .sass-cache
59
+ .sass-cache
60
+
61
+ log
data/CHANGELOG.md CHANGED
@@ -1,4 +1,33 @@
1
- ## 0.54.0
1
+ ## 0.59 2021-06-28
2
+
3
+ - Redesign tracing mechanism and API - now completely separated from Ruby core
4
+ trace API
5
+ - Refactor C code - move run queue into backend
6
+
7
+ ## 0.58 2021-06-25
8
+
9
+ - Implement `Thread#idle_gc_period`, `#on_idle` (#56)
10
+ - Implement `Backend#idle_block=` (#56)
11
+
12
+ ## 0.57.0 2021-06-23
13
+
14
+ - Implement `Backend#splice_chunks` method for both libev and io_uring backends
15
+ - Improve waiting for readiness in libev `Backend#splice`, `#splice_to_eof`
16
+ - Enable splice op in libev `Backend#chain` for non-Linux OS
17
+
18
+ ## 0.56.0 2021-06-22
19
+
20
+ - Implement fake `Backend#splice`, `Backend#splice_to_eof` methods for non-Linux
21
+ OS
22
+
23
+ ## 0.55.0 2021-06-17
24
+
25
+ - Finish io_uring implementation of Backend#chain
26
+ - Reimplement io_uring op_context acquire/release algorithm (using ref count)
27
+ - Fix #gets on sockets
28
+ - Redesign event anti-starvation mechanism
29
+
30
+ ## 0.54.0 2021-06-14
2
31
 
3
32
  - Implement Mutex#owned?, #locked? (#50)
4
33
  - Fix arity for SSLSocket#peeraddr (#55)
@@ -6,15 +35,15 @@
6
35
  - Fix SSLSocket buffering behaviour
7
36
  - Add recv_loop alias for SSLSocket (#54)
8
37
 
9
- ## 0.53.2
38
+ ## 0.53.2 2021-05-10
10
39
 
11
40
  - Remove `splice` methods on libev backend on non-Linux OS (#43)
12
41
 
13
- ## 0.53.0
42
+ ## 0.53.0 2021-04-23
14
43
 
15
44
  - Implement `Backend#splice`, `Backend#splice_to_eof`, along with `IO#splice`, `IO#splice_to_eof`
16
45
 
17
- ## 0.52.0
46
+ ## 0.52.0 2021-02-28
18
47
 
19
48
  - Polyphony is now compatible with Ruby 3.0
20
49
  - Add `Backend#sendv` method for sending multiple strings
@@ -24,19 +53,19 @@
24
53
  - libev backend: Use` pidfd_open` for Linux 5.3+, otherwise use a libev child watcher
25
54
  - Use `:call` as default method in `#feed_loop`
26
55
 
27
- ## 0.51.0
56
+ ## 0.51.0 2021-02-02
28
57
 
29
58
  - Implement `IO#feed_loop`, `Socket#feed_loop`
30
59
  - Fix error handling in `Process.kill_and_await`
31
60
 
32
- ## 0.50.1
61
+ ## 0.50.1 2021-01-31
33
62
 
34
63
  - Set `IOSQE_ASYNC` flag in io_uring backend
35
64
  - Fix error handling in `Backend#waitpid`
36
65
  - Reimplement libev backend's `#waitpid` by using pidfd_open (in similar manner
37
66
  to the io_uring backend)
38
67
 
39
- ## 0.50.0
68
+ ## 0.50.0 2021-01-28
40
69
 
41
70
  - Use `Process::CLOCK_MONOTONIC` in Timer
42
71
  - Add `Timer#sleep`, `Timer#after`, `Timer#every`
@@ -44,50 +73,50 @@
44
73
  - Add `Thread#fiber_index_of` method
45
74
  - Use `Backend#wait_event` in `Fiber#await`
46
75
 
47
- ## 0.49.2
76
+ ## 0.49.2 2021-01-19
48
77
 
49
78
  - Fix hang with 100s or more child fibers when terminating
50
79
  - Fix double pending_count increment in io_uring backend
51
80
 
52
- ## 0.49.1
81
+ ## 0.49.1 2021-01-13
53
82
 
54
83
  - Use `TCPSocket` instead of `Socket` in `Net.tcp_connect`
55
84
  - Catch `Errno::ERSCH` in `Process.kill_and_await`
56
85
  - Set io_uring queue size to 2048
57
86
 
58
- ## 0.49.0
87
+ ## 0.49.0 2021-01-11
59
88
 
60
89
  - Implement `Polyphony::Timer` for performant timeouts
61
90
 
62
- ## 0.48.0
91
+ ## 0.48.0 2021-01-05
63
92
 
64
93
  - Implement graceful shutdown
65
94
  - Add support for `break` / `StopIteration` in `spin_loop`
66
95
  - Fix `IO#gets`, `IO#readpartial`
67
96
 
68
- ## 0.47.5.1
97
+ ## 0.47.5.1 2020-11-20
69
98
 
70
99
  - Add missing `Socket#accept_loop` method
71
100
 
72
- ## 0.47.5
101
+ ## 0.47.5 2020-11-20
73
102
 
74
103
  - Add `socket_class` argument to `Backend#accept`, `Backend#accept_loop`
75
104
  - Fix `#supervise` to stop when all children fibers are done
76
105
 
77
- ## 0.47.4
106
+ ## 0.47.4 2020-11-14
78
107
 
79
108
  - Add support for Unix sockets
80
109
 
81
- ## 0.47.3
110
+ ## 0.47.3 2020-11-12
82
111
 
83
112
  - Enable I/O in signal handlers (#45)
84
113
  - Accept `:interval` argument in `#spin_loop`
85
114
 
86
- ## 0.47.2
115
+ ## 0.47.2 2020-11-10
87
116
 
88
117
  - Fix API compatibility between TCPSocket and IO
89
118
 
90
- ## 0.47.0
119
+ ## 0.47.0 2020-11-10
91
120
 
92
121
  - Implement `#spin_scope` used for creating blocking fiber scopes
93
122
  - Reimplement `move_on_after`, `cancel_after`, `Timeout.timeout` using
@@ -95,18 +124,18 @@
95
124
  - Implement `Backend#timeout` API
96
125
  - Implemented capped queues
97
126
 
98
- ## 0.46.1
127
+ ## 0.46.1 2020-11-04
99
128
 
100
129
  - Add `TCPServer#accept_loop`, `OpenSSL::SSL::SSLSocket#accept_loop` method
101
130
  - Fix compilation error on MacOS (#43)
102
131
  - Fix backtrace for `Timeout.timeout`
103
132
  - Add `Backend#timer_loop`
104
133
 
105
- ## 0.46.0
134
+ ## 0.46.0 2020-10-08
106
135
 
107
136
  - Implement [io_uring backend](https://github.com/digital-fabric/polyphony/pull/44)
108
137
 
109
- ## 0.45.5
138
+ ## 0.45.5 2020-10-04
110
139
 
111
140
  - Fix compilation error (#43)
112
141
  - Add support for resetting move_on_after, cancel_after timeouts
@@ -115,22 +144,22 @@
115
144
  - Schedule parent with priority on uncaught exception
116
145
  - Fix race condition in `Mutex#synchronize` (#41)
117
146
 
118
- ## 0.45.4
147
+ ## 0.45.4 2020-09-06
119
148
 
120
149
  - Improve signal trapping mechanism
121
150
 
122
- ## 0.45.3
151
+ ## 0.45.3 2020-09-02
123
152
 
124
153
  - Don't swallow error in `Process#kill_and_await`
125
154
  - Add `Fiber#mailbox` attribute reader
126
155
  - Fix bug in `Fiber.await`
127
156
  - Implement `IO#getc`, `IO#getbyte`
128
157
 
129
- ## 0.45.2
158
+ ## 0.45.2 2020-08-03
130
159
 
131
160
  - Rewrite `Fiber#<<`, `Fiber#await`, `Fiber#receive` in C
132
161
 
133
- ## 0.45.1
162
+ ## 0.45.1 2020-08-01
134
163
 
135
164
  - Fix Net::HTTP compatibility
136
165
  - Fix fs adapter
@@ -140,7 +169,7 @@
140
169
  - Cleanup code
141
170
  - Improve support for Ruby 3 keyword args
142
171
 
143
- ## 0.45.0
172
+ ## 0.45.0 2020-07-29
144
173
 
145
174
  - Cleanup code
146
175
  - Rename `Agent` to `Backend`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.54.0)
4
+ polyphony (0.59)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/TODO.md CHANGED
@@ -10,9 +10,6 @@
10
10
 
11
11
  - Add support for `break` and `StopIteration` in all loops (with tests)
12
12
 
13
- - Change `IO#gets` to use `String#split` to cut into lines, much faster (see
14
- examples/performance/line_splitting.rb)
15
-
16
13
  - More tight loops
17
14
  - `IO#gets_loop`, `Socket#gets_loop`, `OpenSSL::Socket#gets_loop` (medium effort)
18
15
  - `Fiber#receive_loop` (very little effort, should be implemented in C)
@@ -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,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ i, o = IO.pipe
7
+
8
+ f = spin do
9
+ i.read_loop { |data| STDOUT << data }
10
+ end
11
+
12
+ result = nil
13
+ # File.open(__FILE__, 'r') do |f|
14
+ File.open('../tipi/log', 'r') do |f|
15
+ result = Thread.current.backend.splice_chunks(
16
+ f,
17
+ o,
18
+ "Content-Type: ruby\n\n",
19
+ "0\r\n\r\n",
20
+ ->(len) { "#{len.to_s(16)}\r\n" },
21
+ "\r\n",
22
+ 16384
23
+ )
24
+ end
25
+
26
+
27
+ o.close
28
+ f.await
29
+ p result: result
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ puts 'Please enter your name:'
7
+ name = gets.chomp
8
+ puts "Hello, #{name}!"
@@ -0,0 +1,288 @@
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
+ inline void backend_base_initialize(struct Backend_base *base) {
9
+ runqueue_initialize(&base->runqueue);
10
+ base->currently_polling = 0;
11
+ base->pending_count = 0;
12
+ base->idle_gc_period = 0;
13
+ base->idle_gc_last_time = 0;
14
+ base->idle_proc = Qnil;
15
+ base->trace_proc = Qnil;
16
+ }
17
+
18
+ inline void backend_base_finalize(struct Backend_base *base) {
19
+ runqueue_finalize(&base->runqueue);
20
+ }
21
+
22
+ inline void backend_base_mark(struct Backend_base *base) {
23
+ if (base->idle_proc != Qnil) rb_gc_mark(base->idle_proc);
24
+ if (base->trace_proc != Qnil) rb_gc_mark(base->trace_proc);
25
+ runqueue_mark(&base->runqueue);
26
+ }
27
+
28
+ VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base) {
29
+ VALUE current_fiber = rb_fiber_current();
30
+ runqueue_entry next;
31
+ unsigned int pending_ops_count = base->pending_count;
32
+ unsigned int backend_was_polled = 0;
33
+ unsigned int idle_tasks_run_count = 0;
34
+
35
+ if (SHOULD_TRACE(base) && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
36
+ TRACE(base, 2, SYM_fiber_switchpoint, current_fiber);
37
+
38
+ while (1) {
39
+ next = runqueue_shift(&base->runqueue);
40
+ if (next.fiber != Qnil) {
41
+ // Polling for I/O op completion is normally done when the run queue is
42
+ // empty, but if the runqueue never empties, we'll never get to process
43
+ // any event completions. In order to prevent this, an anti-starve
44
+ // mechanism is employed, under the following conditions:
45
+ // - a blocking poll was not yet performed
46
+ // - there are pending blocking operations
47
+ // - the runqueue has signalled that a non-blocking poll should be
48
+ // performed
49
+ // - the run queue length high watermark has reached its threshold (currently 128)
50
+ // - the run queue switch counter has reached its threshold (currently 64)
51
+ if (!backend_was_polled && pending_ops_count && runqueue_should_poll_nonblocking(&base->runqueue)) {
52
+ // this prevents event starvation in case the run queue never empties
53
+ Backend_poll(backend, Qnil);
54
+ }
55
+ break;
56
+ }
57
+
58
+ if (!idle_tasks_run_count) {
59
+ idle_tasks_run_count++;
60
+ backend_run_idle_tasks(base);
61
+ }
62
+ if (pending_ops_count == 0) break;
63
+ Backend_poll(backend, Qtrue);
64
+ backend_was_polled = 1;
65
+ }
66
+
67
+ if (next.fiber == Qnil) return Qnil;
68
+
69
+ // run next fiber
70
+ COND_TRACE(base, 3, SYM_fiber_run, next.fiber, next.value);
71
+
72
+ rb_ivar_set(next.fiber, ID_ivar_runnable, Qnil);
73
+ RB_GC_GUARD(next.fiber);
74
+ RB_GC_GUARD(next.value);
75
+ return (next.fiber == current_fiber) ?
76
+ next.value : FIBER_TRANSFER(next.fiber, next.value);
77
+ }
78
+
79
+ void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_base *base, VALUE fiber, VALUE value, int prioritize) {
80
+ int already_runnable;
81
+
82
+ if (rb_fiber_alive_p(fiber) != Qtrue) return;
83
+ already_runnable = rb_ivar_get(fiber, ID_ivar_runnable) != Qnil;
84
+
85
+ COND_TRACE(base, 3, SYM_fiber_schedule, fiber, value);
86
+ (prioritize ? runqueue_unshift : runqueue_push)(&base->runqueue, fiber, value, already_runnable);
87
+ if (!already_runnable) {
88
+ rb_ivar_set(fiber, ID_ivar_runnable, Qtrue);
89
+ if (rb_thread_current() != thread) {
90
+ // If the fiber scheduling is done across threads, we need to make sure the
91
+ // target thread is woken up in case it is in the middle of running its
92
+ // event selector. Otherwise it's gonna be stuck waiting for an event to
93
+ // happen, not knowing that it there's already a fiber ready to run in its
94
+ // run queue.
95
+ Backend_wakeup(backend);
96
+ }
97
+ }
98
+ }
99
+
100
+
101
+ inline void backend_trace(struct Backend_base *base, int argc, VALUE *argv) {
102
+ if (base->trace_proc == Qnil) return;
103
+
104
+ rb_funcallv(base->trace_proc, ID_call, argc, argv);
105
+ }
106
+
107
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
108
+ #ifndef __NR_pidfd_open
109
+ #define __NR_pidfd_open 434 /* System call # on most architectures */
110
+ #endif
111
+
112
+ inline int pidfd_open(pid_t pid, unsigned int flags) {
113
+ return syscall(__NR_pidfd_open, pid, flags);
114
+ }
115
+ #endif
116
+
117
+ //////////////////////////////////////////////////////////////////////
118
+ //////////////////////////////////////////////////////////////////////
119
+ // the following is copied verbatim from the Ruby source code (io.c)
120
+
121
+ inline int io_setstrbuf(VALUE *str, long len) {
122
+ #ifdef _WIN32
123
+ len = (len + 1) & ~1L; /* round up for wide char */
124
+ #endif
125
+ if (*str == Qnil) {
126
+ *str = rb_str_new(0, len);
127
+ return 1;
128
+ }
129
+ else {
130
+ VALUE s = StringValue(*str);
131
+ long clen = RSTRING_LEN(s);
132
+ if (clen >= len) {
133
+ rb_str_modify(s);
134
+ return 0;
135
+ }
136
+ len -= clen;
137
+ }
138
+ rb_str_modify_expand(*str, len);
139
+ return 0;
140
+ }
141
+
142
+ #define MAX_REALLOC_GAP 4096
143
+
144
+ inline void io_shrink_read_string(VALUE str, long n) {
145
+ if (rb_str_capacity(str) - n > MAX_REALLOC_GAP) {
146
+ rb_str_resize(str, n);
147
+ }
148
+ }
149
+
150
+ inline void io_set_read_length(VALUE str, long n, int shrinkable) {
151
+ if (RSTRING_LEN(str) != n) {
152
+ rb_str_modify(str);
153
+ rb_str_set_len(str, n);
154
+ if (shrinkable) io_shrink_read_string(str, n);
155
+ }
156
+ }
157
+
158
+ inline rb_encoding* io_read_encoding(rb_io_t *fptr) {
159
+ if (fptr->encs.enc) {
160
+ return fptr->encs.enc;
161
+ }
162
+ return rb_default_external_encoding();
163
+ }
164
+
165
+ inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
166
+ OBJ_TAINT(str);
167
+ rb_enc_associate(str, io_read_encoding(fptr));
168
+ return str;
169
+ }
170
+
171
+ //////////////////////////////////////////////////////////////////////
172
+ //////////////////////////////////////////////////////////////////////
173
+
174
+ VALUE backend_await(struct Backend_base *backend) {
175
+ VALUE ret;
176
+ backend->pending_count++;
177
+ ret = Thread_switch_fiber(rb_thread_current());
178
+ backend->pending_count--;
179
+ RB_GC_GUARD(ret);
180
+ return ret;
181
+ }
182
+
183
+ VALUE backend_snooze() {
184
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
185
+ VALUE ret = Thread_switch_fiber(rb_thread_current());
186
+ return ret;
187
+ }
188
+
189
+ inline void rectify_io_file_pos(rb_io_t *fptr) {
190
+ // Apparently after reopening a closed file, the file position is not reset,
191
+ // which causes the read to fail. Fortunately we can use fptr->rbuf.len to
192
+ // find out if that's the case.
193
+ // See: https://github.com/digital-fabric/polyphony/issues/30
194
+ if (fptr->rbuf.len > 0) {
195
+ lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
196
+ fptr->rbuf.len = 0;
197
+ }
198
+ }
199
+
200
+ inline double current_time() {
201
+ struct timespec ts;
202
+ clock_gettime(CLOCK_MONOTONIC, &ts);
203
+ long long ns = ts.tv_sec;
204
+ ns = ns * 1e9 + ts.tv_nsec;
205
+ double t = ns;
206
+ return t / 1e9;
207
+ }
208
+
209
+ inline VALUE backend_timeout_exception(VALUE exception) {
210
+ if (rb_obj_is_kind_of(exception, rb_cArray) == Qtrue)
211
+ return rb_funcall(rb_ary_entry(exception, 0), ID_new, 1, rb_ary_entry(exception, 1));
212
+ else if (rb_obj_is_kind_of(exception, rb_cClass) == Qtrue)
213
+ return rb_funcall(exception, ID_new, 0);
214
+ else
215
+ return rb_funcall(rb_eRuntimeError, ID_new, 1, exception);
216
+ }
217
+
218
+ VALUE Backend_timeout_safe(VALUE arg) {
219
+ return rb_yield(arg);
220
+ }
221
+
222
+ VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
223
+ return exception;
224
+ }
225
+
226
+ VALUE Backend_timeout_ensure_safe(VALUE arg) {
227
+ return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
228
+ }
229
+
230
+ static VALUE empty_string = Qnil;
231
+
232
+ VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags) {
233
+ switch (RARRAY_LEN(ary)) {
234
+ case 0:
235
+ return Qnil;
236
+ case 1:
237
+ return Backend_send(self, io, RARRAY_AREF(ary, 0), flags);
238
+ default:
239
+ if (empty_string == Qnil) {
240
+ empty_string = rb_str_new_literal("");
241
+ rb_global_variable(&empty_string);
242
+ }
243
+ VALUE joined = rb_ary_join(ary, empty_string);
244
+ VALUE result = Backend_send(self, io, joined, flags);
245
+ RB_GC_GUARD(joined);
246
+ return result;
247
+ }
248
+ }
249
+
250
+ inline void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking) {
251
+ VALUE blocking_mode = rb_ivar_get(io, ID_ivar_blocking_mode);
252
+ if (blocking == blocking_mode) return;
253
+
254
+ rb_ivar_set(io, ID_ivar_blocking_mode, blocking);
255
+
256
+ #ifdef _WIN32
257
+ if (blocking != Qtrue)
258
+ rb_w32_set_nonblock(fptr->fd);
259
+ #elif defined(F_GETFL)
260
+ int flags = fcntl(fptr->fd, F_GETFL);
261
+ if (flags == -1) return;
262
+ int is_nonblocking = flags & O_NONBLOCK;
263
+
264
+ if (blocking == Qtrue) {
265
+ if (!is_nonblocking) return;
266
+ flags &= ~O_NONBLOCK;
267
+ } else {
268
+ if (is_nonblocking) return;
269
+ flags |= O_NONBLOCK;
270
+ }
271
+ fcntl(fptr->fd, F_SETFL, flags);
272
+ #endif
273
+ }
274
+
275
+ inline void backend_run_idle_tasks(struct Backend_base *base) {
276
+ if (base->idle_proc != Qnil)
277
+ rb_funcall(base->idle_proc, ID_call, 0);
278
+
279
+ if (base->idle_gc_period == 0) return;
280
+
281
+ double now = current_time();
282
+ if (now - base->idle_gc_last_time < base->idle_gc_period) return;
283
+
284
+ base->idle_gc_last_time = now;
285
+ rb_gc_enable();
286
+ rb_gc_start();
287
+ rb_gc_disable();
288
+ }