sleepy_penguin 1.4.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +3 -0
- data/.manifest +8 -2
- data/ChangeLog +295 -0
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/LATEST +19 -5
- data/LICENSE +2 -2
- data/NEWS +22 -0
- data/README +4 -4
- data/TODO +0 -2
- data/ext/sleepy_penguin/epoll.c +165 -88
- data/ext/sleepy_penguin/eventfd.c +89 -91
- data/ext/sleepy_penguin/extconf.rb +1 -0
- data/ext/sleepy_penguin/init.c +7 -0
- data/ext/sleepy_penguin/inotify.c +229 -91
- data/ext/sleepy_penguin/missing_epoll.h +30 -0
- data/ext/sleepy_penguin/missing_inotify.h +57 -0
- data/ext/sleepy_penguin/signalfd.c +335 -0
- data/ext/sleepy_penguin/sleepy_penguin.h +15 -55
- data/ext/sleepy_penguin/timerfd.c +78 -36
- data/ext/sleepy_penguin/util.c +149 -0
- data/lib/sleepy_penguin.rb +0 -6
- data/lib/sleepy_penguin/signalfd/sig_info.rb +20 -0
- data/lib/sleepy_penguin/sp.rb +4 -0
- data/pkg.mk +5 -4
- data/test/test_epoll.rb +29 -0
- data/test/test_epoll_gc.rb +1 -1
- data/test/test_epoll_optimizations.rb +5 -2
- data/test/test_eventfd.rb +24 -6
- data/test/test_inotify.rb +80 -1
- data/test/test_signalfd.rb +94 -0
- data/test/test_signalfd_siginfo.rb +32 -0
- data/test/test_timerfd.rb +34 -4
- metadata +22 -10
- data/ext/sleepy_penguin/nonblock.h +0 -19
- data/script/isolate_for_tests +0 -30
@@ -4,15 +4,31 @@
|
|
4
4
|
#include "value2timespec.h"
|
5
5
|
static ID id_for_fd;
|
6
6
|
|
7
|
-
|
7
|
+
/*
|
8
|
+
* call-seq:
|
9
|
+
* TimerFD.new([clockid[, flags]]) -> TimerFD IO object
|
10
|
+
*
|
11
|
+
* Creates a new timer as an IO object.
|
12
|
+
*
|
13
|
+
* If set +clockid+ must be be one of the following:
|
14
|
+
* - :REALTIME - use the settable clock
|
15
|
+
* - :MONOTONIC - use the non-settable clock unaffected by manual changes
|
16
|
+
*
|
17
|
+
* +clockid+ defaults to :MONOTONIC if unspecified
|
18
|
+
* +flags+ may be any or none of the following:
|
19
|
+
*
|
20
|
+
* - :CLOEXEC - set the close-on-exec flag on the new object
|
21
|
+
* - :NONBLOCK - set the non-blocking I/O flag on the new object
|
22
|
+
*/
|
23
|
+
static VALUE s_new(int argc, VALUE *argv, VALUE klass)
|
8
24
|
{
|
9
25
|
VALUE cid, fl;
|
10
|
-
int clockid, flags
|
26
|
+
int clockid, flags;
|
11
27
|
int fd;
|
12
28
|
|
13
29
|
rb_scan_args(argc, argv, "02", &cid, &fl);
|
14
|
-
clockid = NIL_P(cid) ? CLOCK_MONOTONIC :
|
15
|
-
flags =
|
30
|
+
clockid = NIL_P(cid) ? CLOCK_MONOTONIC : rb_sp_get_flags(klass, cid);
|
31
|
+
flags = rb_sp_get_flags(klass, fl);
|
16
32
|
|
17
33
|
fd = timerfd_create(clockid, flags);
|
18
34
|
if (fd == -1) {
|
@@ -35,10 +51,22 @@ static VALUE itimerspec2ary(struct itimerspec *its)
|
|
35
51
|
return rb_ary_new3(2, interval, value);
|
36
52
|
}
|
37
53
|
|
54
|
+
/*
|
55
|
+
* call-seq:
|
56
|
+
* tfd.settime(flags, interval, value) -> [ old_interval, old_value ]
|
57
|
+
*
|
58
|
+
* Arms (starts) or disarms (stops) the timer referred by the TimerFD object
|
59
|
+
* and returns the old value of the timer.
|
60
|
+
*
|
61
|
+
* +flags+ is either zero (or nil) to start a relative timer or :ABSTIME
|
62
|
+
* to start an absolute timer. If the +interval+ is zero, the timer fires
|
63
|
+
* only once, otherwise the timer is fired every +interval+ seconds.
|
64
|
+
* +value+ is the time of the initial expiration in seconds.
|
65
|
+
*/
|
38
66
|
static VALUE settime(VALUE self, VALUE fl, VALUE interval, VALUE value)
|
39
67
|
{
|
40
|
-
int fd =
|
41
|
-
int flags =
|
68
|
+
int fd = rb_sp_fileno(self);
|
69
|
+
int flags = rb_sp_get_flags(self, fl);
|
42
70
|
struct itimerspec old, new;
|
43
71
|
|
44
72
|
value2timespec(&new.it_interval, interval);
|
@@ -50,9 +78,15 @@ static VALUE settime(VALUE self, VALUE fl, VALUE interval, VALUE value)
|
|
50
78
|
return itimerspec2ary(&old);
|
51
79
|
}
|
52
80
|
|
81
|
+
/*
|
82
|
+
* call-seq:
|
83
|
+
* tfd#gettime -> [ interval, value ]
|
84
|
+
*
|
85
|
+
* Returns the current +interval+ and +value+ of the timer as an Array.
|
86
|
+
*/
|
53
87
|
static VALUE gettime(VALUE self)
|
54
88
|
{
|
55
|
-
int fd =
|
89
|
+
int fd = rb_sp_fileno(self);
|
56
90
|
struct itimerspec curr;
|
57
91
|
|
58
92
|
if (timerfd_gettime(fd, &curr) == -1)
|
@@ -61,7 +95,6 @@ static VALUE gettime(VALUE self)
|
|
61
95
|
return itimerspec2ary(&curr);
|
62
96
|
}
|
63
97
|
|
64
|
-
#ifdef HAVE_RB_THREAD_BLOCKING_REGION
|
65
98
|
static VALUE tfd_read(void *args)
|
66
99
|
{
|
67
100
|
uint64_t *buf = args;
|
@@ -71,29 +104,31 @@ static VALUE tfd_read(void *args)
|
|
71
104
|
return (VALUE)r;
|
72
105
|
}
|
73
106
|
|
74
|
-
|
107
|
+
/*
|
108
|
+
* call-seq:
|
109
|
+
* tfd.expirations([nonblock]) -> Integer
|
110
|
+
*
|
111
|
+
* Returns the number of expirations that have occurred. This will block
|
112
|
+
* if no expirations have occurred at the time of the call. Returns +nil+
|
113
|
+
* if +nonblock+ is passed and is +true+
|
114
|
+
*/
|
115
|
+
static VALUE expirations(int argc, VALUE *argv, VALUE self)
|
75
116
|
{
|
76
117
|
ssize_t r;
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
#include "nonblock.h"
|
87
|
-
static VALUE expirations(VALUE self)
|
88
|
-
{
|
89
|
-
int fd = my_fileno(self);
|
90
|
-
uint64_t buf;
|
91
|
-
ssize_t r;
|
92
|
-
|
93
|
-
set_nonblock(fd);
|
118
|
+
int fd = rb_sp_fileno(self);
|
119
|
+
uint64_t buf = (uint64_t)fd;
|
120
|
+
VALUE nonblock;
|
121
|
+
|
122
|
+
rb_scan_args(argc, argv, "01", &nonblock);
|
123
|
+
if (RTEST(nonblock))
|
124
|
+
rb_sp_set_nonblock(fd);
|
125
|
+
else
|
126
|
+
blocking_io_prepare(fd);
|
94
127
|
retry:
|
95
|
-
r =
|
128
|
+
r = (ssize_t)rb_sp_io_region(tfd_read, &buf);
|
96
129
|
if (r == -1) {
|
130
|
+
if (errno == EAGAIN && RTEST(nonblock))
|
131
|
+
return Qnil;
|
97
132
|
if (rb_io_wait_readable(fd))
|
98
133
|
goto retry;
|
99
134
|
rb_sys_fail("read(timerfd)");
|
@@ -101,28 +136,35 @@ retry:
|
|
101
136
|
|
102
137
|
return ULL2NUM(buf);
|
103
138
|
}
|
104
|
-
#endif
|
105
139
|
|
106
140
|
void sleepy_penguin_init_timerfd(void)
|
107
141
|
{
|
108
142
|
VALUE mSleepyPenguin, cTimerFD;
|
109
143
|
|
110
144
|
mSleepyPenguin = rb_define_module("SleepyPenguin");
|
145
|
+
|
146
|
+
/*
|
147
|
+
* Document-class: SleepyPenguin::TimerFD
|
148
|
+
*
|
149
|
+
* TimerFD exposes kernel timers as IO objects that may be monitored
|
150
|
+
* by IO.select or Epoll. IO#close disarms the timers and returns
|
151
|
+
* resources back to the kernel.
|
152
|
+
*/
|
111
153
|
cTimerFD = rb_define_class_under(mSleepyPenguin, "TimerFD", rb_cIO);
|
112
|
-
rb_define_singleton_method(cTimerFD, "
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
rb_define_const(cTimerFD, "ABSTIME", UINT2NUM(TFD_TIMER_ABSTIME));
|
154
|
+
rb_define_singleton_method(cTimerFD, "new", s_new, -1);
|
155
|
+
NODOC_CONST(cTimerFD, "REALTIME", UINT2NUM(CLOCK_REALTIME));
|
156
|
+
NODOC_CONST(cTimerFD, "MONOTONIC", UINT2NUM(CLOCK_MONOTONIC));
|
157
|
+
NODOC_CONST(cTimerFD, "ABSTIME", UINT2NUM(TFD_TIMER_ABSTIME));
|
117
158
|
#ifdef TFD_NONBLOCK
|
118
|
-
|
159
|
+
NODOC_CONST(cTimerFD, "NONBLOCK", UINT2NUM(TFD_NONBLOCK));
|
119
160
|
#endif
|
120
161
|
#ifdef TFD_CLOEXEC
|
121
|
-
|
162
|
+
NODOC_CONST(cTimerFD, "CLOEXEC", UINT2NUM(TFD_CLOEXEC));
|
122
163
|
#endif
|
123
164
|
|
124
165
|
rb_define_method(cTimerFD, "settime", settime, 3);
|
125
|
-
rb_define_method(cTimerFD, "
|
166
|
+
rb_define_method(cTimerFD, "gettime", gettime, 0);
|
167
|
+
rb_define_method(cTimerFD, "expirations", expirations, -1);
|
126
168
|
id_for_fd = rb_intern("for_fd");
|
127
169
|
}
|
128
170
|
#endif /* HAVE_SYS_TIMERFD_H */
|
@@ -0,0 +1,149 @@
|
|
1
|
+
#include "sleepy_penguin.h"
|
2
|
+
|
3
|
+
static VALUE klass_for(VALUE klass)
|
4
|
+
{
|
5
|
+
return (TYPE(klass) == T_CLASS) ? klass : CLASS_OF(klass);
|
6
|
+
}
|
7
|
+
|
8
|
+
int rb_sp_get_flags(VALUE klass, VALUE flags)
|
9
|
+
{
|
10
|
+
switch (TYPE(flags)) {
|
11
|
+
case T_NIL: return 0;
|
12
|
+
case T_FIXNUM: return FIX2INT(flags);
|
13
|
+
case T_BIGNUM: return NUM2INT(flags);
|
14
|
+
case T_SYMBOL:
|
15
|
+
return NUM2INT(rb_const_get(klass_for(klass), SYM2ID(flags)));
|
16
|
+
case T_ARRAY: {
|
17
|
+
VALUE *ptr = RARRAY_PTR(flags);
|
18
|
+
long len = RARRAY_LEN(flags);
|
19
|
+
int rv = 0;
|
20
|
+
|
21
|
+
klass = klass_for(klass);
|
22
|
+
while (--len >= 0) {
|
23
|
+
VALUE tmp = *ptr++;
|
24
|
+
|
25
|
+
Check_Type(tmp, T_SYMBOL);
|
26
|
+
tmp = rb_const_get(klass, SYM2ID(tmp));
|
27
|
+
rv |= NUM2INT(tmp);
|
28
|
+
}
|
29
|
+
return rv;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
rb_raise(rb_eTypeError, "invalid flags");
|
33
|
+
return 0;
|
34
|
+
}
|
35
|
+
|
36
|
+
unsigned rb_sp_get_uflags(VALUE klass, VALUE flags)
|
37
|
+
{
|
38
|
+
switch (TYPE(flags)) {
|
39
|
+
case T_NIL: return 0;
|
40
|
+
case T_FIXNUM: return FIX2UINT(flags);
|
41
|
+
case T_BIGNUM: return NUM2UINT(flags);
|
42
|
+
case T_SYMBOL:
|
43
|
+
return NUM2UINT(rb_const_get(klass_for(klass), SYM2ID(flags)));
|
44
|
+
case T_ARRAY: {
|
45
|
+
VALUE *ptr = RARRAY_PTR(flags);
|
46
|
+
long len = RARRAY_LEN(flags);
|
47
|
+
unsigned rv = 0;
|
48
|
+
|
49
|
+
klass = klass_for(klass);
|
50
|
+
while (--len >= 0) {
|
51
|
+
VALUE tmp = *ptr++;
|
52
|
+
|
53
|
+
Check_Type(tmp, T_SYMBOL);
|
54
|
+
tmp = rb_const_get(klass, SYM2ID(tmp));
|
55
|
+
rv |= NUM2UINT(tmp);
|
56
|
+
}
|
57
|
+
return rv;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
rb_raise(rb_eTypeError, "invalid flags");
|
61
|
+
return 0;
|
62
|
+
}
|
63
|
+
|
64
|
+
#if ! HAVE_RB_IO_T
|
65
|
+
# define rb_io_t OpenFile
|
66
|
+
#endif
|
67
|
+
|
68
|
+
#ifdef GetReadFile
|
69
|
+
# define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr)))
|
70
|
+
#else
|
71
|
+
# if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8)
|
72
|
+
# define FPTR_TO_FD(fptr) fileno(fptr->f)
|
73
|
+
# else
|
74
|
+
# define FPTR_TO_FD(fptr) fptr->fd
|
75
|
+
# endif
|
76
|
+
#endif
|
77
|
+
|
78
|
+
static int fixint_closed_p(VALUE io)
|
79
|
+
{
|
80
|
+
return (fcntl(FIX2INT(io), F_GETFD) == -1 && errno == EBADF);
|
81
|
+
}
|
82
|
+
|
83
|
+
#if defined(RFILE) && defined(HAVE_ST_FD)
|
84
|
+
static int my_rb_io_closed(VALUE io)
|
85
|
+
{
|
86
|
+
return RFILE(io)->fptr->fd < 0;
|
87
|
+
}
|
88
|
+
#else
|
89
|
+
static int my_rb_io_closed(VALUE io)
|
90
|
+
{
|
91
|
+
return rb_funcall(io, rb_intern("closed?"), 0) == Qtrue;
|
92
|
+
}
|
93
|
+
#endif
|
94
|
+
|
95
|
+
int rb_sp_io_closed(VALUE io)
|
96
|
+
{
|
97
|
+
switch (TYPE(io)) {
|
98
|
+
case T_FIXNUM:
|
99
|
+
return fixint_closed_p(io);
|
100
|
+
case T_FILE:
|
101
|
+
break;
|
102
|
+
default:
|
103
|
+
io = rb_convert_type(io, T_FILE, "IO", "to_io");
|
104
|
+
}
|
105
|
+
|
106
|
+
return my_rb_io_closed(io);
|
107
|
+
}
|
108
|
+
|
109
|
+
int rb_sp_fileno(VALUE io)
|
110
|
+
{
|
111
|
+
rb_io_t *fptr;
|
112
|
+
|
113
|
+
switch (TYPE(io)) {
|
114
|
+
case T_FIXNUM: return FIX2INT(io);
|
115
|
+
case T_FILE:
|
116
|
+
GetOpenFile(io, fptr);
|
117
|
+
return FPTR_TO_FD(fptr);
|
118
|
+
}
|
119
|
+
io = rb_convert_type(io, T_FILE, "IO", "to_io");
|
120
|
+
GetOpenFile(io, fptr);
|
121
|
+
return FPTR_TO_FD(fptr);
|
122
|
+
}
|
123
|
+
|
124
|
+
void rb_sp_set_nonblock(int fd)
|
125
|
+
{
|
126
|
+
int flags = fcntl(fd, F_GETFL);
|
127
|
+
|
128
|
+
if (flags == -1)
|
129
|
+
rb_sys_fail("fcntl(F_GETFL)");
|
130
|
+
if ((flags & O_NONBLOCK) == O_NONBLOCK)
|
131
|
+
return;
|
132
|
+
flags = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
133
|
+
if (flags == -1)
|
134
|
+
rb_sys_fail("fcntl(F_SETFL)");
|
135
|
+
}
|
136
|
+
|
137
|
+
#ifndef HAVE_RB_THREAD_BLOCKING_REGION
|
138
|
+
#include <rubysig.h>
|
139
|
+
VALUE rb_sp_io_region(rb_blocking_function_t *func, void *data)
|
140
|
+
{
|
141
|
+
VALUE rv;
|
142
|
+
|
143
|
+
TRAP_BEG;
|
144
|
+
rv = func(data);
|
145
|
+
TRAP_END;
|
146
|
+
|
147
|
+
return rv;
|
148
|
+
}
|
149
|
+
#endif
|
data/lib/sleepy_penguin.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
# :stopdoc:
|
2
|
+
class SleepyPenguin::SignalFD::SigInfo
|
3
|
+
|
4
|
+
def to_hash
|
5
|
+
Hash[*MEMBERS.inject([]) { |ary,k| ary << k << __send__(k) }]
|
6
|
+
end
|
7
|
+
|
8
|
+
def hash
|
9
|
+
to_hash.hash
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
"#<#{self.class}:#{to_hash.inspect}>"
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
other.kind_of?(self.class) && to_hash == other.to_hash
|
18
|
+
end
|
19
|
+
end
|
20
|
+
# :startdoc:
|
data/pkg.mk
CHANGED
@@ -46,22 +46,23 @@ lib := $(lib):$(ext_pfx)/$(ext)
|
|
46
46
|
build: $(ext_dl)
|
47
47
|
endif
|
48
48
|
|
49
|
-
pkg_extra
|
49
|
+
pkg_extra += GIT-VERSION-FILE NEWS ChangeLog LATEST
|
50
50
|
ChangeLog: GIT-VERSION-FILE .wrongdoc.yml
|
51
51
|
$(WRONGDOC) prepare
|
52
|
+
NEWS LATEST: ChangeLog
|
52
53
|
|
53
54
|
manifest:
|
54
55
|
$(RM) .manifest
|
55
56
|
$(MAKE) .manifest
|
56
57
|
|
57
|
-
.manifest:
|
58
|
+
.manifest: $(pkg_extra)
|
58
59
|
(git ls-files && for i in $@ $(pkg_extra); do echo $$i; done) | \
|
59
60
|
LC_ALL=C sort > $@+
|
60
61
|
cmp $@+ $@ || mv $@+ $@
|
61
62
|
$(RM) $@+
|
62
63
|
|
63
|
-
doc:: .document .wrongdoc.yml
|
64
|
-
find lib -type f -name '*.rbc' -exec rm -f '{}' ';'
|
64
|
+
doc:: .document .wrongdoc.yml $(pkg_extra)
|
65
|
+
-find lib -type f -name '*.rbc' -exec rm -f '{}' ';'
|
65
66
|
-find ext -type f -name '*.rbc' -exec rm -f '{}' ';'
|
66
67
|
$(RM) -r doc
|
67
68
|
$(WRONGDOC) all
|
data/test/test_epoll.rb
CHANGED
@@ -14,6 +14,14 @@ class TestEpoll < Test::Unit::TestCase
|
|
14
14
|
@ep = Epoll.new
|
15
15
|
end
|
16
16
|
|
17
|
+
def test_constants
|
18
|
+
Epoll.constants.each do |const|
|
19
|
+
next if const.to_sym == :IO
|
20
|
+
nr = Epoll.const_get(const)
|
21
|
+
assert nr <= 0xffffffff, "#{const}=#{nr}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
17
25
|
def test_cross_thread
|
18
26
|
tmp = []
|
19
27
|
Thread.new { sleep 0.100; @ep.add(@wr, Epoll::OUT) }
|
@@ -347,6 +355,27 @@ class TestEpoll < Test::Unit::TestCase
|
|
347
355
|
assert_nil @ep.flags_for(@rd)
|
348
356
|
end
|
349
357
|
|
358
|
+
def test_flags_for_sym
|
359
|
+
@ep.add @rd, :IN
|
360
|
+
assert_equal Epoll::IN, @ep.flags_for(@rd.fileno)
|
361
|
+
assert_equal Epoll::IN, @ep.flags_for(@rd)
|
362
|
+
|
363
|
+
@ep.del @rd
|
364
|
+
assert_nil @ep.flags_for(@rd.fileno)
|
365
|
+
assert_nil @ep.flags_for(@rd)
|
366
|
+
end
|
367
|
+
|
368
|
+
def test_flags_for_sym_ary
|
369
|
+
@ep.add @rd, [:IN, :ET]
|
370
|
+
expect = Epoll::IN | Epoll::ET
|
371
|
+
assert_equal expect, @ep.flags_for(@rd.fileno)
|
372
|
+
assert_equal expect, @ep.flags_for(@rd)
|
373
|
+
|
374
|
+
@ep.del @rd
|
375
|
+
assert_nil @ep.flags_for(@rd.fileno)
|
376
|
+
assert_nil @ep.flags_for(@rd)
|
377
|
+
end
|
378
|
+
|
350
379
|
def test_include?
|
351
380
|
assert ! @ep.include?(@rd)
|
352
381
|
@ep.add @rd, Epoll::IN
|
data/test/test_epoll_gc.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'test/unit'
|
2
|
-
|
2
|
+
begin
|
3
|
+
require 'strace'
|
4
|
+
rescue LoadError
|
5
|
+
end
|
3
6
|
$-w = true
|
4
7
|
|
5
8
|
require 'sleepy_penguin'
|
@@ -148,4 +151,4 @@ class TestEpollOptimizations < Test::Unit::TestCase
|
|
148
151
|
assert_equal 1, lines.grep(/^epoll_ctl/).size
|
149
152
|
assert_equal 1, lines.grep(/EBADF/).size
|
150
153
|
end
|
151
|
-
end
|
154
|
+
end if defined?(Strace)
|