polyphony 0.54.0 → 0.59

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