ktools 0.0.2 → 0.0.3

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.
@@ -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