ktools 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +62 -0
- data/Rakefile +0 -8
- data/ext/kqueue.c +0 -29
- data/ext/kqueue.h +0 -14
- data/ext/ktools.h +1 -1
- data/ktools.gemspec +3 -5
- data/lib/ktools/epoll.rb +38 -38
- data/lib/ktools/kqueue.rb +48 -63
- data/tests/test_epoll.rb +20 -1
- data/tests/test_kqueue.rb +26 -5
- metadata +3 -5
- data/README +0 -50
- data/ext/epoll.c +0 -32
- data/ext/epoll.h +0 -17
data/README.rdoc
ADDED
@@ -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
|
data/ext/kqueue.c
CHANGED
@@ -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
|
data/ext/kqueue.h
CHANGED
@@ -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
|
data/ext/ktools.h
CHANGED
data/ktools.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "ktools"
|
3
|
-
s.version = "0.0.
|
4
|
-
s.date = "2009-04-
|
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",
|
data/lib/ktools/epoll.rb
CHANGED
@@ -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
|
-
|
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>, :
|
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.
|
140
|
-
#
|
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>, :
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
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
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
199
|
+
@epfd.close
|
200
200
|
end
|
201
201
|
|
202
202
|
end
|
data/lib/ktools/kqueue.rb
CHANGED
@@ -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
|
-
|
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>, :
|
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>, :
|
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
|
-
# => {:
|
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.
|
284
|
-
#
|
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>, :
|
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
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
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
|
-
|
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
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
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, :
|
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, :
|
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
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
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
|
-
|
369
|
+
@kqfd.close
|
385
370
|
end
|
386
371
|
|
387
372
|
end
|
data/tests/test_epoll.rb
CHANGED
@@ -44,7 +44,7 @@ describe "the epoll interface" do
|
|
44
44
|
w.write 'foo'
|
45
45
|
res = ep.poll.first
|
46
46
|
|
47
|
-
res[:
|
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
|
data/tests/test_kqueue.rb
CHANGED
@@ -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[:
|
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[:
|
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[:
|
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[:
|
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[:
|
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.
|
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-
|
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
|
data/ext/epoll.c
DELETED
@@ -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
|
data/ext/epoll.h
DELETED
@@ -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
|