sleepy_penguin 3.1.0 → 3.1.0.26.g7181
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.wrongdoc.yml +2 -0
- data/LICENSE +1 -2
- data/README +4 -3
- data/ext/sleepy_penguin/epoll.c +114 -552
- data/ext/sleepy_penguin/eventfd.c +7 -7
- data/ext/sleepy_penguin/extconf.rb +2 -2
- data/ext/sleepy_penguin/init.c +19 -0
- data/ext/sleepy_penguin/inotify.c +63 -57
- data/ext/sleepy_penguin/missing_epoll.h +1 -1
- data/ext/sleepy_penguin/missing_inotify.h +2 -2
- data/ext/sleepy_penguin/signalfd.c +8 -7
- data/ext/sleepy_penguin/sleepy_penguin.h +47 -10
- data/ext/sleepy_penguin/timerfd.c +9 -9
- data/ext/sleepy_penguin/util.c +18 -14
- data/lib/sleepy_penguin.rb +16 -0
- data/lib/sleepy_penguin/epoll.rb +264 -0
- data/lib/sleepy_penguin/epoll/io.rb +28 -0
- data/lib/sleepy_penguin/sp.rb +1 -1
- data/sleepy_penguin.gemspec +1 -3
- data/test/test_epoll.rb +29 -8
- data/test/test_epoll_gc.rb +2 -0
- data/test/test_epoll_gc_per_thread.rb +23 -0
- data/test/test_epoll_io.rb +24 -0
- data/test/test_epoll_optimizations.rb +1 -1
- data/test/test_eventfd.rb +5 -0
- data/test/test_inotify.rb +5 -13
- data/test/test_timerfd.rb +5 -0
- metadata +69 -71
- data/ext/sleepy_penguin/epoll_green.h +0 -95
@@ -26,16 +26,16 @@ static VALUE s_new(int argc, VALUE *argv, VALUE klass)
|
|
26
26
|
int fd;
|
27
27
|
|
28
28
|
rb_scan_args(argc, argv, "02", &cid, &fl);
|
29
|
-
clockid = NIL_P(cid) ? CLOCK_MONOTONIC : rb_sp_get_flags(klass, cid);
|
30
|
-
flags = rb_sp_get_flags(klass, fl);
|
29
|
+
clockid = NIL_P(cid) ? CLOCK_MONOTONIC : rb_sp_get_flags(klass, cid, 0);
|
30
|
+
flags = rb_sp_get_flags(klass, fl, RB_SP_CLOEXEC(TFD_CLOEXEC));
|
31
31
|
|
32
32
|
fd = timerfd_create(clockid, flags);
|
33
|
-
if (fd
|
33
|
+
if (fd < 0) {
|
34
34
|
if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
|
35
35
|
rb_gc();
|
36
36
|
fd = timerfd_create(clockid, flags);
|
37
37
|
}
|
38
|
-
if (fd
|
38
|
+
if (fd < 0)
|
39
39
|
rb_sys_fail("timerfd_create");
|
40
40
|
}
|
41
41
|
|
@@ -66,13 +66,13 @@ static VALUE itimerspec2ary(struct itimerspec *its)
|
|
66
66
|
static VALUE settime(VALUE self, VALUE fl, VALUE interval, VALUE value)
|
67
67
|
{
|
68
68
|
int fd = rb_sp_fileno(self);
|
69
|
-
int flags = rb_sp_get_flags(self, fl);
|
69
|
+
int flags = rb_sp_get_flags(self, fl, 0);
|
70
70
|
struct itimerspec old, new;
|
71
71
|
|
72
72
|
value2timespec(&new.it_interval, interval);
|
73
73
|
value2timespec(&new.it_value, value);
|
74
74
|
|
75
|
-
if (timerfd_settime(fd, flags, &new, &old)
|
75
|
+
if (timerfd_settime(fd, flags, &new, &old) < 0)
|
76
76
|
rb_sys_fail("timerfd_settime");
|
77
77
|
|
78
78
|
return itimerspec2ary(&old);
|
@@ -89,7 +89,7 @@ static VALUE gettime(VALUE self)
|
|
89
89
|
int fd = rb_sp_fileno(self);
|
90
90
|
struct itimerspec curr;
|
91
91
|
|
92
|
-
if (timerfd_gettime(fd, &curr)
|
92
|
+
if (timerfd_gettime(fd, &curr) < 0)
|
93
93
|
rb_sys_fail("timerfd_gettime");
|
94
94
|
|
95
95
|
return itimerspec2ary(&curr);
|
@@ -126,10 +126,10 @@ static VALUE expirations(int argc, VALUE *argv, VALUE self)
|
|
126
126
|
blocking_io_prepare(fd);
|
127
127
|
retry:
|
128
128
|
r = (ssize_t)rb_sp_fd_region(tfd_read, &buf, fd);
|
129
|
-
if (r
|
129
|
+
if (r < 0) {
|
130
130
|
if (errno == EAGAIN && RTEST(nonblock))
|
131
131
|
return Qnil;
|
132
|
-
if (rb_io_wait_readable
|
132
|
+
if (rb_sp_wait(rb_io_wait_readable, self, &fd))
|
133
133
|
goto retry;
|
134
134
|
rb_sys_fail("read(timerfd)");
|
135
135
|
}
|
data/ext/sleepy_penguin/util.c
CHANGED
@@ -5,10 +5,10 @@ static VALUE klass_for(VALUE klass)
|
|
5
5
|
return (TYPE(klass) == T_CLASS) ? klass : CLASS_OF(klass);
|
6
6
|
}
|
7
7
|
|
8
|
-
int rb_sp_get_flags(VALUE klass, VALUE flags)
|
8
|
+
int rb_sp_get_flags(VALUE klass, VALUE flags, int default_flags)
|
9
9
|
{
|
10
10
|
switch (TYPE(flags)) {
|
11
|
-
case T_NIL: return
|
11
|
+
case T_NIL: return default_flags;
|
12
12
|
case T_FIXNUM: return FIX2INT(flags);
|
13
13
|
case T_BIGNUM: return NUM2INT(flags);
|
14
14
|
case T_SYMBOL:
|
@@ -123,21 +123,25 @@ void rb_sp_set_nonblock(int fd)
|
|
123
123
|
rb_sys_fail("fcntl(F_GETFL)");
|
124
124
|
if ((flags & O_NONBLOCK) == O_NONBLOCK)
|
125
125
|
return;
|
126
|
+
/*
|
127
|
+
* Note: while this is Linux-only and we could safely rely on
|
128
|
+
* ioctl(FIONBIO), needing F_SETFL is an uncommon path, and
|
129
|
+
* F_GETFL is lockless. ioctl(FIONBIO) always acquires a spin
|
130
|
+
* lock, so it's more expensive even if we do not need to change
|
131
|
+
* anything.
|
132
|
+
*/
|
126
133
|
flags = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
127
|
-
if (flags
|
134
|
+
if (flags < 0)
|
128
135
|
rb_sys_fail("fcntl(F_SETFL)");
|
129
136
|
}
|
130
137
|
|
131
|
-
|
132
|
-
#include <rubysig.h>
|
133
|
-
VALUE rb_sp_io_region(rb_blocking_function_t *func, void *data)
|
138
|
+
int rb_sp_wait(rb_sp_waitfn waiter, VALUE obj, int *fd)
|
134
139
|
{
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
return
|
140
|
+
/*
|
141
|
+
* we need to check the fileno before and after waiting, a close()
|
142
|
+
* could've happened at any time (especially when outside of GVL).
|
143
|
+
*/
|
144
|
+
int rc = waiter(rb_sp_fileno(obj));
|
145
|
+
*fd = rb_sp_fileno(obj);
|
146
|
+
return rc;
|
142
147
|
}
|
143
|
-
#endif
|
data/lib/sleepy_penguin.rb
CHANGED
@@ -5,3 +5,19 @@ module SleepyPenguin
|
|
5
5
|
SLEEPY_PENGUIN_VERSION = '3.1.0'
|
6
6
|
end
|
7
7
|
require 'sleepy_penguin_ext'
|
8
|
+
require 'sleepy_penguin/epoll'
|
9
|
+
|
10
|
+
# We need to serialize Inotify#take for Rubinius since that has no GVL
|
11
|
+
# to protect the internal array
|
12
|
+
if defined?(SleepyPenguin::Inotify) &&
|
13
|
+
defined?(Rubinius) && Rubinius.respond_to?(:synchronize)
|
14
|
+
class SleepyPenguin::Inotify
|
15
|
+
# :stopdoc:
|
16
|
+
alias __take take
|
17
|
+
undef_method :take
|
18
|
+
def take(*args)
|
19
|
+
Rubinius.synchronize(@inotify_tmp) { __take(*args) }
|
20
|
+
end
|
21
|
+
# :startdoc
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
require 'thread'
|
2
|
+
class SleepyPenguin::Epoll
|
3
|
+
|
4
|
+
# Epoll objects may be watched by IO.select and similar methods
|
5
|
+
attr_reader :to_io
|
6
|
+
|
7
|
+
# call-seq:
|
8
|
+
# SleepyPenguin::Epoll.new([flags]) -> Epoll object
|
9
|
+
#
|
10
|
+
# Creates a new Epoll object with an optional +flags+ argument.
|
11
|
+
# +flags+ may currently be +:CLOEXEC+ or +0+ (or +nil+).
|
12
|
+
def initialize(create_flags = nil)
|
13
|
+
@to_io = SleepyPenguin::Epoll::IO.new(create_flags)
|
14
|
+
@mtx = Mutex.new
|
15
|
+
@events = []
|
16
|
+
@marks = []
|
17
|
+
@pid = $$
|
18
|
+
@create_flags = create_flags
|
19
|
+
@copies = { @to_io => self }
|
20
|
+
end
|
21
|
+
|
22
|
+
def __ep_reinit # :nodoc:
|
23
|
+
@events.clear
|
24
|
+
@marks.clear
|
25
|
+
@to_io = SleepyPenguin::Epoll::IO.new(@create_flags)
|
26
|
+
end
|
27
|
+
|
28
|
+
# auto-reinitialize the Epoll object after forking
|
29
|
+
def __ep_check # :nodoc:
|
30
|
+
return if @pid == $$
|
31
|
+
return if @to_io.closed?
|
32
|
+
objects = @copies.values
|
33
|
+
@copies.each_key { |epio| epio.close }
|
34
|
+
@copies.clear
|
35
|
+
__ep_reinit
|
36
|
+
objects.each do |obj|
|
37
|
+
io_dup = @to_io.dup
|
38
|
+
@copies[io_dup] = obj
|
39
|
+
end
|
40
|
+
@pid = $$
|
41
|
+
end
|
42
|
+
|
43
|
+
# Calls epoll_wait(2) and yields Integer +events+ and IO objects watched
|
44
|
+
# for. +maxevents+ is the maximum number of events to process at once,
|
45
|
+
# lower numbers may prevent starvation when used by epoll_wait in multiple
|
46
|
+
# threads. Larger +maxevents+ reduces syscall overhead for
|
47
|
+
# single-threaded applications. +maxevents+ defaults to 64 events.
|
48
|
+
# +timeout+ is specified in milliseconds, +nil+
|
49
|
+
# (the default) meaning it will block and wait indefinitely.
|
50
|
+
def wait(maxevents = 64, timeout = nil)
|
51
|
+
# snapshot the marks so we do can sit this thread on epoll_wait while other
|
52
|
+
# threads may call epoll_ctl. People say RCU is a poor man's GC, but our
|
53
|
+
# (ab)use of GC here is inspired by RCU...
|
54
|
+
snapshot = @mtx.synchronize do
|
55
|
+
__ep_check
|
56
|
+
@marks.dup
|
57
|
+
end
|
58
|
+
|
59
|
+
# we keep a snapshot of @marks around in case another thread closes
|
60
|
+
# the IO while it is being transferred to userspace. We release mtx
|
61
|
+
# so another thread may add events to us while we're sleeping.
|
62
|
+
@to_io.epoll_wait(maxevents, timeout) { |events, io| yield(events, io) }
|
63
|
+
ensure
|
64
|
+
# hopefully Ruby does not optimize this array away...
|
65
|
+
snapshot[0]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Starts watching a given +io+ object with +events+ which may be an Integer
|
69
|
+
# bitmask or Array representing arrays to watch for.
|
70
|
+
def add(io, events)
|
71
|
+
fd = io.to_io.fileno
|
72
|
+
events = __event_flags(events)
|
73
|
+
@mtx.synchronize do
|
74
|
+
__ep_check
|
75
|
+
@to_io.epoll_ctl(CTL_ADD, io, events)
|
76
|
+
@events[fd] = events
|
77
|
+
@marks[fd] = io
|
78
|
+
end
|
79
|
+
0
|
80
|
+
end
|
81
|
+
|
82
|
+
# call-seq:
|
83
|
+
# ep.del(io) -> 0
|
84
|
+
#
|
85
|
+
# Disables an IO object from being watched.
|
86
|
+
def del(io)
|
87
|
+
fd = io.to_io.fileno
|
88
|
+
@mtx.synchronize do
|
89
|
+
__ep_check
|
90
|
+
@to_io.epoll_ctl(CTL_DEL, io, 0)
|
91
|
+
@events[fd] = @marks[fd] = nil
|
92
|
+
end
|
93
|
+
0
|
94
|
+
end
|
95
|
+
|
96
|
+
# call-seq:
|
97
|
+
# ep.delete(io) -> io or nil
|
98
|
+
#
|
99
|
+
# This method is deprecated and will be removed in sleepy_penguin 4.x
|
100
|
+
#
|
101
|
+
# Stops an +io+ object from being monitored. This is like Epoll#del
|
102
|
+
# but returns +nil+ on ENOENT instead of raising an error. This is
|
103
|
+
# useful for apps that do not care to track the status of an
|
104
|
+
# epoll object itself.
|
105
|
+
#
|
106
|
+
# This method is deprecated and will be removed in sleepy_penguin 4.x
|
107
|
+
def delete(io)
|
108
|
+
fd = io.to_io.fileno
|
109
|
+
@mtx.synchronize do
|
110
|
+
__ep_check
|
111
|
+
cur_io = @marks[fd]
|
112
|
+
return if nil == cur_io || cur_io.to_io.closed?
|
113
|
+
@to_io.epoll_ctl(CTL_DEL, io, 0)
|
114
|
+
@events[fd] = @marks[fd] = nil
|
115
|
+
end
|
116
|
+
io
|
117
|
+
rescue Errno::ENOENT, Errno::EBADF
|
118
|
+
end
|
119
|
+
|
120
|
+
# call-seq:
|
121
|
+
# epoll.mod(io, flags) -> 0
|
122
|
+
#
|
123
|
+
# Changes the watch for an existing IO object based on +events+.
|
124
|
+
# Returns zero on success, will raise SystemError on failure.
|
125
|
+
def mod(io, events)
|
126
|
+
events = __event_flags(events)
|
127
|
+
fd = io.to_io.fileno
|
128
|
+
@mtx.synchronize do
|
129
|
+
__ep_check
|
130
|
+
@to_io.epoll_ctl(CTL_MOD, io, events)
|
131
|
+
@marks[fd] = io # may be a different object with same fd/file
|
132
|
+
@events[fd] = events
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# call-seq:
|
137
|
+
# ep.set(io, flags) -> 0
|
138
|
+
#
|
139
|
+
# This method is deprecated and will be removed in sleepy_penguin 4.x
|
140
|
+
#
|
141
|
+
# Used to avoid exceptions when your app is too lazy to check
|
142
|
+
# what state a descriptor is in, this sets the epoll descriptor
|
143
|
+
# to watch an +io+ with the given +events+
|
144
|
+
#
|
145
|
+
# +events+ may be an array of symbols or an unsigned Integer bit mask:
|
146
|
+
#
|
147
|
+
# - events = [ :IN, :ET ]
|
148
|
+
# - events = SleepyPenguin::Epoll::IN | SleepyPenguin::Epoll::ET
|
149
|
+
#
|
150
|
+
# See constants in Epoll for more information.
|
151
|
+
#
|
152
|
+
# This method is deprecated and will be removed in sleepy_penguin 4.x
|
153
|
+
def set(io, events)
|
154
|
+
fd = io.to_io.fileno
|
155
|
+
@mtx.synchronize do
|
156
|
+
__ep_check
|
157
|
+
cur_io = @marks[fd]
|
158
|
+
if cur_io == io
|
159
|
+
cur_events = @events[fd]
|
160
|
+
return 0 if (cur_events & ONESHOT) == 0 && cur_events == events
|
161
|
+
begin
|
162
|
+
@to_io.epoll_ctl(CTL_MOD, io, events)
|
163
|
+
rescue Errno::ENOENT
|
164
|
+
warn "epoll event cache failed (mod -> add)"
|
165
|
+
@to_io.epoll_ctl(CTL_ADD, io, events)
|
166
|
+
@marks[fd] = io
|
167
|
+
end
|
168
|
+
else
|
169
|
+
begin
|
170
|
+
@to_io.epoll_ctl(CTL_ADD, io, events)
|
171
|
+
rescue Errno::EEXIST
|
172
|
+
warn "epoll event cache failed (add -> mod)"
|
173
|
+
@to_io.epoll_ctl(CTL_MOD, io, events)
|
174
|
+
end
|
175
|
+
@marks[fd] = io
|
176
|
+
end
|
177
|
+
@events[fd] = events
|
178
|
+
end
|
179
|
+
0
|
180
|
+
end
|
181
|
+
|
182
|
+
# call-seq:
|
183
|
+
# ep.close -> nil
|
184
|
+
#
|
185
|
+
# Closes an existing Epoll object and returns memory back to the kernel.
|
186
|
+
# Raises IOError if object is already closed.
|
187
|
+
def close
|
188
|
+
@mtx.synchronize do
|
189
|
+
@copies.delete(@to_io)
|
190
|
+
@to_io.close
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# call-seq:
|
195
|
+
# ep.closed? -> true or false
|
196
|
+
#
|
197
|
+
# Returns whether or not an Epoll object is closed.
|
198
|
+
def closed?
|
199
|
+
@mtx.synchronize do
|
200
|
+
@to_io.closed?
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# we still support integer FDs for some debug functions
|
205
|
+
def __fileno(io) # :nodoc:
|
206
|
+
Integer === io ? io : io.to_io.fileno
|
207
|
+
end
|
208
|
+
|
209
|
+
# call-seq:
|
210
|
+
# ep.io_for(io) -> object
|
211
|
+
#
|
212
|
+
# Returns the given IO object currently being watched for. Different
|
213
|
+
# IO objects may internally refer to the same process file descriptor.
|
214
|
+
# Mostly used for debugging.
|
215
|
+
def io_for(io)
|
216
|
+
fd = __fileno(io)
|
217
|
+
@mtx.synchronize do
|
218
|
+
__ep_check
|
219
|
+
@marks[fd]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# call-seq:
|
224
|
+
# epoll.events_for(io) -> Integer
|
225
|
+
#
|
226
|
+
# Returns the events currently watched for in current Epoll object.
|
227
|
+
# Mostly used for debugging.
|
228
|
+
def events_for(io)
|
229
|
+
fd = __fileno(io)
|
230
|
+
@mtx.synchronize do
|
231
|
+
__ep_check
|
232
|
+
@events[fd]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# backwards compatibility, to be removed in 4.x
|
237
|
+
alias flags_for events_for
|
238
|
+
|
239
|
+
# call-seq:
|
240
|
+
# epoll.include?(io) -> true or false
|
241
|
+
#
|
242
|
+
# Returns whether or not a given IO is watched and prevented from being
|
243
|
+
# garbage-collected by the current Epoll object. This may include
|
244
|
+
# closed IO objects.
|
245
|
+
def include?(io)
|
246
|
+
fd = __fileno(io)
|
247
|
+
@mtx.synchronize do
|
248
|
+
__ep_check
|
249
|
+
@marks[fd] ? true : false
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def initialize_copy(src) # :nodoc:
|
254
|
+
@mtx.synchronize do
|
255
|
+
__ep_check
|
256
|
+
rv = super
|
257
|
+
unless @to_io.closed?
|
258
|
+
@to_io = @to_io.dup
|
259
|
+
@copies[@to_io] = self
|
260
|
+
end
|
261
|
+
rv
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class SleepyPenguin::Epoll::IO
|
2
|
+
# :stopdoc:
|
3
|
+
alias __epoll_wait epoll_wait
|
4
|
+
undef_method :epoll_wait
|
5
|
+
def epoll_wait(maxevents = 64, timeout = nil)
|
6
|
+
begin
|
7
|
+
if timeout == nil || timeout < 0 # wait forever
|
8
|
+
begin
|
9
|
+
IO.select([self])
|
10
|
+
n = __epoll_wait(maxevents, 0) { |events,io| yield(events, io) }
|
11
|
+
end while n == 0
|
12
|
+
elsif timeout == 0
|
13
|
+
return __epoll_wait(maxevents, 0) { |events,io| yield(events, io) }
|
14
|
+
else
|
15
|
+
done = Time.now + (timeout / 1000.0)
|
16
|
+
begin
|
17
|
+
tout = done - Time.now
|
18
|
+
IO.select([self], nil, nil, tout) if tout > 0
|
19
|
+
n = __epoll_wait(maxevents, 0) { |events,io| yield(events, io) }
|
20
|
+
end while n == 0 && tout > 0
|
21
|
+
end
|
22
|
+
n
|
23
|
+
rescue Errno::EINTR
|
24
|
+
retry
|
25
|
+
end
|
26
|
+
end
|
27
|
+
# :startdoc:
|
28
|
+
end
|
data/lib/sleepy_penguin/sp.rb
CHANGED
data/sleepy_penguin.gemspec
CHANGED
@@ -9,13 +9,11 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.version = ENV["VERSION"].dup
|
10
10
|
s.homepage = Wrongdoc.config[:rdoc_url]
|
11
11
|
s.authors = ["#{name} hackers"]
|
12
|
-
s.date = Time.now.utc.strftime('%Y-%m-%d')
|
13
12
|
s.description = readme_description
|
14
13
|
s.email = %q{sleepy.penguin@librelist.org}
|
15
14
|
s.extra_rdoc_files = extra_rdoc_files(manifest)
|
16
15
|
s.files = manifest
|
17
16
|
s.rdoc_options = rdoc_options
|
18
|
-
s.require_paths = %w(lib ext)
|
19
17
|
s.rubyforge_project = %q{rainbows}
|
20
18
|
s.summary = summary
|
21
19
|
s.test_files = Dir['test/test_*.rb']
|
@@ -23,5 +21,5 @@ Gem::Specification.new do |s|
|
|
23
21
|
s.add_development_dependency('wrongdoc', '~> 1.5')
|
24
22
|
s.add_development_dependency('strace_me', '~> 1.0')
|
25
23
|
|
26
|
-
|
24
|
+
s.licenses = %w(LGPLv2.1 LGPLv3)
|
27
25
|
end
|