sleepy_penguin 1.4.0 → 2.0.0
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.
- 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)
|