sleepy_penguin 3.1.0.26.g7181 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -77,4 +77,16 @@ static inline VALUE fake_blocking_region(VALUE (*fn)(void *), void *data)
77
77
 
78
78
  typedef int rb_sp_waitfn(int fd);
79
79
  int rb_sp_wait(rb_sp_waitfn waiter, VALUE obj, int *fd);
80
+
81
+ /* Flexible array elements are standard in C99 */
82
+ #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
83
+ # define FLEX_ARRAY
84
+ #elif defined(__GNUC__)
85
+ # if (__GNUC__ >= 3)
86
+ # define FLEX_ARRAY
87
+ # else
88
+ # define FLEX_ARRAY 0
89
+ # endif
90
+ #endif
91
+
80
92
  #endif /* SLEEPY_PENGUIN_H */
@@ -53,7 +53,7 @@ static struct timespec *value2timespec(struct timespec *ts, VALUE num)
53
53
  # define TIMET2NUM(n) LONG2NUM(n)
54
54
  #endif
55
55
 
56
- static VALUE timespec2num(struct timespec *ts)
56
+ static inline VALUE timespec2num(struct timespec *ts)
57
57
  {
58
58
  if (ts->tv_nsec == 0)
59
59
  return TIMET2NUM(ts->tv_sec);
@@ -1,11 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
- module SleepyPenguin
3
-
4
- # the version of sleepy_penguin, currently 3.1.0
5
- SLEEPY_PENGUIN_VERSION = '3.1.0'
6
- end
7
2
  require 'sleepy_penguin_ext'
8
- require 'sleepy_penguin/epoll'
9
3
 
10
4
  # We need to serialize Inotify#take for Rubinius since that has no GVL
11
5
  # to protect the internal array
@@ -1,45 +1,50 @@
1
1
  require 'thread'
2
2
  class SleepyPenguin::Epoll
3
3
 
4
- # Epoll objects may be watched by IO.select and similar methods
5
- attr_reader :to_io
6
-
7
4
  # call-seq:
8
5
  # SleepyPenguin::Epoll.new([flags]) -> Epoll object
9
6
  #
10
7
  # Creates a new Epoll object with an optional +flags+ argument.
11
8
  # +flags+ may currently be +:CLOEXEC+ or +0+ (or +nil+).
12
9
  def initialize(create_flags = nil)
13
- @to_io = SleepyPenguin::Epoll::IO.new(create_flags)
10
+ @io = SleepyPenguin::Epoll::IO.new(create_flags)
14
11
  @mtx = Mutex.new
15
12
  @events = []
16
13
  @marks = []
17
14
  @pid = $$
18
15
  @create_flags = create_flags
19
- @copies = { @to_io => self }
16
+ @copies = { @io => self }
20
17
  end
21
18
 
22
19
  def __ep_reinit # :nodoc:
23
20
  @events.clear
24
21
  @marks.clear
25
- @to_io = SleepyPenguin::Epoll::IO.new(@create_flags)
22
+ @io = SleepyPenguin::Epoll::IO.new(@create_flags)
26
23
  end
27
24
 
28
25
  # auto-reinitialize the Epoll object after forking
29
26
  def __ep_check # :nodoc:
30
27
  return if @pid == $$
31
- return if @to_io.closed?
28
+ return if @io.closed?
32
29
  objects = @copies.values
33
30
  @copies.each_key { |epio| epio.close }
34
31
  @copies.clear
35
32
  __ep_reinit
36
33
  objects.each do |obj|
37
- io_dup = @to_io.dup
34
+ io_dup = @io.dup
38
35
  @copies[io_dup] = obj
39
36
  end
40
37
  @pid = $$
41
38
  end
42
39
 
40
+ # Epoll objects may be watched by IO.select and similar methods
41
+ def to_io
42
+ @mtx.synchronize do
43
+ __ep_check
44
+ @io
45
+ end
46
+ end
47
+
43
48
  # Calls epoll_wait(2) and yields Integer +events+ and IO objects watched
44
49
  # for. +maxevents+ is the maximum number of events to process at once,
45
50
  # lower numbers may prevent starvation when used by epoll_wait in multiple
@@ -59,10 +64,10 @@ class SleepyPenguin::Epoll
59
64
  # we keep a snapshot of @marks around in case another thread closes
60
65
  # the IO while it is being transferred to userspace. We release mtx
61
66
  # so another thread may add events to us while we're sleeping.
62
- @to_io.epoll_wait(maxevents, timeout) { |events, io| yield(events, io) }
67
+ @io.epoll_wait(maxevents, timeout) { |events, io| yield(events, io) }
63
68
  ensure
64
69
  # hopefully Ruby does not optimize this array away...
65
- snapshot[0]
70
+ snapshot.clear
66
71
  end
67
72
 
68
73
  # Starts watching a given +io+ object with +events+ which may be an Integer
@@ -72,7 +77,7 @@ class SleepyPenguin::Epoll
72
77
  events = __event_flags(events)
73
78
  @mtx.synchronize do
74
79
  __ep_check
75
- @to_io.epoll_ctl(CTL_ADD, io, events)
80
+ @io.epoll_ctl(CTL_ADD, io, events)
76
81
  @events[fd] = events
77
82
  @marks[fd] = io
78
83
  end
@@ -87,7 +92,7 @@ class SleepyPenguin::Epoll
87
92
  fd = io.to_io.fileno
88
93
  @mtx.synchronize do
89
94
  __ep_check
90
- @to_io.epoll_ctl(CTL_DEL, io, 0)
95
+ @io.epoll_ctl(CTL_DEL, io, 0)
91
96
  @events[fd] = @marks[fd] = nil
92
97
  end
93
98
  0
@@ -110,7 +115,7 @@ class SleepyPenguin::Epoll
110
115
  __ep_check
111
116
  cur_io = @marks[fd]
112
117
  return if nil == cur_io || cur_io.to_io.closed?
113
- @to_io.epoll_ctl(CTL_DEL, io, 0)
118
+ @io.epoll_ctl(CTL_DEL, io, 0)
114
119
  @events[fd] = @marks[fd] = nil
115
120
  end
116
121
  io
@@ -127,7 +132,7 @@ class SleepyPenguin::Epoll
127
132
  fd = io.to_io.fileno
128
133
  @mtx.synchronize do
129
134
  __ep_check
130
- @to_io.epoll_ctl(CTL_MOD, io, events)
135
+ @io.epoll_ctl(CTL_MOD, io, events)
131
136
  @marks[fd] = io # may be a different object with same fd/file
132
137
  @events[fd] = events
133
138
  end
@@ -159,18 +164,18 @@ class SleepyPenguin::Epoll
159
164
  cur_events = @events[fd]
160
165
  return 0 if (cur_events & ONESHOT) == 0 && cur_events == events
161
166
  begin
162
- @to_io.epoll_ctl(CTL_MOD, io, events)
167
+ @io.epoll_ctl(CTL_MOD, io, events)
163
168
  rescue Errno::ENOENT
164
169
  warn "epoll event cache failed (mod -> add)"
165
- @to_io.epoll_ctl(CTL_ADD, io, events)
170
+ @io.epoll_ctl(CTL_ADD, io, events)
166
171
  @marks[fd] = io
167
172
  end
168
173
  else
169
174
  begin
170
- @to_io.epoll_ctl(CTL_ADD, io, events)
175
+ @io.epoll_ctl(CTL_ADD, io, events)
171
176
  rescue Errno::EEXIST
172
177
  warn "epoll event cache failed (add -> mod)"
173
- @to_io.epoll_ctl(CTL_MOD, io, events)
178
+ @io.epoll_ctl(CTL_MOD, io, events)
174
179
  end
175
180
  @marks[fd] = io
176
181
  end
@@ -186,8 +191,8 @@ class SleepyPenguin::Epoll
186
191
  # Raises IOError if object is already closed.
187
192
  def close
188
193
  @mtx.synchronize do
189
- @copies.delete(@to_io)
190
- @to_io.close
194
+ @copies.delete(@io)
195
+ @io.close
191
196
  end
192
197
  end
193
198
 
@@ -197,7 +202,7 @@ class SleepyPenguin::Epoll
197
202
  # Returns whether or not an Epoll object is closed.
198
203
  def closed?
199
204
  @mtx.synchronize do
200
- @to_io.closed?
205
+ @io.closed?
201
206
  end
202
207
  end
203
208
 
@@ -254,9 +259,9 @@ class SleepyPenguin::Epoll
254
259
  @mtx.synchronize do
255
260
  __ep_check
256
261
  rv = super
257
- unless @to_io.closed?
258
- @to_io = @to_io.dup
259
- @copies[@to_io] = self
262
+ unless @io.closed?
263
+ @io = @io.dup
264
+ @copies[@io] = self
260
265
  end
261
266
  rv
262
267
  end
@@ -0,0 +1,7 @@
1
+ # This class represents a "struct kevent" structure for Ruby.
2
+ # This may be passed to Kqueue::IO#kevent as either a single element
3
+ # or as an element inside an array to inject changes into the kevent
4
+ # list.
5
+ class SleepyPenguin::Kevent < Struct.new(:ident, :filter, :flags,
6
+ :fflags, :data, :udata)
7
+ end
@@ -0,0 +1,122 @@
1
+ require 'thread'
2
+
3
+ # The high-level Kqueue interface. This provides fork-safety under Ruby 1.9
4
+ # and later (but not Ruby 1.8).
5
+ # This also provides memory protection from bugs due to not storing an
6
+ # external reference to an object, but still requires the user to store
7
+ # their own object references.
8
+ # Events registered to a Kqueue object cannot be shared across fork
9
+ # due to the underlying implementation of kqueue in *BSDs.
10
+ class SleepyPenguin::Kqueue
11
+
12
+ # Initialize a new Kqueue object, this allocates an underlying Kqueue::IO
13
+ # object and may fail if the system is out of file descriptors or
14
+ # kernel memory
15
+ def initialize
16
+ @io = SleepyPenguin::Kqueue::IO.new
17
+ @mtx = Mutex.new
18
+ @pid = $$
19
+ @copies = { @io => self }
20
+ end
21
+
22
+ # Kqueue objects may be watched by IO.select and similar methods
23
+ def to_io
24
+ @mtx.synchronize do
25
+ __kq_check
26
+ @io
27
+ end
28
+ end
29
+
30
+ def __kq_reinit # :nodoc:
31
+ @io = SleepyPenguin::Kqueue::IO.new
32
+ end
33
+
34
+ def __kq_check # :nodoc:
35
+ return if @pid == $$ || @io.closed?
36
+ unless @io.respond_to?(:autoclose=)
37
+ raise RuntimeError,
38
+ "Kqueue is not safe to use without IO#autoclose=, upgrade to Ruby 1.9+"
39
+ end
40
+
41
+ # kqueue has (strange) close-on-fork behavior
42
+ objects = @copies.values
43
+ @copies.each_key { |kqio| kqio.autoclose = false }
44
+ @copies.clear
45
+ __kq_reinit
46
+ objects.each do |obj|
47
+ io_dup = @io.dup
48
+ @copies[io_dup] = obj
49
+ end
50
+ @pid = $$
51
+ end
52
+
53
+ # A high-level wrapper around Kqueue::IO#kevent
54
+ # Users are responsible for ensuring +udata+ objects remain visible to the
55
+ # Ruby GC, otherwise ObjectSpace._id2ref may return invalid objects.
56
+ # Unlike the low-level Kqueue::IO#kevent, the block given yields only
57
+ # a single Kevent struct, not a 6-element array.
58
+ def kevent(changelist = nil, *args)
59
+ @mtx.synchronize { __kq_check }
60
+ if changelist
61
+ changelist = [ changelist ] if Struct === changelist
62
+
63
+ # store the object_id instead of the raw VALUE itself in kqueue and
64
+ # use _id2ref to safely recover the object without the possibility of
65
+ # invalid memory acccess.
66
+ #
67
+ # We may still raise and drop events due to user error
68
+ changelist = changelist.map do |item|
69
+ item = item.dup
70
+ item[5] = item[5].object_id
71
+ item
72
+ end
73
+ end
74
+
75
+ if block_given?
76
+ n = @io.kevent(changelist, *args) do |ident,filter,flags,
77
+ fflags,data,udata|
78
+ # This may raise and cause events to be lost,
79
+ # that's the users' fault/problem
80
+ udata = ObjectSpace._id2ref(udata)
81
+ yield SleepyPenguin::Kevent.new(ident, filter, flags,
82
+ fflags, data, udata)
83
+ end
84
+ else
85
+ n = @io.kevent(changelist, *args)
86
+ end
87
+ end
88
+
89
+ def initialize_copy(src) # :nodoc:
90
+ @mtx.synchronize do
91
+ __kq_check
92
+ rv = super
93
+ unless @io.closed?
94
+ @io = @io.dup
95
+ @copies[@io] = self
96
+ end
97
+ rv
98
+ end
99
+ end
100
+
101
+ # call-seq:
102
+ # kq.close -> nil
103
+ #
104
+ # Closes an existing Kqueue object and returns memory back to the kernel.
105
+ # Raises IOError if object is already closed.
106
+ def close
107
+ @mtx.synchronize do
108
+ @copies.delete(@io)
109
+ @io.close
110
+ end
111
+ end
112
+
113
+ # call-seq:
114
+ # kq.closed? -> true or false
115
+ #
116
+ # Returns whether or not an Kqueue object is closed.
117
+ def closed?
118
+ @mtx.synchronize do
119
+ @io.closed?
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,30 @@
1
+ class SleepyPenguin::Kqueue::IO
2
+ # :stopdoc:
3
+ # this file is only for Ruby 1.8 green threads compatibility
4
+ alias __kevent kevent
5
+ undef_method :kevent
6
+
7
+ def __update_timeout(expire_at)
8
+ now = Time.now
9
+ diff = expire_at - now
10
+ diff > 0 ? diff : 0
11
+ end
12
+
13
+ def kevent(changelist = nil, nevents = nil, timeout = nil)
14
+ if block_given?
15
+ expire_at = timeout ? Time.now + timeout : nil
16
+ begin
17
+ IO.select([self], nil, nil, timeout)
18
+ n = __kevent(changelist, nevents, 0) do |*args|
19
+ yield(*args)
20
+ end
21
+ end while n == 0 && timeout != 0 &&
22
+ (expire_at == nil || timeout = __update_timeout(expire_at))
23
+ n
24
+ else
25
+ # nevents should be zero or nil here
26
+ __kevent(changelist, nevents, 0)
27
+ end
28
+ end
29
+ # :startdoc:
30
+ end
data/pkg.mk CHANGED
@@ -36,7 +36,7 @@ $(ext_pfx)/$(ext)/%: $(ext)/% $(ext_d)
36
36
  install -m 644 $< $@
37
37
  $(ext_pfx)/$(ext)/Makefile: $(ext)/extconf.rb $(ext_d) $(ext_h)
38
38
  $(RM) -f $(@D)/*.o
39
- cd $(@D) && $(RUBY) $(CURDIR)/$(ext)/extconf.rb
39
+ cd $(@D) && $(RUBY) $(CURDIR)/$(ext)/extconf.rb $(EXTCONF_ARGS)
40
40
  ext_sfx := _ext.$(DLEXT)
41
41
  ext_dl := $(ext_pfx)/$(ext)/$(notdir $(ext)_ext.$(DLEXT))
42
42
  $(ext_dl): $(ext_src) $(ext_pfx_src) $(ext_pfx)/$(ext)/Makefile
@@ -0,0 +1,16 @@
1
+ require 'test/unit'
2
+ $-w = true
3
+ Thread.abort_on_exception = true
4
+ require 'sleepy_penguin/sp'
5
+
6
+ class TestConstants < Test::Unit::TestCase
7
+ def test_constants
8
+ assert_equal SleepyPenguin::SLEEPY_PENGUIN_VERSION,
9
+ SP::SLEEPY_PENGUIN_VERSION
10
+ v = SP::SLEEPY_PENGUIN_VERSION.split('.')
11
+
12
+ (0..2).each do |i|
13
+ assert_equal v[i], v[i].to_i.to_s, v.inspect
14
+ end
15
+ end
16
+ end
data/test/test_epoll.rb CHANGED
@@ -9,7 +9,6 @@ require 'sleepy_penguin'
9
9
 
10
10
  class TestEpoll < Test::Unit::TestCase
11
11
  include SleepyPenguin
12
- RBX = defined?(RUBY_ENGINE) && (RUBY_ENGINE == 'rbx')
13
12
 
14
13
  def setup
15
14
  @rd, @wr = IO.pipe
@@ -122,11 +121,9 @@ class TestEpoll < Test::Unit::TestCase
122
121
  end
123
122
 
124
123
  def teardown
125
- assert_nothing_raised do
126
- @rd.close unless @rd.closed?
127
- @wr.close unless @wr.closed?
128
- @ep.close unless @ep.closed?
129
- end
124
+ @rd.close unless @rd.closed?
125
+ @wr.close unless @wr.closed?
126
+ @ep.close unless @ep.closed?
130
127
  end
131
128
 
132
129
  def test_max_events_big
@@ -156,11 +153,17 @@ class TestEpoll < Test::Unit::TestCase
156
153
  end
157
154
 
158
155
  def test_signal_safe_wait_forever
156
+ sigpipe = IO.pipe
159
157
  time = {}
158
+ thr = Thread.new do
159
+ IO.select([sigpipe[0]]) # wait for USR1
160
+ sigpipe[0].read(1)
161
+ sleep 0.5
162
+ @wr.syswrite '.'
163
+ end
160
164
  trap(:USR1) do
161
165
  time[:USR1] = Time.now
162
- sleep 0.5
163
- @wr.write '.'
166
+ sigpipe[1].syswrite('.') # wake up thr
164
167
  end
165
168
  @ep.add @rd, Epoll::IN
166
169
  tmp = []
@@ -170,11 +173,9 @@ class TestEpoll < Test::Unit::TestCase
170
173
  exit!(0)
171
174
  end
172
175
  time[:START_WAIT] = Time.now
173
- assert_nothing_raised do
174
- @ep.wait do |flags, obj|
175
- tmp << [ flags, obj ]
176
- time[:EP] = Time.now
177
- end
176
+ @ep.wait do |flags, obj|
177
+ tmp << [ flags, obj ]
178
+ time[:EP] = Time.now
178
179
  end
179
180
  assert_equal([[Epoll::IN, @rd]], tmp)
180
181
  _, status = Process.waitpid2(pid)
@@ -183,9 +184,12 @@ class TestEpoll < Test::Unit::TestCase
183
184
  assert_in_delta(0.5, usr1_delay, 0.1, "usr1_delay=#{usr1_delay}")
184
185
  ep_delay = time[:EP] - time[:USR1]
185
186
  assert_in_delta(0.5, ep_delay, 0.1, "ep1_delay=#{ep_delay}")
187
+ assert_kind_of Thread, thr
188
+ thr.join
186
189
  ensure
190
+ sigpipe.each { |io| io.close }
187
191
  trap(:USR1, 'DEFAULT')
188
- end unless RBX
192
+ end
189
193
 
190
194
  def test_close
191
195
  @ep.add @rd, Epoll::IN
@@ -193,10 +197,12 @@ class TestEpoll < Test::Unit::TestCase
193
197
  thr = Thread.new { @ep.wait { |flags, obj| tmp << [ flags, obj ] } }
194
198
  @rd.close
195
199
  @wr.close
196
- assert_nil thr.join(0.01)
200
+ Thread.pass
201
+ assert_nil thr.join(0.25)
197
202
  assert thr.alive?
198
203
  thr.kill
199
204
  assert tmp.empty?
205
+ thr.join
200
206
  end
201
207
 
202
208
  def test_rdhup
@@ -222,12 +228,10 @@ class TestEpoll < Test::Unit::TestCase
222
228
 
223
229
  def test_multiple
224
230
  r, w = IO.pipe
225
- assert_nothing_raised do
226
- @ep.add r, Epoll::IN
227
- @ep.add @rd, Epoll::IN
228
- @ep.add w, Epoll::OUT
229
- @ep.add @wr, Epoll::OUT
230
- end
231
+ @ep.add r, Epoll::IN
232
+ @ep.add @rd, Epoll::IN
233
+ @ep.add w, Epoll::OUT
234
+ @ep.add @wr, Epoll::OUT
231
235
  tmp = []
232
236
  @ep.wait { |flags, obj| tmp << [ flags, obj ] }
233
237
  assert_equal 2, tmp.size
@@ -237,21 +241,6 @@ class TestEpoll < Test::Unit::TestCase
237
241
  assert ios.include?(w)
238
242
  end
239
243
 
240
- def test_gc
241
- assert_nothing_raised { 4096.times { Epoll.new } }
242
- assert ! @ep.closed?
243
- end unless RBX
244
-
245
- def test_gc_to_io
246
- assert_nothing_raised do
247
- 4096.times do
248
- ep = Epoll.new
249
- assert_kind_of IO, ep.to_io
250
- end
251
- end
252
- assert ! @ep.closed?
253
- end unless RBX
254
-
255
244
  def test_clone
256
245
  tmp = []
257
246
  clone = @ep.clone
@@ -259,7 +248,7 @@ class TestEpoll < Test::Unit::TestCase
259
248
  clone.add @wr, Epoll::OUT
260
249
  @ep.wait(nil, 0) { |flags, obj| tmp << [ flags, obj ] }
261
250
  assert_equal([[Epoll::OUT, @wr]], tmp)
262
- assert_nothing_raised { clone.close }
251
+ clone.close
263
252
  end
264
253
 
265
254
  def test_dup
@@ -269,16 +258,14 @@ class TestEpoll < Test::Unit::TestCase
269
258
  clone.add @wr, Epoll::OUT
270
259
  @ep.wait(nil, 0) { |flags, obj| tmp << [ flags, obj ] }
271
260
  assert_equal([[Epoll::OUT, @wr]], tmp)
272
- assert_nothing_raised { clone.close }
261
+ clone.close
273
262
  end
274
263
 
275
264
  def test_set_idempotency
276
- assert_nothing_raised do
277
- @ep.set @rd, Epoll::IN
278
- @ep.set @rd, Epoll::IN
279
- @ep.set @wr, Epoll::OUT
280
- @ep.set @wr, Epoll::OUT
281
- end
265
+ @ep.set @rd, Epoll::IN
266
+ @ep.set @rd, Epoll::IN
267
+ @ep.set @wr, Epoll::OUT
268
+ @ep.set @wr, Epoll::OUT
282
269
  end
283
270
 
284
271
  def test_wait_timeout
@@ -290,10 +277,8 @@ class TestEpoll < Test::Unit::TestCase
290
277
 
291
278
  def test_del
292
279
  assert_raises(Errno::ENOENT) { @ep.del(@rd) }
293
- assert_nothing_raised do
294
- @ep.add(@rd, Epoll::IN)
295
- @ep.del(@rd)
296
- end
280
+ @ep.add(@rd, Epoll::IN)
281
+ @ep.del(@rd)
297
282
  end
298
283
 
299
284
  def test_wait_read
@@ -368,7 +353,7 @@ class TestEpoll < Test::Unit::TestCase
368
353
  def test_delete
369
354
  assert_nil @ep.delete(@rd)
370
355
  assert_nil @ep.delete(@wr)
371
- assert_nothing_raised { @ep.add @rd, Epoll::IN }
356
+ @ep.add @rd, Epoll::IN
372
357
  assert_equal @rd, @ep.delete(@rd)
373
358
  assert_nil @ep.delete(@rd)
374
359
  end
@@ -466,12 +451,12 @@ class TestEpoll < Test::Unit::TestCase
466
451
  trap(:USR1, "DEFAULT")
467
452
  sleep 0.1
468
453
  ppid = Process.ppid
469
- nr.times { Process.kill(:USR1, ppid); sleep 0.01 }
454
+ nr.times { Process.kill(:USR1, ppid); sleep 0.05 }
470
455
  @wr.syswrite('.')
471
456
  exit!(0)
472
457
  end
473
458
  while tmp.empty?
474
- assert_nothing_raised { @ep.wait(nil, 100) { |flags,obj| tmp << obj } }
459
+ @ep.wait(nil, 100) { |flags,obj| tmp << obj }
475
460
  empty += 1
476
461
  end
477
462
  _, status = Process.waitpid2(pid)
@@ -545,4 +530,4 @@ class TestEpoll < Test::Unit::TestCase
545
530
  end
546
531
  @ep.wait(1) { |flags, io| assert_equal(first[0], io) }
547
532
  end
548
- end
533
+ end if defined?(SleepyPenguin::Epoll)