ktools 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ = ktools
2
+
3
+ Bringing common kernel APIs into Ruby using FFI.
4
+
5
+ http://www.github.com/yakischloba/ktools
6
+
7
+ == Synopsis
8
+
9
+ irb(main):001:0> require 'ktools'
10
+ => true
11
+ irb(main):002:0> r, w = IO.pipe
12
+ => [#<IO:0x4fa90c>, #<IO:0x4fa880>]
13
+ irb(main):003:0> kq = Kqueue.new
14
+ => #<Kernel::Kqueue:0x4f43a4 @kqfd=6, @fds={}>
15
+ irb(main):004:0> kq.add(:socket, r, :events => [:read, :write])
16
+ => true
17
+ irb(main):005:0> kq.poll
18
+ => []
19
+ irb(main):006:0> w.write "foo"
20
+ => 3
21
+ irb(main):007:0> kq.poll
22
+ => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :events=>[:read]}]
23
+ irb(main):008:0> [r, w, kq].each {|i| i.close}
24
+
25
+
26
+ == Features
27
+
28
+ I plan to support the following kernel APIs:
29
+
30
+ * kqueue (works - see tests/test_kqueue.rb)
31
+ * epoll (works - see tests/test_epoll.rb)
32
+ * inotify
33
+ * netlink
34
+
35
+ and maybe some others! I will at first hook up the C interfaces as directly
36
+ as possible, and then write Rubyist-friendly wrappers around them.
37
+
38
+ Currently kqueue and epoll have Ruby wrapper APIs.
39
+
40
+
41
+ == Install
42
+
43
+ 1. git clone git://github.com/yakischloba/ktools.git
44
+ 2. cd ktools
45
+ 3. gem build ktools.gemspec
46
+ 4. sudo gem install ktools-x.x.x.gem
47
+
48
+ Also gems are on Rubyforge, so you can simply 'sudo gem install ktools', but commits will
49
+ be frequent for some time, so you'll probably want to be pulling the latest from Github.
50
+
51
+ == Documentation
52
+
53
+ * Documentation is located at http://ktools.rubyforge.org
54
+ * Familiarity with the actual kernel subsystems is helpful for nitty-gritty usage or debugging issues.
55
+
56
+ == Help
57
+
58
+ Please file all issues on the Github issue tracker. Patches (with tests) are welcome and
59
+ encouraged, as are suggestions about API design, etc. It's all up in the air right now.
60
+
61
+ * yakischloba on freenode
62
+ * jakecdouglas at gmail
data/Rakefile CHANGED
@@ -12,7 +12,6 @@ end
12
12
  task :config do
13
13
  $ktools_defines = []
14
14
  $ktools_dlext = RbConfig::expand(CONFIG['DLEXT'])
15
- (add_define "HAVE_TBR" and build_against_ruby_stuff = true) if have_func('rb_thread_blocking_region')
16
15
  add_define "HAVE_KQUEUE" if have_header("sys/event.h") and have_header("sys/queue.h")
17
16
  #add_define "HAVE_INOTIFY" if inotify = have_func('inotify_init', 'sys/inotify.h')
18
17
  #add_define "HAVE_OLD_INOTIFY" if !inotify && have_macro('__NR_inotify_init', 'sys/syscall.h')
@@ -34,17 +33,10 @@ task :config do
34
33
  $ktools_srcs = ["ktools.c"]
35
34
  $ktools_srcs << "kqueue.c" if $ktools_defines.include?("-DHAVE_KQUEUE")
36
35
  $ktools_srcs << "inotify.c" if $ktools_defines.include?("-DHAVE_INOTIFY")
37
- $ktools_srcs << "epoll.c" if $ktools_defines.include?("-DHAVE_EPOLL")
38
36
  $ktools_srcs << "netlink.c" if $ktools_defines.include?("-DHAVE_NETLINK")
39
37
 
40
- if CONFIG["rubyhdrdir"]
41
- hdrdir = RbConfig::expand(CONFIG["rubyhdrdir"])
42
- $ktools_includes = "-I. -I#{hdrdir}/#{RbConfig::expand(CONFIG["sitearch"])} -I#{hdrdir}" if build_against_ruby_stuff
43
- end
44
-
45
38
  $ktools_ldshared = RbConfig::expand(CONFIG['LDSHARED'])
46
39
  $ktools_ldshared << " -o ../lib/ktools.#{$ktools_dlext} " + $ktools_srcs.collect{|x| x.gsub(/\.c/, ".o")}.join(" ")
47
- $ktools_ldshared << " -L#{RbConfig::expand(CONFIG['libdir'])} #{RbConfig::expand(CONFIG['LIBRUBYARG_SHARED'])}" if build_against_ruby_stuff
48
40
  end
49
41
 
50
42
  task :clean do
@@ -2,38 +2,9 @@
2
2
 
3
3
  #include "kqueue.h"
4
4
 
5
- #ifdef HAVE_TBR
6
- #include <ruby.h>
7
- #endif
8
-
9
5
  void wrap_evset(struct kevent *kev, unsigned int ident, short filter, unsigned short flags, unsigned int fflags, int data, void *udata)
10
6
  {
11
7
  EV_SET(kev, ident, filter, flags, fflags, data, udata);
12
8
  }
13
9
 
14
- int wrap_kevent(int kqfd, struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, struct timespec *timeout)
15
- {
16
- #ifdef HAVE_TBR
17
- struct wrapped_kevent wevent;
18
- wevent.kqfd = kqfd;
19
- wevent.changelist = changelist;
20
- wevent.nchanges = nchanges;
21
- wevent.eventlist = eventlist;
22
- wevent.nevents = nevents;
23
- wevent.timeout = timeout;
24
- wevent.result = -1;
25
- rb_thread_blocking_region((rb_blocking_function_t *) tbr_kevent, &wevent, RUBY_UBF_IO, 0);
26
- return wevent.result;
27
- #else
28
- return kevent(kqfd, changelist, nchanges, eventlist, nevents, timeout);
29
- #endif
30
- }
31
-
32
- #ifdef HAVE_TBR
33
- void tbr_kevent(struct wrapped_kevent *wevent)
34
- {
35
- wevent->result = kevent(wevent->kqfd, wevent->changelist, wevent->nchanges, wevent->eventlist, wevent->nevents, wevent->timeout);
36
- }
37
- #endif
38
-
39
10
  #endif
@@ -3,18 +3,4 @@
3
3
  #include <sys/event.h>
4
4
  #include <sys/queue.h>
5
5
 
6
- #ifdef HAVE_TBR
7
- struct wrapped_kevent {
8
- int kqfd;
9
- struct kevent *changelist;
10
- int nchanges;
11
- struct kevent *eventlist;
12
- int nevents;
13
- struct timespec *timeout;
14
- int result;
15
- };
16
-
17
- void tbr_kevent(struct wrapped_kevent*);
18
- #endif
19
-
20
6
  #endif
@@ -2,7 +2,7 @@
2
2
  #define KTOOLS_H
3
3
 
4
4
  #ifdef HAVE_EPOLL
5
- #include "epoll.h"
5
+ #include <sys/epoll.h>
6
6
  #endif
7
7
 
8
8
  #ifdef HAVE_KQUEUE
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "ktools"
3
- s.version = "0.0.2"
4
- s.date = "2009-04-22"
3
+ s.version = "0.0.3"
4
+ s.date = "2009-04-26"
5
5
  s.authors = ["Jake Douglas"]
6
6
  s.email = "jakecdouglas@gmail.com"
7
7
  s.rubyforge_project = "ktools"
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
  s.extensions = ["Rakefile"]
15
15
  s.files =
16
16
  ["ktools.gemspec",
17
- "README",
17
+ "README.rdoc",
18
18
  "Rakefile",
19
19
  "lib/ktools.rb",
20
20
  "lib/ktools/ktools.rb",
@@ -22,8 +22,6 @@ Gem::Specification.new do |s|
22
22
  "lib/ktools/kqueue.rb",
23
23
  "ext/ktools.c",
24
24
  "ext/ktools.h",
25
- "ext/epoll.c",
26
- "ext/epoll.h",
27
25
  "ext/kqueue.c",
28
26
  "ext/kqueue.h",
29
27
  "tests/test_epoll.rb",
@@ -2,18 +2,18 @@ module Kernel
2
2
  class Epoll
3
3
  extend FFI::Library
4
4
 
5
- class Epoll_data < FFI::Struct
5
+ class Epoll_data < FFI::Struct #:nodoc:
6
6
  layout :ptr, :pointer,
7
7
  :fd, :int,
8
8
  :u32, :uint32,
9
9
  :u64, :uint64
10
10
  end
11
11
 
12
- class Epoll_event < FFI::Struct
12
+ class Epoll_event < FFI::Struct #:nodoc:
13
13
  layout :events, :uint32,
14
14
  :data, :pointer
15
15
 
16
- def [] key
16
+ def [] key #:nodoc:
17
17
  key == :data ? Epoll_data.new(super(key)) : super(key)
18
18
  end
19
19
  end
@@ -50,14 +50,14 @@ module Kernel
50
50
  :error => EPOLLERR
51
51
  }
52
52
 
53
+ # Missing in some kernel versions
53
54
  EP_FLAGS[:remote_hangup] = EPOLLRDHUP if const_defined?("EPOLLRDHUP")
54
55
 
55
56
  # Attach directly to epoll_create
56
57
  attach_function :epoll_create, [:int], :int
57
58
  # Attach directly to epoll_ctl
58
59
  attach_function :epoll_ctl, [:int, :int, :int, :pointer], :int
59
- # Attach to the epoll_wait wrapper so we can use rb_thread_blocking_region when possible
60
- attach_function :epoll_wait, :wrap_epoll_wait, [:int, :pointer, :int, :int], :int
60
+ attach_function :epoll_wait, [:int, :pointer, :int, :int], :int
61
61
 
62
62
  # Creates a new epoll event queue. Takes an optional size parameter (default 1024) that is a hint
63
63
  # to the kernel about how many descriptors it will be handling. Read man epoll_create for details
@@ -66,6 +66,7 @@ module Kernel
66
66
  @fds = {}
67
67
  @epfd = epoll_create(size)
68
68
  raise SystemCallError.new("Error creating epoll descriptor", get_errno) unless @epfd > 0
69
+ @epfd = IO.for_fd(@epfd)
69
70
  end
70
71
 
71
72
  # Generic method for adding events. This simply calls the proper add_foo method specified by the type symbol.
@@ -115,7 +116,7 @@ module Kernel
115
116
  # irb(main):006:0> w.write 'foo'
116
117
  # => 3
117
118
  # irb(main):007:0> ep.poll
118
- # => [{:target=>#<IO:0x89be38c>, :event=>:read, :type=>:socket}]
119
+ # => [{:target=>#<IO:0x89be38c>, :events=>[:read], :type=>:socket}]
119
120
  # irb(main):008:0> [r, w, ep].each{|x| x.close }
120
121
  def add_socket(target, options={})
121
122
  fdnum = target.respond_to?(:fileno) ? target.fileno : target
@@ -126,7 +127,7 @@ module Kernel
126
127
  ev[:data] = Epoll_data.new
127
128
  ev[:data][:fd] = fdnum
128
129
 
129
- if epoll_ctl(@epfd, EPOLL_CTL_ADD, fdnum, ev) == -1
130
+ if epoll_ctl(@epfd.fileno, EPOLL_CTL_ADD, fdnum, ev) == -1
130
131
  return false
131
132
  else
132
133
  @fds[fdnum] = {:target => target, :event => ev}
@@ -136,51 +137,49 @@ module Kernel
136
137
 
137
138
  # Poll for an event. Pass an optional timeout float as number of seconds to wait for an event. Default is 0.0 (do not wait).
138
139
  #
139
- # Using a timeout will block for the duration of the timeout. Under Ruby 1.9.1, we use rb_thread_blocking_region() under the
140
- # hood to allow other threads to run during this call. Prior to 1.9 though, we do not have native threads and hence this call
141
- # will block the whole interpreter (all threads) until it returns.
140
+ # Using a timeout will block the current thread for the duration of the timeout. We use select() on the epoll descriptor and
141
+ # then call epoll_wait() with 0 timeout, instead of blocking the whole interpreter with epoll_wait().
142
142
  #
143
143
  # This call returns an array of hashes, similar to the following:
144
- # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
144
+ # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :events=>[:read]}]
145
145
  #
146
146
  # * :type - will be the type of event target, i.e. an event set with #add_socket will have :type => :socket
147
147
  # * :target - the 'target' or 'subject' of the event. This can be a File, IO, process or signal number.
148
- # * :event - the event that occurred on the target. This is one of the symbols you passed as :events => [:foo] when adding the event.
148
+ # * :event - the event that occurred on the target. This is one or more of the symbols you passed as :events => [:foo] when adding the event.
149
149
  #
150
150
  # Note: even though epoll only supports :socket style descriptors, we keep :type for consistency with other APIs.
151
151
  def poll(timeout=0.0)
152
- timeout = (timeout * 1000).to_i
153
152
  ev = Epoll_event.new
154
- case epoll_wait(@epfd, ev, 1, timeout)
155
- when -1
156
- [errno]
157
- when 0
158
- []
153
+
154
+ r, w, e = IO.select([@epfd], nil, nil, timeout)
155
+
156
+ if r.nil? || r.empty?
157
+ return []
159
158
  else
160
- [process_event(ev)]
159
+ case epoll_wait(@epfd.fileno, ev, 1, 0)
160
+ when -1
161
+ [errno]
162
+ when 0
163
+ []
164
+ else
165
+ [process_event(ev)]
166
+ end
161
167
  end
162
168
  end
163
169
 
164
170
  def process_event(ev) #:nodoc:
165
171
  h = @fds[ev[:data][:fd]]
166
172
  return nil if h.nil?
167
-
168
- event = if ev[:events] & EPOLLIN == EPOLLIN
169
- :read
170
- elsif ev[:events] & EPOLLOUT == EPOLLOUT
171
- :write
172
- elsif ev[:events] & ERPOLLPRI == EPOLLPRI
173
- :priority
174
- elsif ev[:events] & EPOLLERR == EPOLLERR
175
- :error
176
- elsif ev[:events] & EPOLLHUP == EPOLLHUP
177
- :hangup
178
- elsif Epoll.const_defined?("EPOLLRDHUP") and ev[:events] & EPOLLRDHUP == EPOLLRDHUP
179
- :remote_hangup
180
- end
181
-
182
- delete(:socket, h[:target]) if ev[:events] & EPOLLONESHOT == EPOLLONESHOT
183
- {:target => h[:target], :event => event, :type => :socket}
173
+ events = []
174
+ events << :read if ev[:events] & EPOLLIN == EPOLLIN
175
+ events << :write if ev[:events] & EPOLLOUT == EPOLLOUT
176
+ events << :priority if ev[:events] & EPOLLPRI == EPOLLPRI
177
+ events << :error if ev[:events] & EPOLLERR == EPOLLERR
178
+ events << :hangup if ev[:events] & EPOLLHUP == EPOLLHUP
179
+ events << :remote_hangup if Epoll.const_defined?("EPOLLRDHUP") and ev[:events] & EPOLLRDHUP == EPOLLRDHUP
180
+ events << :oneshot if h[:event][:events] & EPOLLONESHOT == EPOLLONESHOT
181
+ delete(:socket, h[:target]) if events.include?(:oneshot) || events.include?(:hangup) || events.include?(:remote_hangup)
182
+ {:target => h[:target], :events => events, :type => :socket}
184
183
  end
185
184
 
186
185
  # Stop generating events for the given type and event target, ie:
@@ -191,12 +190,13 @@ module Kernel
191
190
  ident = target.respond_to?(:fileno) ? target.fileno : target
192
191
  h = @fds[ident]
193
192
  return false if h.nil?
194
- epoll_ctl(@epfd, EPOLL_CTL_DEL, ident, h[:event])
193
+ epoll_ctl(@epfd.fileno, EPOLL_CTL_DEL, ident, h[:event])
194
+ @fds.delete(ident)
195
195
  return true
196
196
  end
197
197
 
198
198
  def close
199
- IO.for_fd(@epfd).close
199
+ @epfd.close
200
200
  end
201
201
 
202
202
  end
@@ -3,7 +3,7 @@ module Kernel
3
3
  class Kqueue
4
4
  extend FFI::Library
5
5
 
6
- class Kevent < FFI::Struct
6
+ class Kevent < FFI::Struct #:nodoc:
7
7
  layout :ident, :uint,
8
8
  :filter, :short,
9
9
  :flags, :ushort,
@@ -12,11 +12,6 @@ module Kernel
12
12
  :udata, :pointer
13
13
  end
14
14
 
15
- class Timespec < FFI::Struct
16
- layout :tv_sec, :long,
17
- :tv_nsec, :long
18
- end
19
-
20
15
  kqc = FFI::ConstGenerator.new do |c|
21
16
  c.include 'sys/event.h'
22
17
 
@@ -110,8 +105,7 @@ module Kernel
110
105
  attach_function :ev_set, :wrap_evset, [:pointer, :uint, :short, :ushort, :uint, :int, :pointer], :void
111
106
  # Attach directly to kqueue function, no wrapper needed
112
107
  attach_function :kqueue, [], :int
113
- # We wrap kqueue and kevent because we use rb_thread_blocking_region when it's available in MRI
114
- attach_function :kevent, :wrap_kevent, [:int, :pointer, :int, :pointer, :int, Timespec], :int
108
+ attach_function :kevent, [:int, :pointer, :int, :pointer, :int, :pointer], :int
115
109
 
116
110
  # We provide the raw C interface above. Now we OO-ify it.
117
111
 
@@ -121,6 +115,7 @@ module Kernel
121
115
  @pids = {}
122
116
  @kqfd = kqueue
123
117
  raise SystemCallError.new("Error creating kqueue descriptor", get_errno) unless @kqfd > 0
118
+ @kqfd = IO.for_fd(@kqfd)
124
119
  end
125
120
 
126
121
  # Generic method for adding events. This simply calls the proper add_foo method specified by the type symbol.
@@ -172,7 +167,7 @@ module Kernel
172
167
  # irb(main):006:0> file.delete
173
168
  # => #<File:/tmp/kqueue-test20090417-602-evm5wc-0>
174
169
  # irb(main):007:0> kq.poll
175
- # => [{:type=>:file, :target=>#<File:/tmp/kqueue-test20090417-602-evm5wc-0>, :event=>:delete}]
170
+ # => [{:type=>:file, :target=>#<File:/tmp/kqueue-test20090417-602-evm5wc-0>, :events=>[:delete]}]
176
171
  # irb(main):008:0> file.close and kq.close
177
172
  # => nil
178
173
  def add_file(file, options={})
@@ -187,7 +182,7 @@ module Kernel
187
182
  fflags = fflags.inject(0){|m,i| m | KQ_FFLAGS[i] }
188
183
  ev_set(k, fdnum, EVFILT_VNODE, EV_ADD | flags, fflags, 0, nil)
189
184
 
190
- if kevent(@kqfd, k, 1, nil, 0, nil) == -1
185
+ if kevent(@kqfd.fileno, k, 1, nil, 0, nil) == -1
191
186
  return false
192
187
  else
193
188
  @fds[fdnum] = {:target => file, :event => k}
@@ -220,7 +215,7 @@ module Kernel
220
215
  # irb(main):006:0> w.write "foo"
221
216
  # => 3
222
217
  # irb(main):007:0> kq.poll
223
- # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
218
+ # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :events=>[:read]}]
224
219
  # irb(main):008:0> [r, w, kq].each {|i| i.close}
225
220
  def add_socket(target, options={})
226
221
  filters, flags = options.values_at :events, :flags
@@ -231,7 +226,7 @@ module Kernel
231
226
  k = Kevent.new
232
227
  ev_set(k, fdnum, filters, EV_ADD | flags, 0, 0, nil)
233
228
 
234
- if kevent(@kqfd, k, 1, nil, 0, nil) == -1
229
+ if kevent(@kqfd.fileno, k, 1, nil, 0, nil) == -1
235
230
  return false
236
231
  else
237
232
  @fds[fdnum] = {:target => target, :event => k}
@@ -260,7 +255,7 @@ module Kernel
260
255
  # irb(main):005:0> Process.kill('TERM', fpid)
261
256
  # => 1
262
257
  # irb(main):006:0> kq.poll.first
263
- # => {:event=>:exit, :type=>:process, :target=>616}
258
+ # => {:events=>[:exit], :type=>:process, :target=>616}
264
259
  #
265
260
  def add_process(pid, options={})
266
261
  flags, fflags = options.values_at :flags, :events
@@ -270,7 +265,7 @@ module Kernel
270
265
  k = Kevent.new
271
266
  ev_set(k, pid, EVFILT_PROC, EV_ADD | flags, fflags, 0, nil)
272
267
 
273
- if kevent(@kqfd, k, 1, nil, 0, nil) == -1
268
+ if kevent(@kqfd.fileno, k, 1, nil, 0, nil) == -1
274
269
  return false
275
270
  else
276
271
  @pids[pid] = {:target => pid, :event => k}
@@ -280,29 +275,31 @@ module Kernel
280
275
 
281
276
  # Poll for an event. Pass an optional timeout float as number of seconds to wait for an event. Default is 0.0 (do not wait).
282
277
  #
283
- # Using a timeout will block for the duration of the timeout. Under Ruby 1.9.1, we use rb_thread_blocking_region() under the
284
- # hood to allow other threads to run during this call. Prior to 1.9 though, we do not have native threads and hence this call
285
- # will block the whole interpreter (all threads) until it returns.
278
+ # Using a timeout will block the current thread for the duration of the timeout. We use select() on the kqueue descriptor and
279
+ # then call kevent() with 0 timeout, instead of blocking the whole interpreter with kevent().
286
280
  #
287
281
  # This call returns an array of hashes, similar to the following:
288
- # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
282
+ # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :events=>[:read]}]
289
283
  #
290
284
  # * :type - will be the type of event target, i.e. an event set with #add_file will have :type => :file
291
285
  # * :target - the 'target' or 'subject' of the event. This can be a File, IO, process or signal number.
292
- # * :event - the event that occurred on the target. This is one of the symbols you passed as :events => [:foo] when adding the event.
286
+ # * :event - the event that occurred on the target. This is one or more of the symbols you passed as :events => [:foo] when adding the event.
293
287
  def poll(timeout=0.0)
294
288
  k = Kevent.new
295
- t = Timespec.new
296
- t[:tv_sec] = timeout.to_i
297
- t[:tv_nsec] = ((timeout - timeout.to_i) * 1e9).to_i
298
-
299
- case kevent(@kqfd, nil, 0, k, 1, t)
300
- when -1
301
- [errno]
302
- when 0
303
- []
289
+
290
+ r, w, e = IO.select([@kqfd], nil, nil, timeout)
291
+
292
+ if r.nil? || r.empty?
293
+ return []
304
294
  else
305
- [process_event(k)]
295
+ case kevent(@kqfd.fileno, nil, 0, k, 1, nil)
296
+ when -1
297
+ [errno]
298
+ when 0
299
+ []
300
+ else
301
+ [process_event(k)]
302
+ end
306
303
  end
307
304
  end
308
305
 
@@ -311,47 +308,35 @@ module Kernel
311
308
  when EVFILT_VNODE
312
309
  h = @fds[k[:ident]]
313
310
  return nil if h.nil?
314
- event = if k[:fflags] & NOTE_DELETE == NOTE_DELETE
315
- :delete
316
- elsif k[:fflags] & NOTE_WRITE == NOTE_WRITE
317
- :write
318
- elsif k[:fflags] & NOTE_EXTEND == NOTE_EXTEND
319
- :extend
320
- elsif k[:fflags] & NOTE_ATTRIB == NOTE_ATTRIB
321
- :attrib
322
- elsif k[:fflags] & NOTE_LINK == NOTE_LINK
323
- :link
324
- elsif k[:fflags] & NOTE_RENAME == NOTE_RENAME
325
- :rename
326
- elsif k[:fflags] & NOTE_REVOKE == NOTE_REVOKE
327
- :revoke
328
- end
329
- delete(:file, k[:ident]) if event == :delete || event == :revoke
330
- {:target => h[:target], :type => :file, :event => event}
311
+ events = []
312
+ events << :delete if k[:fflags] & NOTE_DELETE == NOTE_DELETE
313
+ events << :write if k[:fflags] & NOTE_WRITE == NOTE_WRITE
314
+ events << :extend if k[:fflags] & NOTE_EXTEND == NOTE_EXTEND
315
+ events << :attrib if k[:fflags] & NOTE_ATTRIB == NOTE_ATTRIB
316
+ events << :link if k[:fflags] & NOTE_LINK == NOTE_LINK
317
+ events << :rename if k[:fflags] & NOTE_RENAME == NOTE_RENAME
318
+ events << :revoke if k[:fflags] & NOTE_REVOKE == NOTE_REVOKE
319
+ delete(:file, k[:ident]) if events.include?(:delete) || events.include?(:revoke)
320
+ {:target => h[:target], :type => :file, :events => events}
331
321
  when EVFILT_READ
332
322
  h = @fds[k[:ident]]
333
323
  return nil if h.nil?
334
- {:target => h[:target], :type => :socket, :event => :read}
324
+ {:target => h[:target], :type => :socket, :events => [:read]}
335
325
  when EVFILT_WRITE
336
326
  h = @fds[k[:ident]]
337
327
  return nil if h.nil?
338
- {:target => h[:target], :type => :socket, :event => :write}
328
+ {:target => h[:target], :type => :socket, :events => [:write]}
339
329
  when EVFILT_PROC
340
330
  h = @pids[k[:ident]]
341
331
  return nil if h.nil?
342
- event = if k[:fflags] & NOTE_EXIT == NOTE_EXIT
343
- :exit
344
- elsif k[:fflags] & NOTE_FORK == NOTE_FORK
345
- :fork
346
- elsif k[:fflags] & NOTE_EXEC == NOTE_EXEC
347
- :exec
348
- elsif Kqueue.const_defined?("NOTE_SIGNAL") and k[:fflags] & NOTE_SIGNAL == NOTE_SIGNAL
349
- :signal
350
- elsif Kqueue.const_defined?("NOTE_REAP") and k[:fflags] & NOTE_REAP == NOTE_REAP
351
- :reap
352
- end
353
- delete(:process, k[:ident]) if event == :exit
354
- {:target => h[:target], :type => :process, :event => event}
332
+ events = []
333
+ events << :exit if k[:fflags] & NOTE_EXIT == NOTE_EXIT
334
+ events << :fork if k[:fflags] & NOTE_FORK == NOTE_FORK
335
+ events << :exec if k[:fflags] & NOTE_EXEC == NOTE_EXEC
336
+ events << :signal if Kqueue.const_defined?("NOTE_SIGNAL") and k[:fflags] & NOTE_SIGNAL == NOTE_SIGNAL
337
+ events << :reap if Kqueue.const_defined?("NOTE_REAP") and k[:fflags] & NOTE_REAP == NOTE_REAP
338
+ delete(:process, k[:ident]) if events.include?(:exit)
339
+ {:target => h[:target], :type => :process, :events => events}
355
340
  end
356
341
 
357
342
  delete(res[:type], res[:target]) if k[:flags] & EV_ONESHOT == EV_ONESHOT
@@ -374,14 +359,14 @@ module Kernel
374
359
  return false if h.nil?
375
360
  k = h[:event]
376
361
  ev_set(k, k[:ident], k[:filter], EV_DELETE, k[:fflags], 0, nil)
377
- kevent(@kqfd, k, 1, nil, 0, nil)
362
+ kevent(@kqfd.fileno, k, 1, nil, 0, nil)
378
363
  container.delete(ident)
379
364
  return true
380
365
  end
381
366
 
382
367
  # Close the kqueue descriptor. This essentially shuts down your kqueue and renders all active events on this kqueue removed.
383
368
  def close
384
- IO.for_fd(@kqfd).close
369
+ @kqfd.close
385
370
  end
386
371
 
387
372
  end
@@ -44,7 +44,7 @@ describe "the epoll interface" do
44
44
  w.write 'foo'
45
45
  res = ep.poll.first
46
46
 
47
- res[:event].should.equal :read
47
+ res[:events].should.include :read
48
48
  res[:target].fileno.should.equal r.fileno
49
49
  res[:type].should.equal :socket
50
50
 
@@ -65,5 +65,24 @@ describe "the epoll interface" do
65
65
 
66
66
  [r,w,ep].each{|i| i.close}
67
67
  end
68
+
69
+ it "should provide aggregated events on a target" do
70
+ r, w = IO.pipe
71
+ ep = Epoll.new
72
+
73
+ ep.add(:socket, r, :events => [:read, :hangup]).should.be.true
74
+ ep.poll.should.be.empty
75
+
76
+ w.write 'foo'
77
+ w.close
78
+
79
+ res = ep.poll.first
80
+ res[:type].should.equal :socket
81
+ res[:target].fileno.should.equal r.fileno
82
+ res[:events].should.include :read
83
+ res[:events].should.include :hangup
84
+
85
+ [r, ep].each{|i| i.close}
86
+ end
68
87
 
69
88
  end
@@ -42,7 +42,7 @@ describe "the kqueue interface" do
42
42
  res[:target].class.should.equal Tempfile
43
43
  res[:target].fileno.should.equal file.fileno
44
44
  res[:type].should.equal :file
45
- res[:event].should.equal :write
45
+ res[:events].should.include :write
46
46
 
47
47
  kq.poll.should.be.empty
48
48
  file.delete
@@ -51,7 +51,7 @@ describe "the kqueue interface" do
51
51
  res2[:target].class.should.equal Tempfile
52
52
  res2[:target].fileno.should.equal file.fileno
53
53
  res2[:type].should.equal :file
54
- res2[:event].should.equal :delete
54
+ res2[:events].should.include :delete
55
55
 
56
56
  file.close
57
57
  kq.close
@@ -69,7 +69,7 @@ describe "the kqueue interface" do
69
69
  res[:target].class.should.equal IO
70
70
  res[:target].fileno.should.equal r.fileno
71
71
  res[:type].should.equal :socket
72
- res[:event].should.equal :read
72
+ res[:events].should.include :read
73
73
 
74
74
  kq.poll.should.be.empty
75
75
  kq.delete(:socket, r).should.be.true
@@ -89,7 +89,7 @@ describe "the kqueue interface" do
89
89
  res = kq.poll(1).first
90
90
  res[:target].should.equal Process.pid
91
91
  res[:type].should.equal :process
92
- res[:event].should.equal :fork
92
+ res[:events].should.include :fork
93
93
 
94
94
  # Watch for the child to exit and kill it
95
95
  kq.add(:process, fpid, :events => [:exit])
@@ -99,11 +99,32 @@ describe "the kqueue interface" do
99
99
  res2 = kq.poll(1).first
100
100
  res2[:target].should.equal fpid
101
101
  res2[:type].should.equal :process
102
- res2[:event].should.equal :exit
102
+ res2[:events].should.include :exit
103
103
 
104
104
  kq.poll.should.be.empty
105
105
 
106
106
  kq.close
107
107
  end
108
108
 
109
+ it "should provide aggregated events on a single target" do
110
+ file = Tempfile.new("kqueue-test")
111
+ kq = Kqueue.new
112
+ kq.add(:file, file, :events => [:write, :delete]).should.be.true
113
+
114
+ kq.poll.should.be.empty
115
+ File.open(file.path, 'w'){|x| x.puts 'foo'}
116
+ file.delete
117
+
118
+ res = kq.poll.first
119
+ res.class.should.equal Hash
120
+ res[:target].class.should.equal Tempfile
121
+ res[:target].fileno.should.equal file.fileno
122
+ res[:type].should.equal :file
123
+ res[:events].should.include :write
124
+ res[:events].should.include :delete
125
+
126
+ file.close
127
+ kq.close
128
+ end
129
+
109
130
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ktools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Douglas
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-22 00:00:00 -07:00
12
+ date: 2009-04-26 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -42,7 +42,7 @@ extra_rdoc_files: []
42
42
 
43
43
  files:
44
44
  - ktools.gemspec
45
- - README
45
+ - README.rdoc
46
46
  - Rakefile
47
47
  - lib/ktools.rb
48
48
  - lib/ktools/ktools.rb
@@ -50,8 +50,6 @@ files:
50
50
  - lib/ktools/kqueue.rb
51
51
  - ext/ktools.c
52
52
  - ext/ktools.h
53
- - ext/epoll.c
54
- - ext/epoll.h
55
53
  - ext/kqueue.c
56
54
  - ext/kqueue.h
57
55
  - tests/test_epoll.rb
data/README DELETED
@@ -1,50 +0,0 @@
1
- Bringing common kernel APIs into Ruby using FFI.
2
- http://github.com/yakischloba/ktools
3
-
4
- Synopsis:
5
-
6
- # irb(main):001:0> require 'ktools'
7
- # => true
8
- # irb(main):002:0> r, w = IO.pipe
9
- # => [#<IO:0x4fa90c>, #<IO:0x4fa880>]
10
- # irb(main):003:0> kq = Kqueue.new
11
- # => #<Kernel::Kqueue:0x4f43a4 @kqfd=6, @fds={}>
12
- # irb(main):004:0> kq.add(:socket, r, :events => [:read, :write])
13
- # => true
14
- # irb(main):005:0> kq.poll
15
- # => []
16
- # irb(main):006:0> w.write "foo"
17
- # => 3
18
- # irb(main):007:0> kq.poll
19
- # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
20
- # irb(main):008:0> [r, w, kq].each {|i| i.close}
21
-
22
-
23
- I plan to support the following kernel APIs:
24
-
25
- kqueue (works - see tests/test_kqueue.rb)
26
- epoll (works - see tests/test_epoll.rb)
27
- inotify
28
- netlink
29
-
30
- and maybe some others! I will at first hook up the C interfaces as directly
31
- as possible, and then write Rubyist-friendly wrappers around them.
32
-
33
- Currently kqueue and epoll have Ruby wrapper APIs.
34
-
35
-
36
- To install:
37
-
38
- git clone git://github.com/yakischloba/ktools.git
39
- cd ktools
40
- gem build
41
- sudo gem install ktools-<version>.gem
42
-
43
- Also gems are on Rubyforge, so you can simply 'sudo gem install ktools', but commits will
44
- be frequent for some time, so you'll probably want to be pulling the latest from Github.
45
-
46
- Please file all issues on the Github issue tracker. Patches (with tests) are welcome and
47
- encouraged, as are suggestions about API design, etc. It's all up in the air right now.
48
-
49
- yakischloba on freenode
50
- jakecdouglas at gmail
@@ -1,32 +0,0 @@
1
- #ifdef HAVE_EPOLL
2
-
3
- #include "epoll.h"
4
-
5
- #ifdef HAVE_TBR
6
- #include <ruby.h>
7
- #endif
8
-
9
- int wrap_epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout)
10
- {
11
- #ifdef HAVE_TBR
12
- struct wrapped_epoll_event epevent;
13
- epevent.epfd = epfd;
14
- epevent.event = event;
15
- epevent.maxevents = maxevents;
16
- epevent.timeout = timeout;
17
- epevent.result = -1;
18
- rb_thread_blocking_region((rb_blocking_function_t *) tbr_epoll_wait, &epevent, RUBY_UBF_IO, 0);
19
- return epevent.result;
20
- #else
21
- return epoll_wait(epfd, event, maxevents, timeout);
22
- #endif
23
- }
24
-
25
- #ifdef HAVE_TBR
26
- void tbr_epoll_wait(struct wrapped_epoll_event *event)
27
- {
28
- event->result = epoll_wait(event->epfd, event->event, event->maxevents, event->timeout);
29
- }
30
- #endif
31
-
32
- #endif
@@ -1,17 +0,0 @@
1
- #ifdef HAVE_EPOLL
2
-
3
- #include <sys/epoll.h>
4
-
5
- #ifdef HAVE_TBR
6
- struct wrapped_epoll_event {
7
- int epfd;
8
- struct epoll_event *event;
9
- int maxevents;
10
- int timeout;
11
- int result;
12
- };
13
-
14
- void tbr_epoll_wait(struct wrapped_epoll_event*);
15
- #endif
16
-
17
- #endif