polyphony 0.61 → 0.65
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +1 -1
- data/ext/polyphony/backend_common.c +26 -21
- data/ext/polyphony/backend_common.h +1 -0
- data/ext/polyphony/runqueue.c +4 -0
- data/ext/polyphony/runqueue.h +1 -0
- data/lib/polyphony/extensions/io.rb +11 -3
- data/lib/polyphony/extensions/openssl.rb +22 -2
- data/lib/polyphony/extensions/socket.rb +66 -10
- data/lib/polyphony/version.rb +1 -1
- data/test/test_backend.rb +26 -0
- data/test/test_io.rb +20 -0
- data/test/test_socket.rb +39 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ffef1fb2b3d081c388c32219346ace5c99b689c73549fed65be882ff1f9e1eb
|
4
|
+
data.tar.gz: 20f2a26377b984f23c427f65fb6f776fe6cfda504667cc710fa3152ec63c920d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31223abf79dad98bfa68c43159474756e1a5e4c713b54239117ec8a57e540ce2914fcfac244b598d36ff3bed49e6efb0b979efc775fdd0a1a8b9e5e1cbe2d25e
|
7
|
+
data.tar.gz: d126c5da62b17a96ae915304c291e520662030dcabe856bcbe4a18d22f2f9c5dfde913a3d89b09a0ad00562ae949aa26a25942163a2decdf2f3184cae0df4a7b
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
## 0.65 2021-07-29
|
2
|
+
|
3
|
+
- Add `#__polyphony_read_method__` method for read method detection
|
4
|
+
|
5
|
+
## 0.64 2021-07-26
|
6
|
+
|
7
|
+
- Add optional raise_on_eof argument to `#readpartial`
|
8
|
+
|
9
|
+
## 0.63 2021-07-26
|
10
|
+
|
11
|
+
- Add support for specifying buf and buf_pos in `IO#read`
|
12
|
+
- Fix `Socket#read` to work and conform to `IO#read` interface
|
13
|
+
|
14
|
+
## 0.62 2021-07-21
|
15
|
+
|
16
|
+
- Add `runqueue_size` to backend stats
|
17
|
+
|
1
18
|
## 0.61 2021-07-20
|
2
19
|
|
3
20
|
- Add more statistics, move stats to `Backend#stats`
|
data/Gemfile.lock
CHANGED
data/Rakefile
CHANGED
@@ -112,22 +112,6 @@ inline void backend_trace(struct Backend_base *base, int argc, VALUE *argv) {
|
|
112
112
|
rb_funcallv(base->trace_proc, ID_call, argc, argv);
|
113
113
|
}
|
114
114
|
|
115
|
-
inline struct backend_stats backend_base_stats(struct Backend_base *base) {
|
116
|
-
struct backend_stats stats = {
|
117
|
-
.runqueue_length = runqueue_len(&base->runqueue),
|
118
|
-
.runqueue_max_length = runqueue_max_len(&base->runqueue),
|
119
|
-
.op_count = base->op_count,
|
120
|
-
.switch_count = base->switch_count,
|
121
|
-
.poll_count = base->poll_count,
|
122
|
-
.pending_ops = base->pending_count
|
123
|
-
};
|
124
|
-
|
125
|
-
base->op_count = 0;
|
126
|
-
base->switch_count = 0;
|
127
|
-
base->poll_count = 0;
|
128
|
-
return stats;
|
129
|
-
}
|
130
|
-
|
131
115
|
#ifdef POLYPHONY_USE_PIDFD_OPEN
|
132
116
|
#ifndef __NR_pidfd_open
|
133
117
|
#define __NR_pidfd_open 434 /* System call # on most architectures */
|
@@ -312,6 +296,24 @@ inline void backend_run_idle_tasks(struct Backend_base *base) {
|
|
312
296
|
rb_gc_disable();
|
313
297
|
}
|
314
298
|
|
299
|
+
inline struct backend_stats backend_base_stats(struct Backend_base *base) {
|
300
|
+
struct backend_stats stats = {
|
301
|
+
.runqueue_size = runqueue_size(&base->runqueue),
|
302
|
+
.runqueue_length = runqueue_len(&base->runqueue),
|
303
|
+
.runqueue_max_length = runqueue_max_len(&base->runqueue),
|
304
|
+
.op_count = base->op_count,
|
305
|
+
.switch_count = base->switch_count,
|
306
|
+
.poll_count = base->poll_count,
|
307
|
+
.pending_ops = base->pending_count
|
308
|
+
};
|
309
|
+
|
310
|
+
base->op_count = 0;
|
311
|
+
base->switch_count = 0;
|
312
|
+
base->poll_count = 0;
|
313
|
+
return stats;
|
314
|
+
}
|
315
|
+
|
316
|
+
VALUE SYM_runqueue_size;
|
315
317
|
VALUE SYM_runqueue_length;
|
316
318
|
VALUE SYM_runqueue_max_length;
|
317
319
|
VALUE SYM_op_count;
|
@@ -323,6 +325,7 @@ VALUE Backend_stats(VALUE self) {
|
|
323
325
|
struct backend_stats backend_stats = backend_get_stats(self);
|
324
326
|
|
325
327
|
VALUE stats = rb_hash_new();
|
328
|
+
rb_hash_aset(stats, SYM_runqueue_size, INT2NUM(backend_stats.runqueue_size));
|
326
329
|
rb_hash_aset(stats, SYM_runqueue_length, INT2NUM(backend_stats.runqueue_length));
|
327
330
|
rb_hash_aset(stats, SYM_runqueue_max_length, INT2NUM(backend_stats.runqueue_max_length));
|
328
331
|
rb_hash_aset(stats, SYM_op_count, INT2NUM(backend_stats.op_count));
|
@@ -334,13 +337,15 @@ VALUE Backend_stats(VALUE self) {
|
|
334
337
|
}
|
335
338
|
|
336
339
|
void backend_setup_stats_symbols() {
|
337
|
-
|
340
|
+
SYM_runqueue_size = ID2SYM(rb_intern("runqueue_size"));
|
341
|
+
SYM_runqueue_length = ID2SYM(rb_intern("runqueue_length"));
|
338
342
|
SYM_runqueue_max_length = ID2SYM(rb_intern("runqueue_max_length"));
|
339
|
-
SYM_op_count
|
340
|
-
SYM_switch_count
|
341
|
-
SYM_poll_count
|
342
|
-
SYM_pending_ops
|
343
|
+
SYM_op_count = ID2SYM(rb_intern("op_count"));
|
344
|
+
SYM_switch_count = ID2SYM(rb_intern("switch_count"));
|
345
|
+
SYM_poll_count = ID2SYM(rb_intern("poll_count"));
|
346
|
+
SYM_pending_ops = ID2SYM(rb_intern("pending_ops"));
|
343
347
|
|
348
|
+
rb_global_variable(&SYM_runqueue_size);
|
344
349
|
rb_global_variable(&SYM_runqueue_length);
|
345
350
|
rb_global_variable(&SYM_runqueue_max_length);
|
346
351
|
rb_global_variable(&SYM_op_count);
|
data/ext/polyphony/runqueue.c
CHANGED
@@ -44,6 +44,10 @@ inline void runqueue_clear(runqueue_t *runqueue) {
|
|
44
44
|
runqueue_ring_buffer_clear(&runqueue->entries);
|
45
45
|
}
|
46
46
|
|
47
|
+
inline long runqueue_size(runqueue_t *runqueue) {
|
48
|
+
return runqueue->entries.size;
|
49
|
+
}
|
50
|
+
|
47
51
|
inline long runqueue_len(runqueue_t *runqueue) {
|
48
52
|
return runqueue->entries.count;
|
49
53
|
}
|
data/ext/polyphony/runqueue.h
CHANGED
@@ -19,6 +19,7 @@ runqueue_entry runqueue_shift(runqueue_t *runqueue);
|
|
19
19
|
void runqueue_delete(runqueue_t *runqueue, VALUE fiber);
|
20
20
|
int runqueue_index_of(runqueue_t *runqueue, VALUE fiber);
|
21
21
|
void runqueue_clear(runqueue_t *runqueue);
|
22
|
+
long runqueue_size(runqueue_t *runqueue);
|
22
23
|
long runqueue_len(runqueue_t *runqueue);
|
23
24
|
long runqueue_max_len(runqueue_t *runqueue);
|
24
25
|
int runqueue_empty_p(runqueue_t *runqueue);
|
@@ -76,6 +76,10 @@ end
|
|
76
76
|
|
77
77
|
# IO instance method patches
|
78
78
|
class ::IO
|
79
|
+
def __polyphony_read_method__
|
80
|
+
:backend_read
|
81
|
+
end
|
82
|
+
|
79
83
|
# def each(sep = $/, limit = nil, chomp: nil)
|
80
84
|
# sep, limit = $/, sep if sep.is_a?(Integer)
|
81
85
|
# end
|
@@ -108,7 +112,11 @@ class ::IO
|
|
108
112
|
end
|
109
113
|
|
110
114
|
alias_method :orig_read, :read
|
111
|
-
def read(len = nil)
|
115
|
+
def read(len = nil, buf = nil, buf_pos = 0)
|
116
|
+
if buf
|
117
|
+
return Polyphony.backend_read(self, buf, len, true, buf_pos)
|
118
|
+
end
|
119
|
+
|
112
120
|
@read_buffer ||= +''
|
113
121
|
result = Polyphony.backend_read(self, @read_buffer, len, true, -1)
|
114
122
|
return nil unless result
|
@@ -119,9 +127,9 @@ class ::IO
|
|
119
127
|
end
|
120
128
|
|
121
129
|
alias_method :orig_readpartial, :read
|
122
|
-
def readpartial(len, str = +'', buffer_pos = 0)
|
130
|
+
def readpartial(len, str = +'', buffer_pos = 0, raise_on_eof = true)
|
123
131
|
result = Polyphony.backend_read(self, str, len, false, buffer_pos)
|
124
|
-
raise EOFError
|
132
|
+
raise EOFError if !result && raise_on_eof
|
125
133
|
|
126
134
|
result
|
127
135
|
end
|
@@ -5,6 +5,10 @@ require_relative './socket'
|
|
5
5
|
|
6
6
|
# OpenSSL socket helper methods (to make it compatible with Socket API) and overrides
|
7
7
|
class ::OpenSSL::SSL::SSLSocket
|
8
|
+
def __polyphony_read_method__
|
9
|
+
:readpartial
|
10
|
+
end
|
11
|
+
|
8
12
|
alias_method :orig_initialize, :initialize
|
9
13
|
def initialize(socket, context = nil)
|
10
14
|
socket = socket.respond_to?(:io) ? socket.io || socket : socket
|
@@ -64,7 +68,21 @@ class ::OpenSSL::SSL::SSLSocket
|
|
64
68
|
# @sync = osync
|
65
69
|
end
|
66
70
|
|
67
|
-
|
71
|
+
alias_method :orig_read, :read
|
72
|
+
def read(maxlen = nil, buf = nil, buf_pos = 0)
|
73
|
+
return readpartial(maxlen, buf, buf_pos) if buf
|
74
|
+
|
75
|
+
buf = +''
|
76
|
+
return readpartial(maxlen, buf) if maxlen
|
77
|
+
|
78
|
+
while true
|
79
|
+
readpartial(4096, buf, -1)
|
80
|
+
end
|
81
|
+
rescue EOFError
|
82
|
+
buf
|
83
|
+
end
|
84
|
+
|
85
|
+
def readpartial(maxlen, buf = +'', buffer_pos = 0, raise_on_eof = true)
|
68
86
|
if buffer_pos != 0
|
69
87
|
if (result = sysread(maxlen, +''))
|
70
88
|
if buffer_pos == -1
|
@@ -76,7 +94,9 @@ class ::OpenSSL::SSL::SSLSocket
|
|
76
94
|
else
|
77
95
|
result = sysread(maxlen, buf)
|
78
96
|
end
|
79
|
-
|
97
|
+
|
98
|
+
raise EOFError if !result && raise_on_eof
|
99
|
+
result
|
80
100
|
end
|
81
101
|
|
82
102
|
def read_loop(maxlen = 8192)
|
@@ -5,6 +5,12 @@ require 'socket'
|
|
5
5
|
require_relative './io'
|
6
6
|
require_relative '../core/thread_pool'
|
7
7
|
|
8
|
+
class BasicSocket
|
9
|
+
def __polyphony_read_method__
|
10
|
+
:backend_recv
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
8
14
|
# Socket overrides (eventually rewritten in C)
|
9
15
|
class ::Socket
|
10
16
|
def accept
|
@@ -22,6 +28,23 @@ class ::Socket
|
|
22
28
|
Polyphony.backend_connect(self, addr.ip_address, addr.ip_port)
|
23
29
|
end
|
24
30
|
|
31
|
+
alias_method :orig_read, :read
|
32
|
+
def read(maxlen = nil, buf = nil, buf_pos = 0)
|
33
|
+
return Polyphony.backend_recv(self, buf, maxlen, buf_pos) if buf
|
34
|
+
return Polyphony.backend_recv(self, buf || +'', maxlen, 0) if maxlen
|
35
|
+
|
36
|
+
buf = +''
|
37
|
+
len = buf.bytesize
|
38
|
+
while true
|
39
|
+
Polyphony.backend_recv(self, buf, maxlen || 4096, -1)
|
40
|
+
new_len = buf.bytesize
|
41
|
+
break if new_len == len
|
42
|
+
|
43
|
+
len = new_len
|
44
|
+
end
|
45
|
+
buf
|
46
|
+
end
|
47
|
+
|
25
48
|
def recv(maxlen, flags = 0, outbuf = nil)
|
26
49
|
Polyphony.backend_recv(self, outbuf || +'', maxlen, 0)
|
27
50
|
end
|
@@ -60,8 +83,9 @@ class ::Socket
|
|
60
83
|
# Polyphony.backend_send(self, mesg, 0)
|
61
84
|
# end
|
62
85
|
|
63
|
-
def readpartial(maxlen, str = +'', buffer_pos = 0)
|
64
|
-
Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
86
|
+
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof = true)
|
87
|
+
result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
88
|
+
raise EOFError if !result && raise_on_eof
|
65
89
|
end
|
66
90
|
|
67
91
|
ZERO_LINGER = [0, 0].pack('ii').freeze
|
@@ -140,6 +164,23 @@ class ::TCPSocket
|
|
140
164
|
setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, 1)
|
141
165
|
end
|
142
166
|
|
167
|
+
alias_method :orig_read, :read
|
168
|
+
def read(maxlen = nil, buf = nil, buf_pos = 0)
|
169
|
+
return Polyphony.backend_recv(self, buf, maxlen, buf_pos) if buf
|
170
|
+
return Polyphony.backend_recv(self, buf || +'', maxlen, 0) if maxlen
|
171
|
+
|
172
|
+
buf = +''
|
173
|
+
len = buf.bytesize
|
174
|
+
while true
|
175
|
+
Polyphony.backend_recv(self, buf, maxlen || 4096, -1)
|
176
|
+
new_len = buf.bytesize
|
177
|
+
break if new_len == len
|
178
|
+
|
179
|
+
len = new_len
|
180
|
+
end
|
181
|
+
buf
|
182
|
+
end
|
183
|
+
|
143
184
|
def recv(maxlen, flags = 0, outbuf = nil)
|
144
185
|
Polyphony.backend_recv(self, outbuf || +'', maxlen, 0)
|
145
186
|
end
|
@@ -165,11 +206,10 @@ class ::TCPSocket
|
|
165
206
|
# Polyphony.backend_send(self, mesg, 0)
|
166
207
|
# end
|
167
208
|
|
168
|
-
def readpartial(maxlen, str = +'', buffer_pos = 0)
|
209
|
+
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof)
|
169
210
|
result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
170
|
-
raise EOFError
|
171
|
-
|
172
|
-
str
|
211
|
+
raise EOFError if !result && raise_on_eof
|
212
|
+
result
|
173
213
|
end
|
174
214
|
|
175
215
|
def read_nonblock(len, str = nil, exception: true)
|
@@ -217,6 +257,23 @@ class ::UNIXServer
|
|
217
257
|
end
|
218
258
|
|
219
259
|
class ::UNIXSocket
|
260
|
+
alias_method :orig_read, :read
|
261
|
+
def read(maxlen = nil, buf = nil, buf_pos = 0)
|
262
|
+
return Polyphony.backend_recv(self, buf, maxlen, buf_pos) if buf
|
263
|
+
return Polyphony.backend_recv(self, buf || +'', maxlen, 0) if maxlen
|
264
|
+
|
265
|
+
buf = +''
|
266
|
+
len = buf.bytesize
|
267
|
+
while true
|
268
|
+
Polyphony.backend_recv(self, buf, maxlen || 4096, -1)
|
269
|
+
new_len = buf.bytesize
|
270
|
+
break if new_len == len
|
271
|
+
|
272
|
+
len = new_len
|
273
|
+
end
|
274
|
+
buf
|
275
|
+
end
|
276
|
+
|
220
277
|
def recv(maxlen, flags = 0, outbuf = nil)
|
221
278
|
Polyphony.backend_recv(self, outbuf || +'', maxlen, 0)
|
222
279
|
end
|
@@ -242,11 +299,10 @@ class ::UNIXSocket
|
|
242
299
|
Polyphony.backend_send(self, mesg, 0)
|
243
300
|
end
|
244
301
|
|
245
|
-
def readpartial(maxlen, str = +'', buffer_pos = 0)
|
302
|
+
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof)
|
246
303
|
result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
247
|
-
raise EOFError
|
248
|
-
|
249
|
-
str
|
304
|
+
raise EOFError if !result && raise_on_eof
|
305
|
+
result
|
250
306
|
end
|
251
307
|
|
252
308
|
def read_nonblock(len, str = nil, exception: true)
|
data/lib/polyphony/version.rb
CHANGED
data/test/test_backend.rb
CHANGED
@@ -98,6 +98,32 @@ class BackendTest < MiniTest::Test
|
|
98
98
|
assert_equal return_value, buf
|
99
99
|
end
|
100
100
|
|
101
|
+
def test_read_concat_big
|
102
|
+
i, o = IO.pipe
|
103
|
+
|
104
|
+
body = " " * 4000
|
105
|
+
|
106
|
+
data = "post /?q=time&blah=blah HTTP/1\r\nHost: dev.realiteq.net\r\n\r\n" +
|
107
|
+
"get /?q=time HTTP/1.1\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}" +
|
108
|
+
"get /?q=time HTTP/1.1\r\nCookie: foo\r\nCookie: bar\r\n\r\n"
|
109
|
+
|
110
|
+
o << data
|
111
|
+
o.close
|
112
|
+
|
113
|
+
buf = +''
|
114
|
+
|
115
|
+
@backend.read(i, buf, 4096, false, -1)
|
116
|
+
assert_equal 4096, buf.bytesize
|
117
|
+
|
118
|
+
@backend.read(i, buf, 1, false, -1)
|
119
|
+
assert_equal 4097, buf.bytesize
|
120
|
+
|
121
|
+
@backend.read(i, buf, 4096, false, -1)
|
122
|
+
|
123
|
+
assert_equal data.bytesize, buf.bytesize
|
124
|
+
assert_equal data, buf
|
125
|
+
end
|
126
|
+
|
101
127
|
def test_waitpid
|
102
128
|
pid = fork do
|
103
129
|
@backend.post_fork
|
data/test/test_io.rb
CHANGED
@@ -73,6 +73,26 @@ class IOTest < MiniTest::Test
|
|
73
73
|
assert_equal [:wait_readable, 'foo'], results
|
74
74
|
end
|
75
75
|
|
76
|
+
def test_read
|
77
|
+
i, o = IO.pipe
|
78
|
+
|
79
|
+
o << 'hi'
|
80
|
+
assert_equal 'hi', i.read(2)
|
81
|
+
|
82
|
+
o << 'foobarbaz'
|
83
|
+
assert_equal 'foo', i.read(3)
|
84
|
+
assert_equal 'bar', i.read(3)
|
85
|
+
|
86
|
+
buf = +'abc'
|
87
|
+
assert_equal 'baz', i.read(3, buf)
|
88
|
+
assert_equal 'baz', buf
|
89
|
+
|
90
|
+
buf = +'def'
|
91
|
+
o << 'foobar'
|
92
|
+
assert_equal 'deffoobar', i.read(6, buf, -1)
|
93
|
+
assert_equal 'deffoobar', buf
|
94
|
+
end
|
95
|
+
|
76
96
|
def test_readpartial
|
77
97
|
i, o = IO.pipe
|
78
98
|
|
data/test/test_socket.rb
CHANGED
@@ -12,7 +12,6 @@ class SocketTest < MiniTest::Test
|
|
12
12
|
def test_tcp
|
13
13
|
port = rand(1234..5678)
|
14
14
|
server = TCPServer.new('127.0.0.1', port)
|
15
|
-
|
16
15
|
server_fiber = spin do
|
17
16
|
while (socket = server.accept)
|
18
17
|
spin do
|
@@ -34,6 +33,45 @@ class SocketTest < MiniTest::Test
|
|
34
33
|
server&.close
|
35
34
|
end
|
36
35
|
|
36
|
+
def test_read
|
37
|
+
port = rand(1234..5678)
|
38
|
+
server = TCPServer.new('127.0.0.1', port)
|
39
|
+
server_fiber = spin do
|
40
|
+
while (socket = server.accept)
|
41
|
+
spin do
|
42
|
+
while (data = socket.read(8192))
|
43
|
+
socket << data
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
snooze
|
50
|
+
client = TCPSocket.new('127.0.0.1', port)
|
51
|
+
|
52
|
+
client << 'hi'
|
53
|
+
assert_equal 'hi', client.read(2)
|
54
|
+
|
55
|
+
client << 'foobarbaz'
|
56
|
+
assert_equal 'foo', client.read(3)
|
57
|
+
assert_equal 'bar', client.read(3)
|
58
|
+
|
59
|
+
buf = +'abc'
|
60
|
+
assert_equal 'baz', client.read(3, buf)
|
61
|
+
assert_equal 'baz', buf
|
62
|
+
|
63
|
+
buf = +'def'
|
64
|
+
client << 'foobar'
|
65
|
+
assert_equal 'deffoobar', client.read(6, buf, -1)
|
66
|
+
assert_equal 'deffoobar', buf
|
67
|
+
|
68
|
+
client.close
|
69
|
+
ensure
|
70
|
+
server_fiber&.stop
|
71
|
+
server_fiber&.await
|
72
|
+
server&.close
|
73
|
+
end
|
74
|
+
|
37
75
|
# sending multiple strings at once
|
38
76
|
def test_sendv
|
39
77
|
port = rand(1234..5678)
|
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.
|
4
|
+
version: '0.65'
|
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-07-
|
11
|
+
date: 2021-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|