sleepy_penguin 1.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/README ADDED
@@ -0,0 +1,62 @@
1
+ = sleepy_penguin - Ruby I/O events for Linux
2
+
3
+ sleepy_penguin provides access to newer, Linux-only system calls to wait
4
+ on events from traditionally non-I/O sources. Bindings to the eventfd,
5
+ timerfd, and epoll interfaces are provided.
6
+
7
+ == Features
8
+
9
+ * Thread-safe blocking operations under both Ruby 1.8 and 1.9.
10
+
11
+ * Mostly working under Rubinius
12
+
13
+ * IO-like objects are backwards-compatible with IO.select.
14
+
15
+ * Epoll interface is fork-safe
16
+
17
+ * Unlike portable event frameworks, the Linux-only Epoll interface
18
+ allows using edge-triggered I/O for possibly improved performance
19
+
20
+ == Install
21
+
22
+ If you're using a packaged Ruby distribution, make sure you have a C
23
+ compiler and the matching Ruby development libraries and headers.
24
+
25
+ If you use RubyGems:
26
+
27
+ gem install sleepy_penguin
28
+
29
+ Otherwise grab the latest tarball from:
30
+
31
+ http://bogomips.org/sleepy_penguin/files/
32
+
33
+ Unpack it, and run "ruby setup.rb"
34
+
35
+ == Development
36
+
37
+ You can get the latest source via git from the following locations:
38
+
39
+ git://git.bogomips.org/sleepy_penguin.git
40
+ git://repo.or.cz/sleepy_penguin.git (mirror)
41
+
42
+ You may browse the code from the web and download the latest snapshot
43
+ tarballs here:
44
+
45
+ * http://git.bogomips.org/cgit/sleepy_penguin.git (cgit)
46
+ * http://repo.or.cz/w/sleepy_penguin.git (gitweb)
47
+
48
+ Inline patches (from "git format-patch") to the mailing list are
49
+ preferred because they allow code review and comments in the reply to
50
+ the patch.
51
+
52
+ We will adhere to mostly the same conventions for patch submissions as
53
+ git itself. See the Documentation/SubmittingPatches document
54
+ distributed with git on on patch submission guidelines to follow. Just
55
+ don't email the git mailing list or maintainer with sleepy_penguin patches.
56
+
57
+ == Contact
58
+
59
+ All feedback (bug reports, user/development discussion, patches, pull
60
+ requests) go to the mailing list: mailto:sleepy.penguin@librelist.com
61
+
62
+ * http://bogomips.org/sleepy_penguin/archives/
data/Rakefile ADDED
@@ -0,0 +1,167 @@
1
+ # -*- encoding: binary -*-
2
+ # most tasks are in the GNUmakefile which offers better parallelism
3
+ def tags
4
+ timefmt = '%Y-%m-%dT%H:%M:%SZ'
5
+ @tags ||= `git tag -l`.split(/\n/).map do |tag|
6
+ if %r{\Av[\d\.]+\z} =~ tag
7
+ header, subject, body = `git cat-file tag #{tag}`.split(/\n\n/, 3)
8
+ header = header.split(/\n/)
9
+ tagger = header.grep(/\Atagger /).first
10
+ body ||= "initial"
11
+ {
12
+ :time => Time.at(tagger.split(/ /)[-2].to_i).utc.strftime(timefmt),
13
+ :tagger_name => %r{^tagger ([^<]+)}.match(tagger)[1].strip,
14
+ :tagger_email => %r{<([^>]+)>}.match(tagger)[1].strip,
15
+ :id => `git rev-parse refs/tags/#{tag}`.chomp!,
16
+ :tag => tag,
17
+ :subject => subject,
18
+ :body => body,
19
+ }
20
+ end
21
+ end.compact.sort { |a,b| b[:time] <=> a[:time] }
22
+ end
23
+
24
+ cgit_url = "http://git.bogomips.org/cgit/sleepy_penguin.git"
25
+ git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/sleepy_penguin.git'
26
+ web_url = "http://bogomips.org/sleepy_penguin"
27
+
28
+ desc 'prints news as an Atom feed'
29
+ task :news_atom do
30
+ require 'nokogiri'
31
+ new_tags = tags[0,10]
32
+ puts(Nokogiri::XML::Builder.new do
33
+ feed :xmlns => "http://www.w3.org/2005/Atom" do
34
+ id! "#{web_url}NEWS.atom.xml"
35
+ title "sleepy_penguin news"
36
+ subtitle "epoll"
37
+ link! :rel => "alternate", :type => "text/html",
38
+ :href => "#{web_url}NEWS.html"
39
+ updated(new_tags.empty? ? "1970-01-01T00:00:00Z" : new_tags.first[:time])
40
+ new_tags.each do |tag|
41
+ entry do
42
+ title tag[:subject]
43
+ updated tag[:time]
44
+ published tag[:time]
45
+ author {
46
+ name tag[:tagger_name]
47
+ email tag[:tagger_email]
48
+ }
49
+ url = "#{cgit_url}/tag/?id=#{tag[:tag]}"
50
+ link! :rel => "alternate", :type => "text/html", :href =>url
51
+ id! url
52
+ message_only = tag[:body].split(/\n.+\(\d+\):\n {6}/s).first.strip
53
+ content({:type =>:text}, message_only)
54
+ content(:type =>:xhtml) { pre tag[:body] }
55
+ end
56
+ end
57
+ end
58
+ end.to_xml)
59
+ end
60
+
61
+ desc 'prints RDoc-formatted news'
62
+ task :news_rdoc do
63
+ tags.each do |tag|
64
+ time = tag[:time].tr!('T', ' ').gsub!(/:\d\dZ/, ' UTC')
65
+ puts "=== #{tag[:tag].sub(/^v/, '')} / #{time}"
66
+ puts ""
67
+
68
+ body = tag[:body]
69
+ puts tag[:body].gsub(/^/sm, " ").gsub(/[ \t]+$/sm, "")
70
+ puts ""
71
+ end
72
+ end
73
+
74
+ desc "print release changelog for Rubyforge"
75
+ task :release_changes do
76
+ version = ENV['VERSION'] or abort "VERSION= needed"
77
+ version = "v#{version}"
78
+ vtags = tags.map { |tag| tag[:tag] =~ /\Av/ and tag[:tag] }.sort
79
+ prev = vtags[vtags.index(version) - 1]
80
+ if prev
81
+ system('git', 'diff', '--stat', prev, version) or abort $?
82
+ puts ""
83
+ system('git', 'log', "#{prev}..#{version}") or abort $?
84
+ else
85
+ system('git', 'log', version) or abort $?
86
+ end
87
+ end
88
+
89
+ desc "print release notes for Rubyforge"
90
+ task :release_notes do
91
+ spec = Gem::Specification.load('sleepy_penguin.gemspec')
92
+ puts spec.description.strip
93
+ puts ""
94
+ puts "* #{spec.homepage}"
95
+ puts "* #{spec.email}"
96
+ puts "* #{git_url}"
97
+
98
+ _, _, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3)
99
+ print "\nChanges:\n\n"
100
+ puts body
101
+ end
102
+
103
+ desc "read news article from STDIN and post to rubyforge"
104
+ task :publish_news do
105
+ require 'rubyforge'
106
+ spec = Gem::Specification.load('sleepy_penguin.gemspec')
107
+ tmp = Tempfile.new('rf-news')
108
+ _, subject, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3)
109
+ tmp.puts subject
110
+ tmp.puts
111
+ tmp.puts spec.description.strip
112
+ tmp.puts ""
113
+ tmp.puts "* #{spec.homepage}"
114
+ tmp.puts "* #{spec.email}"
115
+ tmp.puts "* #{git_url}"
116
+ tmp.print "\nChanges:\n\n"
117
+ tmp.puts body
118
+ tmp.flush
119
+ system(ENV["VISUAL"], tmp.path) or abort "#{ENV["VISUAL"]} failed: #$?"
120
+ msg = File.readlines(tmp.path)
121
+ subject = msg.shift
122
+ blank = msg.shift
123
+ blank == "\n" or abort "no newline after subject!"
124
+ subject.strip!
125
+ body = msg.join("").strip!
126
+
127
+ rf = RubyForge.new.configure
128
+ rf.login
129
+ rf.post_news('rainbows', subject, body)
130
+ end
131
+
132
+ desc "post to RAA"
133
+ task :raa_update do
134
+ require 'net/http'
135
+ require 'net/netrc'
136
+ rc = Net::Netrc.locate('sleepy_penguin-raa') or abort "~/.netrc not found"
137
+ password = rc.password
138
+
139
+ s = Gem::Specification.load('sleepy_penguin.gemspec')
140
+ desc = [ s.description.strip ]
141
+ desc << ""
142
+ desc << "* #{s.email}"
143
+ desc << "* #{git_url}"
144
+ desc << "* #{cgit_url}"
145
+ desc = desc.join("\n")
146
+ uri = URI.parse('http://raa.ruby-lang.org/regist.rhtml')
147
+ form = {
148
+ :name => s.name,
149
+ :short_description => s.summary,
150
+ :version => s.version.to_s,
151
+ :status => 'experimental',
152
+ :owner => s.authors.first,
153
+ :email => s.email,
154
+ :category_major => 'Library',
155
+ :category_minor => 'System',
156
+ :url => s.homepage,
157
+ :download => 'http://rubyforge.org/frs/?group_id=8977',
158
+ :license => "LGPL",
159
+ :description_style => 'Plain',
160
+ :description => desc,
161
+ :pass => password,
162
+ :submit => 'Update',
163
+ }
164
+ res = Net::HTTP.post_form(uri, form)
165
+ p res
166
+ puts res.body
167
+ end
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ * SignalFD interface
2
+
3
+ * Inotify? Several libraries already support it, but it fits this project, too
@@ -0,0 +1,563 @@
1
+ #include "sleepy_penguin.h"
2
+ #include <sys/epoll.h>
3
+ #include <pthread.h>
4
+ #ifdef HAVE_RUBY_ST_H
5
+ # include <ruby/st.h>
6
+ #else
7
+ # include <st.h>
8
+ #endif
9
+
10
+ #ifndef EPOLL_CLOEXEC
11
+ # define EPOLL_CLOEXEC (int)(02000000)
12
+ #endif
13
+
14
+ #define EP_RECREATE (-2)
15
+
16
+ #ifndef HAVE_RB_MEMERROR
17
+ static void rb_memerror(void)
18
+ {
19
+ static const char e[] = "[FATAL] failed to allocate memory\n";
20
+ write(2, e, sizeof(e) - 1);
21
+ abort();
22
+ }
23
+ #endif
24
+ #ifndef HAVE_RB_IO_CLOSE
25
+ static VALUE rb_io_close(VALUE io)
26
+ {
27
+ return rb_funcall(io, rb_intern("close"), 0);
28
+ }
29
+ #endif
30
+
31
+ static st_table *active;
32
+ static const int step = 64; /* unlikely to grow unless you're huge */
33
+ static VALUE cEpoll_IO;
34
+ static ID id_for_fd;
35
+
36
+ static void pack_event_data(struct epoll_event *event, VALUE obj)
37
+ {
38
+ event->data.ptr = (void *)obj;
39
+ }
40
+
41
+ static VALUE unpack_event_data(struct epoll_event *event)
42
+ {
43
+ return (VALUE)event->data.ptr;
44
+ }
45
+
46
+ struct rb_epoll {
47
+ int fd;
48
+ int timeout;
49
+ int maxevents;
50
+ int capa;
51
+ struct epoll_event *events;
52
+ VALUE io;
53
+ int flags;
54
+ };
55
+
56
+ static struct rb_epoll *ep_get(VALUE self)
57
+ {
58
+ struct rb_epoll *ep;
59
+
60
+ Data_Get_Struct(self, struct rb_epoll, ep);
61
+
62
+ return ep;
63
+ }
64
+
65
+ #ifndef HAVE_EPOLL_CREATE1
66
+ /*
67
+ * fake epoll_create1() since some systems don't have it.
68
+ * Don't worry about thread-safety since current Ruby 1.9 won't
69
+ * call this without GVL.
70
+ */
71
+ static int epoll_create1(int flags)
72
+ {
73
+ int fd = epoll_create(1024); /* size ignored since 2.6.8 */
74
+
75
+ if (fd < 0 || flags == 0)
76
+ return fd;
77
+
78
+ if ((flags & EPOLL_CLOEXEC) && (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1))
79
+ goto err;
80
+ return fd;
81
+ err:
82
+ {
83
+ int saved_errno = errno;
84
+ close(fd);
85
+ errno = saved_errno;
86
+ return -1;
87
+ }
88
+ }
89
+ #endif
90
+
91
+ static void gcmark(void *ptr)
92
+ {
93
+ struct rb_epoll *ep = ptr;
94
+
95
+ rb_gc_mark(ep->io);
96
+ }
97
+
98
+ static void gcfree(void *ptr)
99
+ {
100
+ struct rb_epoll *ep = ptr;
101
+
102
+ xfree(ep->events);
103
+ if (ep->fd >= 0) {
104
+ st_data_t key = ep->fd;
105
+ st_delete(active, &key, NULL);
106
+ }
107
+ if (NIL_P(ep->io) && ep->fd >= 0) {
108
+ /* can't raise during GC */
109
+ (void)close(ep->fd);
110
+ errno = 0;
111
+ }
112
+ /* let GC take care of the underlying IO object if there is one */
113
+
114
+ xfree(ep);
115
+ }
116
+
117
+ static VALUE alloc(VALUE klass)
118
+ {
119
+ struct rb_epoll *ep;
120
+ VALUE self;
121
+
122
+ self = Data_Make_Struct(klass, struct rb_epoll, gcmark, gcfree, ep);
123
+ ep->fd = -1;
124
+ ep->io = Qnil;
125
+ ep->capa = step;
126
+ ep->flags = EPOLL_CLOEXEC;
127
+ ep->events = xmalloc(sizeof(struct epoll_event) * ep->capa);
128
+
129
+ return self;
130
+ }
131
+
132
+ static void my_epoll_create(struct rb_epoll *ep)
133
+ {
134
+ ep->fd = epoll_create1(ep->flags);
135
+
136
+ if (ep->fd == -1) {
137
+ if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
138
+ rb_gc();
139
+ ep->fd = epoll_create1(ep->flags);
140
+ }
141
+ if (ep->fd == -1)
142
+ rb_sys_fail("epoll_create1");
143
+ }
144
+ st_insert(active, (st_data_t)ep->fd, (st_data_t)ep);
145
+ }
146
+
147
+ static void ep_check(struct rb_epoll *ep)
148
+ {
149
+ if (ep->fd == EP_RECREATE)
150
+ my_epoll_create(ep);
151
+ if (ep->fd == -1)
152
+ rb_raise(rb_eIOError, "closed");
153
+ }
154
+
155
+ /*
156
+ * creates a new Epoll object with an optional +flags+ argument.
157
+ * +flags+ may currently be +Epoll::CLOEXEC+ or 0 (or nil)
158
+ */
159
+ static VALUE init(int argc, VALUE *argv, VALUE self)
160
+ {
161
+ int flags;
162
+ struct rb_epoll *ep = ep_get(self);
163
+ VALUE fl;
164
+
165
+ rb_scan_args(argc, argv, "01", &fl);
166
+ if (NIL_P(fl)) {
167
+ flags = EPOLL_CLOEXEC;
168
+ } else {
169
+ switch (TYPE(fl)) {
170
+ case T_FIXNUM:
171
+ case T_BIGNUM:
172
+ flags = NUM2INT(fl);
173
+ break;
174
+ default:
175
+ rb_raise(rb_eArgError, "flags must be an integer");
176
+ }
177
+ }
178
+ ep->flags = flags;
179
+ my_epoll_create(ep);
180
+
181
+ return self;
182
+ }
183
+
184
+ static VALUE ctl(VALUE self, VALUE io, VALUE flags, int op)
185
+ {
186
+ struct epoll_event event;
187
+ struct rb_epoll *ep = ep_get(self);
188
+ int fd = my_fileno(io);
189
+ int rv;
190
+
191
+ ep_check(ep);
192
+ event.events = NUM2UINT(flags);
193
+ pack_event_data(&event, io);
194
+
195
+ rv = epoll_ctl(ep->fd, op, fd, &event);
196
+ if (rv == -1) {
197
+ if (errno == ENOMEM) {
198
+ rb_gc();
199
+ rv = epoll_ctl(ep->fd, op, fd, &event);
200
+ }
201
+ if (rv == -1)
202
+ rb_sys_fail("epoll_ctl");
203
+ }
204
+
205
+ return INT2NUM(rv);
206
+ }
207
+
208
+ /*
209
+ */
210
+ static VALUE set(VALUE self, VALUE io, VALUE flags)
211
+ {
212
+ struct epoll_event event;
213
+ struct rb_epoll *ep = ep_get(self);
214
+ int fd = my_fileno(io);
215
+ int rv;
216
+
217
+ ep_check(ep);
218
+ event.events = NUM2UINT(flags);
219
+ pack_event_data(&event, io);
220
+
221
+ rv = epoll_ctl(ep->fd, EPOLL_CTL_MOD, fd, &event);
222
+ if (rv == -1) {
223
+ if (errno == ENOENT) {
224
+ rv = epoll_ctl(ep->fd, EPOLL_CTL_ADD, fd, &event);
225
+ if (rv == -1)
226
+ rb_sys_fail("epoll_ctl - add");
227
+ return INT2NUM(rv);
228
+ }
229
+ rb_sys_fail("epoll_ctl - mod");
230
+ }
231
+
232
+ return INT2NUM(rv);
233
+ }
234
+
235
+ static VALUE epwait_result(struct rb_epoll *ep, int n)
236
+ {
237
+ int i;
238
+ struct epoll_event *epoll_event = ep->events;
239
+ VALUE obj_events, obj;
240
+
241
+ if (n == -1)
242
+ rb_sys_fail("epoll_wait");
243
+
244
+ for (i = n; --i >= 0; epoll_event++) {
245
+ obj_events = UINT2NUM(epoll_event->events);
246
+ obj = unpack_event_data(epoll_event);
247
+ rb_yield_values(2, obj_events, obj);
248
+ }
249
+
250
+ /* grow our event buffer for the next epoll_wait call */
251
+ if (n == ep->capa) {
252
+ xfree(ep->events);
253
+ ep->capa += step;
254
+ ep->events = xmalloc(sizeof(struct epoll_event) * ep->capa);
255
+ }
256
+
257
+ return INT2NUM(n);
258
+ }
259
+
260
+ #if defined(HAVE_RB_THREAD_BLOCKING_REGION)
261
+ static VALUE nogvl_wait(void *args)
262
+ {
263
+ struct rb_epoll *ep = args;
264
+ int n = epoll_wait(ep->fd, ep->events, ep->maxevents, ep->timeout);
265
+
266
+ return (VALUE)n;
267
+ }
268
+
269
+ static VALUE real_epwait(struct rb_epoll *ep)
270
+ {
271
+ int n;
272
+
273
+ do {
274
+ n = (int)rb_thread_blocking_region(nogvl_wait, ep,
275
+ RUBY_UBF_IO, 0);
276
+ } while (n == -1 && errno == EINTR);
277
+
278
+ return epwait_result(ep, n);
279
+ }
280
+ #else /* 1.8 Green thread compatible code */
281
+ /*
282
+ * we have to worry about green threads and always pass zero
283
+ * as the timeout for epoll_wait :(
284
+ */
285
+ # include <rubysig.h>
286
+ # include <sys/time.h>
287
+
288
+ /* in case _BSD_SOURCE doesn't give us this macro */
289
+ #ifndef timersub
290
+ # define timersub(a, b, result) \
291
+ do { \
292
+ (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
293
+ (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
294
+ if ((result)->tv_usec < 0) { \
295
+ --(result)->tv_sec; \
296
+ (result)->tv_usec += 1000000; \
297
+ } \
298
+ } while (0)
299
+ #endif
300
+
301
+ static int safe_epoll_wait(struct rb_epoll *ep)
302
+ {
303
+ int n;
304
+
305
+ TRAP_BEG;
306
+ n = epoll_wait(ep->fd, ep->events, ep->maxevents, 0);
307
+ TRAP_END;
308
+
309
+ return n;
310
+ }
311
+
312
+ static int epwait_forever(struct rb_epoll *ep)
313
+ {
314
+ int n;
315
+
316
+ do {
317
+ (void)rb_io_wait_readable(ep->fd);
318
+ n = safe_epoll_wait(ep);
319
+ } while (n == 0 || (n == -1 && errno == EINTR));
320
+
321
+ return n;
322
+ }
323
+
324
+ static int epwait_timed(struct rb_epoll *ep)
325
+ {
326
+ struct timeval tv;
327
+
328
+ tv.tv_sec = ep->timeout / 1000;
329
+ tv.tv_usec = (ep->timeout % 1000) * 1000;
330
+
331
+ for (;;) {
332
+ struct timeval t0, now, diff;
333
+ int n;
334
+ fd_set rfds;
335
+
336
+ FD_ZERO(&rfds);
337
+ FD_SET(ep->fd, &rfds);
338
+
339
+ gettimeofday(&t0, NULL);
340
+ (void)rb_thread_select(ep->fd + 1, &rfds, NULL, NULL, &tv);
341
+ n = safe_epoll_wait(ep);
342
+
343
+ /*
344
+ * if we got EINTR from epoll_wait /and/ timed out
345
+ * just consider it a timeout and don't raise an error
346
+ */
347
+
348
+ if (n > 0 || (n == -1 && errno != EINTR))
349
+ return n;
350
+
351
+ gettimeofday(&now, NULL);
352
+ timersub(&now, &t0, &diff);
353
+ timersub(&tv, &diff, &tv);
354
+
355
+ if (tv.tv_usec < 0 || tv.tv_sec < 0)
356
+ return (n == -1) ? 0 : n;
357
+ }
358
+
359
+ assert("should never get here (epwait_timed)");
360
+ return -1;
361
+ }
362
+
363
+ static VALUE real_epwait(struct rb_epoll *ep)
364
+ {
365
+ int n;
366
+
367
+ if (ep->timeout == -1)
368
+ n = epwait_forever(ep);
369
+ else if (ep->timeout == 0)
370
+ n = safe_epoll_wait(ep);
371
+ else
372
+ n = epwait_timed(ep);
373
+
374
+ return epwait_result(ep, n);
375
+ }
376
+ #endif /* 1.8 Green thread compatibility code */
377
+
378
+ static VALUE epwait(int argc, VALUE *argv, VALUE self)
379
+ {
380
+ VALUE timeout, maxevents;
381
+ struct rb_epoll *ep = ep_get(self);
382
+
383
+ ep_check(ep);
384
+ rb_need_block();
385
+ rb_scan_args(argc, argv, "02", &maxevents, &timeout);
386
+ ep->timeout = NIL_P(timeout) ? -1 : NUM2INT(timeout);
387
+ ep->maxevents = NIL_P(maxevents) ? ep->capa : NUM2INT(maxevents);
388
+
389
+ if (ep->maxevents > ep->capa) {
390
+ xfree(ep->events);
391
+ ep->capa = ep->maxevents;
392
+ ep->events = xmalloc(sizeof(struct epoll_event) * ep->capa);
393
+ }
394
+
395
+ return real_epwait(ep);
396
+ }
397
+
398
+ /* adds +io+ object the +self+ with +flags+ */
399
+ static VALUE add(VALUE self, VALUE io, VALUE flags)
400
+ {
401
+ return ctl(self, io, flags, EPOLL_CTL_ADD);
402
+ }
403
+
404
+ /* adds +io+ object the +self+ with +flags+ */
405
+ static VALUE del(VALUE self, VALUE io)
406
+ {
407
+ return ctl(self, io, INT2NUM(0), EPOLL_CTL_DEL);
408
+ }
409
+
410
+ static VALUE mod(VALUE self, VALUE io, VALUE flags)
411
+ {
412
+ return ctl(self, io, flags, EPOLL_CTL_MOD);
413
+ }
414
+
415
+ static VALUE to_io(VALUE self)
416
+ {
417
+ struct rb_epoll *ep = ep_get(self);
418
+
419
+ ep_check(ep);
420
+
421
+ if (NIL_P(ep->io))
422
+ ep->io = rb_funcall(cEpoll_IO, id_for_fd, 1, INT2NUM(ep->fd));
423
+
424
+ return ep->io;
425
+ }
426
+
427
+ static VALUE epclose(VALUE self)
428
+ {
429
+ struct rb_epoll *ep = ep_get(self);
430
+
431
+ if (ep->fd >= 0) {
432
+ st_data_t key = ep->fd;
433
+ st_delete(active, &key, NULL);
434
+ }
435
+
436
+ if (NIL_P(ep->io)) {
437
+ if (ep->fd == EP_RECREATE) {
438
+ ep->fd = -1;
439
+ } else if (ep->fd == -1) {
440
+ rb_raise(rb_eIOError, "closed");
441
+ } else {
442
+ int e = close(ep->fd);
443
+
444
+ ep->fd = -1;
445
+ if (e == -1)
446
+ rb_sys_fail("close");
447
+ }
448
+ } else {
449
+ ep->fd = -1;
450
+ rb_io_close(ep->io);
451
+ }
452
+
453
+ return Qnil;
454
+ }
455
+
456
+ static VALUE epclosed(VALUE self)
457
+ {
458
+ struct rb_epoll *ep = ep_get(self);
459
+
460
+ return ep->fd == -1 ? Qtrue : Qfalse;
461
+ }
462
+
463
+ static int cloexec_dup(struct rb_epoll *ep)
464
+ {
465
+ #ifdef F_DUPFD_CLOEXEC
466
+ int flags = ep->flags & EPOLL_CLOEXEC ? F_DUPFD_CLOEXEC : F_DUPFD;
467
+ int fd = fcntl(ep->fd, flags, 0);
468
+ #else
469
+ int fd = dup(ep->fd);
470
+ if (fd >= 0)
471
+ (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
472
+ #endif
473
+ return fd;
474
+ }
475
+
476
+ static VALUE init_copy(VALUE copy, VALUE orig)
477
+ {
478
+ struct rb_epoll *a = ep_get(orig);
479
+ struct rb_epoll *b = ep_get(copy);
480
+
481
+ assert(a->events && b->events && a->events != b->events &&
482
+ NIL_P(b->io) && "Ruby broken?");
483
+
484
+ ep_check(a);
485
+ b->flags = a->flags;
486
+ b->fd = cloexec_dup(a);
487
+ if (b->fd == -1) {
488
+ if (errno == ENFILE || errno == EMFILE) {
489
+ rb_gc();
490
+ b->fd = cloexec_dup(a);
491
+ }
492
+ if (b->fd == -1)
493
+ rb_sys_fail("dup");
494
+ }
495
+ st_insert(active, (st_data_t)b->fd, (st_data_t)b);
496
+
497
+ return copy;
498
+ }
499
+
500
+ /*
501
+ * we close (or lose to GC) epoll descriptors at fork to avoid leakage
502
+ * and invalid objects being referenced later in the child
503
+ */
504
+ static int ep_atfork(st_data_t key, st_data_t value, void *ignored)
505
+ {
506
+ struct rb_epoll *ep = (struct rb_epoll *)value;
507
+
508
+ if (NIL_P(ep->io)) {
509
+ if (ep->fd >= 0)
510
+ (void)close(ep->fd);
511
+ } else {
512
+ ep->io = Qnil; /* must let GC take care of it later :< */
513
+ }
514
+ ep->fd = EP_RECREATE;
515
+
516
+ return ST_CONTINUE;
517
+ }
518
+
519
+ static void atfork_child(void)
520
+ {
521
+ st_table *old = active;
522
+
523
+ active = st_init_numtable();
524
+ st_foreach(old, ep_atfork, (st_data_t)NULL);
525
+ st_free_table(old);
526
+ }
527
+
528
+ void sleepy_penguin_init_epoll(void)
529
+ {
530
+ VALUE mSleepyPenguin, cEpoll;
531
+
532
+ mSleepyPenguin = rb_const_get(rb_cObject, rb_intern("SleepyPenguin"));
533
+ cEpoll = rb_define_class_under(mSleepyPenguin, "Epoll", rb_cObject);
534
+ cEpoll_IO = rb_define_class_under(cEpoll, "IO", rb_cIO);
535
+ rb_define_method(cEpoll, "initialize", init, -1);
536
+ rb_define_method(cEpoll, "initialize_copy", init_copy, 1);
537
+ rb_define_alloc_func(cEpoll, alloc);
538
+ rb_define_method(cEpoll, "to_io", to_io, 0);
539
+ rb_define_method(cEpoll, "close", epclose, 0);
540
+ rb_define_method(cEpoll, "closed?", epclosed, 0);
541
+ rb_define_method(cEpoll, "add", add, 2);
542
+ rb_define_method(cEpoll, "mod", mod, 2);
543
+ rb_define_method(cEpoll, "del", del, 1);
544
+ rb_define_method(cEpoll, "set", set, 2);
545
+ rb_define_method(cEpoll, "wait", epwait, -1);
546
+ rb_define_const(cEpoll, "CLOEXEC", INT2NUM(EPOLL_CLOEXEC));
547
+ rb_define_const(cEpoll, "IN", INT2NUM(EPOLLIN));
548
+ rb_define_const(cEpoll, "OUT", INT2NUM(EPOLLOUT));
549
+ rb_define_const(cEpoll, "RDHUP", INT2NUM(EPOLLRDHUP));
550
+ rb_define_const(cEpoll, "PRI", INT2NUM(EPOLLPRI));
551
+ rb_define_const(cEpoll, "ERR", INT2NUM(EPOLLERR));
552
+ rb_define_const(cEpoll, "HUP", INT2NUM(EPOLLHUP));
553
+ rb_define_const(cEpoll, "ET", INT2NUM(EPOLLET));
554
+ rb_define_const(cEpoll, "ONESHOT", INT2NUM(EPOLLONESHOT));
555
+ id_for_fd = rb_intern("for_fd");
556
+ active = st_init_numtable();
557
+
558
+ if (pthread_atfork(NULL, NULL, atfork_child) != 0) {
559
+ rb_gc();
560
+ if (pthread_atfork(NULL, NULL, atfork_child) != 0)
561
+ rb_memerror();
562
+ }
563
+ }