sleepy_penguin 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }