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.
@@ -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 == -1) {
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 == -1)
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) == -1)
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) == -1)
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 == -1) {
129
+ if (r < 0) {
130
130
  if (errno == EAGAIN && RTEST(nonblock))
131
131
  return Qnil;
132
- if (rb_io_wait_readable(fd = rb_sp_fileno(self)))
132
+ if (rb_sp_wait(rb_io_wait_readable, self, &fd))
133
133
  goto retry;
134
134
  rb_sys_fail("read(timerfd)");
135
135
  }
@@ -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 0;
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 == -1)
134
+ if (flags < 0)
128
135
  rb_sys_fail("fcntl(F_SETFL)");
129
136
  }
130
137
 
131
- #ifndef HAVE_RB_THREAD_BLOCKING_REGION
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
- VALUE rv;
136
-
137
- TRAP_BEG;
138
- rv = func(data);
139
- TRAP_END;
140
-
141
- return rv;
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
@@ -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
@@ -1,4 +1,4 @@
1
1
  # :stopdoc:
2
2
  require "sleepy_penguin"
3
- SP = SleepyPenguin
3
+ Object.const_set(:SP, SleepyPenguin)
4
4
  # :startdoc:
@@ -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
- # s.license = %w(LGPL) # disabled for compatibility with older RubyGems
24
+ s.licenses = %w(LGPLv2.1 LGPLv3)
27
25
  end