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