ktools 0.0.1
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 +49 -0
- data/Rakefile +75 -0
- data/ext/epoll.c +32 -0
- data/ext/epoll.h +17 -0
- data/ext/kqueue.c +39 -0
- data/ext/kqueue.h +20 -0
- data/ext/ktools.c +43 -0
- data/ext/ktools.h +20 -0
- data/ktools.gemspec +30 -0
- data/lib/ktools.rb +6 -0
- data/lib/ktools/epoll.rb +45 -0
- data/lib/ktools/kqueue.rb +378 -0
- data/lib/ktools/ktools.rb +18 -0
- data/tests/test_epoll.rb +42 -0
- data/tests/test_kqueue.rb +112 -0
- metadata +76 -0
data/README
ADDED
@@ -0,0 +1,49 @@
|
|
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. Needs Ruby wrapper.)
|
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. Kqueue
|
32
|
+
currently has a (I think) decent wrapper and epoll is up next.
|
33
|
+
|
34
|
+
|
35
|
+
To install:
|
36
|
+
|
37
|
+
git clone git://github.com/yakischloba/ktools.git
|
38
|
+
cd ktools
|
39
|
+
gem build
|
40
|
+
sudo gem install ktools-<version>.gem
|
41
|
+
|
42
|
+
Also gems are on Rubyforge, so you can simply 'sudo gem install ktools', but commits will
|
43
|
+
be frequent for some time, so you'll probably want to be pulling the latest from Github.
|
44
|
+
|
45
|
+
Please file all issues on the Github issue tracker. Patches (with tests) are welcome and
|
46
|
+
encouraged, as are suggestions about API design, etc. It's all up in the air right now.
|
47
|
+
|
48
|
+
yakischloba on freenode
|
49
|
+
jakecdouglas at gmail
|
data/Rakefile
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'rake' unless defined?(Rake)
|
2
|
+
require 'mkmf'
|
3
|
+
include Config
|
4
|
+
|
5
|
+
task :default => [:build, :test]
|
6
|
+
task :build => [:clean, :config, :objs, :shared]
|
7
|
+
|
8
|
+
def add_define(name)
|
9
|
+
$ktools_defines.push("-D#{name}")
|
10
|
+
end
|
11
|
+
|
12
|
+
task :config do
|
13
|
+
$ktools_defines = []
|
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
|
+
add_define "HAVE_KQUEUE" if have_header("sys/event.h") and have_header("sys/queue.h")
|
17
|
+
#add_define "HAVE_INOTIFY" if inotify = have_func('inotify_init', 'sys/inotify.h')
|
18
|
+
#add_define "HAVE_OLD_INOTIFY" if !inotify && have_macro('__NR_inotify_init', 'sys/syscall.h')
|
19
|
+
|
20
|
+
if have_header('sys/epoll.h')
|
21
|
+
File.open("hasEpollTest.c", "w") {|f|
|
22
|
+
f.puts "#include <sys/epoll.h>"
|
23
|
+
f.puts "int main() { epoll_create(1024); return 0;}"
|
24
|
+
}
|
25
|
+
(e = system( "gcc hasEpollTest.c -o hasEpollTest " )) and (e = $?.to_i)
|
26
|
+
`rm -f hasEpollTest.c hasEpollTest`
|
27
|
+
add_define 'HAVE_EPOLL' if e == 0
|
28
|
+
end
|
29
|
+
|
30
|
+
$ktools_cc = `which #{RbConfig::expand(CONFIG["CC"])}`.chomp
|
31
|
+
$ktools_cflags = RbConfig::expand(CONFIG['CFLAGS']).split(" ")
|
32
|
+
$ktools_cflags.delete("$(cflags)")
|
33
|
+
$ktools_cflags = $ktools_cflags.join(" ")
|
34
|
+
$ktools_srcs = ["ktools.c"]
|
35
|
+
$ktools_srcs << "kqueue.c" if $ktools_defines.include?("-DHAVE_KQUEUE")
|
36
|
+
$ktools_srcs << "inotify.c" if $ktools_defines.include?("-DHAVE_INOTIFY")
|
37
|
+
$ktools_srcs << "epoll.c" if $ktools_defines.include?("-DHAVE_EPOLL")
|
38
|
+
$ktools_srcs << "netlink.c" if $ktools_defines.include?("-DHAVE_NETLINK")
|
39
|
+
|
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
|
+
$ktools_ldshared = RbConfig::expand(CONFIG['LDSHARED'])
|
46
|
+
$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
|
+
end
|
49
|
+
|
50
|
+
task :clean do
|
51
|
+
chdir "ext" do
|
52
|
+
sh "rm -f *.o *.bundle *.so"
|
53
|
+
end
|
54
|
+
chdir "lib" do
|
55
|
+
sh "rm -f *.o *.bundle *.so"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
task :test do
|
60
|
+
require 'lib/ktools'
|
61
|
+
sh "bacon tests/test_kqueue.rb" if Kernel.have_kqueue?
|
62
|
+
sh "bacon tests/test_epoll.rb" if Kernel.have_epoll?
|
63
|
+
end
|
64
|
+
|
65
|
+
task :objs do
|
66
|
+
chdir "ext" do
|
67
|
+
$ktools_srcs.each {|c| sh "#{$ktools_cc} #{$ktools_cflags} #{$ktools_defines.join(' ')} #{$ktools_includes} -c #{c}"}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
task :shared do
|
72
|
+
chdir "ext" do
|
73
|
+
sh "#{$ktools_ldshared}"
|
74
|
+
end
|
75
|
+
end
|
data/ext/epoll.c
ADDED
@@ -0,0 +1,32 @@
|
|
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
ADDED
@@ -0,0 +1,17 @@
|
|
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
|
data/ext/kqueue.c
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#ifdef HAVE_KQUEUE
|
2
|
+
|
3
|
+
#include "kqueue.h"
|
4
|
+
|
5
|
+
#ifdef HAVE_TBR
|
6
|
+
#include <ruby.h>
|
7
|
+
#endif
|
8
|
+
|
9
|
+
void wrap_evset(struct kevent *kev, unsigned int ident, short filter, unsigned short flags, unsigned int fflags, int data, void *udata)
|
10
|
+
{
|
11
|
+
EV_SET(kev, ident, filter, flags, fflags, data, udata);
|
12
|
+
}
|
13
|
+
|
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
|
+
#endif
|
data/ext/kqueue.h
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#ifdef HAVE_KQUEUE
|
2
|
+
|
3
|
+
#include <sys/event.h>
|
4
|
+
#include <sys/queue.h>
|
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
|
+
#endif
|
data/ext/ktools.c
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#include "ktools.h"
|
2
|
+
#include <errno.h>
|
3
|
+
|
4
|
+
int get_errno()
|
5
|
+
{
|
6
|
+
return errno;
|
7
|
+
}
|
8
|
+
|
9
|
+
int have_kqueue()
|
10
|
+
{
|
11
|
+
#ifdef HAVE_KQUEUE
|
12
|
+
return 1;
|
13
|
+
#else
|
14
|
+
return 0;
|
15
|
+
#endif
|
16
|
+
}
|
17
|
+
|
18
|
+
int have_epoll()
|
19
|
+
{
|
20
|
+
#ifdef HAVE_EPOLL
|
21
|
+
return 1;
|
22
|
+
#else
|
23
|
+
return 0;
|
24
|
+
#endif
|
25
|
+
}
|
26
|
+
|
27
|
+
int have_inotify()
|
28
|
+
{
|
29
|
+
#ifdef HAVE_INOTIFY
|
30
|
+
return 1;
|
31
|
+
#else
|
32
|
+
return 0;
|
33
|
+
#endif
|
34
|
+
}
|
35
|
+
|
36
|
+
int have_netlink()
|
37
|
+
{
|
38
|
+
#ifdef HAVE_NETLINK
|
39
|
+
return 1;
|
40
|
+
#else
|
41
|
+
return 0;
|
42
|
+
#endif
|
43
|
+
}
|
data/ext/ktools.h
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#ifndef KTOOLS_H
|
2
|
+
#define KTOOLS_H
|
3
|
+
|
4
|
+
#ifdef HAVE_EPOLL
|
5
|
+
#include "epoll.h"
|
6
|
+
#endif
|
7
|
+
|
8
|
+
#ifdef HAVE_KQUEUE
|
9
|
+
#include "kqueue.h"
|
10
|
+
#endif
|
11
|
+
|
12
|
+
/*#ifdef HAVE_NETLINK
|
13
|
+
#include "netlink.h"
|
14
|
+
#endif
|
15
|
+
|
16
|
+
#ifdef HAVE_INOTIFY
|
17
|
+
#include "inotify.h"
|
18
|
+
#endif*/
|
19
|
+
|
20
|
+
#endif
|
data/ktools.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "ktools"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.date = "2009-04-21"
|
5
|
+
s.authors = ["Jake Douglas"]
|
6
|
+
s.email = "jakecdouglas@gmail.com"
|
7
|
+
s.rubyforge_project = "ktools"
|
8
|
+
s.has_rdoc = true
|
9
|
+
s.add_dependency('ffi')
|
10
|
+
s.summary = "Bringing common kernel APIs into Ruby using FFI"
|
11
|
+
s.homepage = "http://www.github.com/yakischloba/ktools"
|
12
|
+
s.description = "Bringing common kernel APIs into Ruby using FFI"
|
13
|
+
s.extensions = ["Rakefile"]
|
14
|
+
s.files =
|
15
|
+
["ktools.gemspec",
|
16
|
+
"README",
|
17
|
+
"Rakefile",
|
18
|
+
"lib/ktools.rb",
|
19
|
+
"lib/ktools/ktools.rb",
|
20
|
+
"lib/ktools/epoll.rb",
|
21
|
+
"lib/ktools/kqueue.rb",
|
22
|
+
"ext/ktools.c",
|
23
|
+
"ext/ktools.h",
|
24
|
+
"ext/epoll.c",
|
25
|
+
"ext/epoll.h",
|
26
|
+
"ext/kqueue.c",
|
27
|
+
"ext/kqueue.h",
|
28
|
+
"tests/test_epoll.rb",
|
29
|
+
"tests/test_kqueue.rb"]
|
30
|
+
end
|
data/lib/ktools.rb
ADDED
data/lib/ktools/epoll.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Kernel
|
2
|
+
module Epoll
|
3
|
+
extend FFI::Library
|
4
|
+
|
5
|
+
class Epoll_data < FFI::Struct
|
6
|
+
layout :ptr, :pointer,
|
7
|
+
:fd, :int,
|
8
|
+
:u32, :uint32,
|
9
|
+
:u64, :uint64
|
10
|
+
end
|
11
|
+
|
12
|
+
class Epoll_event < FFI::Struct
|
13
|
+
layout :events, :uint32,
|
14
|
+
:data, Epoll_data
|
15
|
+
end
|
16
|
+
|
17
|
+
epc = FFI::ConstGenerator.new do |c|
|
18
|
+
c.include('sys/epoll.h')
|
19
|
+
c.const("EPOLLIN")
|
20
|
+
c.const("EPOLLPRI")
|
21
|
+
c.const("EPOLLOUT")
|
22
|
+
c.const("EPOLLRDNORM")
|
23
|
+
c.const("EPOLLRDBAND")
|
24
|
+
c.const("EPOLLWRNORM")
|
25
|
+
c.const("EPOLLWRBAND")
|
26
|
+
c.const("EPOLLMSG")
|
27
|
+
c.const("EPOLLERR")
|
28
|
+
c.const("EPOLLHUP")
|
29
|
+
c.const("EPOLLONESHOT")
|
30
|
+
c.const("EPOLLET")
|
31
|
+
c.const("EPOLL_CTL_ADD")
|
32
|
+
c.const("EPOLL_CTL_DEL")
|
33
|
+
c.const("EPOLL_CTL_MOD")
|
34
|
+
end
|
35
|
+
|
36
|
+
eval epc.to_ruby
|
37
|
+
|
38
|
+
# Attach directly to epoll_create
|
39
|
+
attach_function :epoll_create, [:int], :int
|
40
|
+
# Attach directly to epoll_ctl
|
41
|
+
attach_function :epoll_ctl, [:int, :int, :int, :pointer], :int
|
42
|
+
# Attach to the epoll_wait wrapper so we can use rb_thread_blocking_region when possible
|
43
|
+
attach_function :epoll_wait, :wrap_epoll_wait, [:int, :pointer, :int, :int], :int
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,378 @@
|
|
1
|
+
module Kernel
|
2
|
+
|
3
|
+
class Kqueue
|
4
|
+
extend FFI::Library
|
5
|
+
|
6
|
+
class Kevent < FFI::Struct
|
7
|
+
layout :ident, :uint,
|
8
|
+
:filter, :short,
|
9
|
+
:flags, :ushort,
|
10
|
+
:fflags, :uint,
|
11
|
+
:data, :int,
|
12
|
+
:udata, :pointer
|
13
|
+
end
|
14
|
+
|
15
|
+
class Timespec < FFI::Struct
|
16
|
+
layout :tv_sec, :long,
|
17
|
+
:tv_nsec, :long
|
18
|
+
end
|
19
|
+
|
20
|
+
kqc = FFI::ConstGenerator.new do |c|
|
21
|
+
c.include 'sys/event.h'
|
22
|
+
|
23
|
+
# filters - signed short
|
24
|
+
c.const("EVFILT_READ", "%d")
|
25
|
+
c.const("EVFILT_WRITE", "%d")
|
26
|
+
c.const("EVFILT_AIO", "%d")
|
27
|
+
c.const("EVFILT_VNODE", "%d")
|
28
|
+
c.const("EVFILT_PROC", "%d")
|
29
|
+
c.const("EVFILT_SIGNAL", "%d")
|
30
|
+
|
31
|
+
# flags - unsigned short
|
32
|
+
c.const("EV_ADD", "%u")
|
33
|
+
c.const("EV_DELETE", "%u")
|
34
|
+
c.const("EV_ENABLE", "%u")
|
35
|
+
c.const("EV_DISABLE", "%u")
|
36
|
+
c.const("EV_RECEIPT", "%u")
|
37
|
+
c.const("EV_ONESHOT", "%u")
|
38
|
+
c.const("EV_CLEAR", "%u")
|
39
|
+
c.const("EV_SYSFLAGS", "%u")
|
40
|
+
c.const("EV_FLAG0", "%u")
|
41
|
+
c.const("EV_FLAG1", "%u")
|
42
|
+
c.const("EV_EOF", "%u")
|
43
|
+
c.const("EV_ERROR", "%u")
|
44
|
+
c.const("EV_POLL", "%u")
|
45
|
+
c.const("EV_OOBAND", "%u")
|
46
|
+
|
47
|
+
# fflags - unsigned int
|
48
|
+
c.const("NOTE_LOWAT", "%u")
|
49
|
+
c.const("NOTE_DELETE", "%u")
|
50
|
+
c.const("NOTE_WRITE", "%u")
|
51
|
+
c.const("NOTE_EXTEND", "%u")
|
52
|
+
c.const("NOTE_ATTRIB", "%u")
|
53
|
+
c.const("NOTE_LINK", "%u")
|
54
|
+
c.const("NOTE_RENAME", "%u")
|
55
|
+
c.const("NOTE_REVOKE", "%u")
|
56
|
+
c.const("NOTE_EXIT", "%u")
|
57
|
+
c.const("NOTE_FORK", "%u")
|
58
|
+
c.const("NOTE_EXEC", "%u")
|
59
|
+
c.const("NOTE_REAP", "%u")
|
60
|
+
c.const("NOTE_SIGNAL", "%u")
|
61
|
+
c.const("NOTE_PDATAMASK", "%u")
|
62
|
+
c.const("NOTE_PCTRLMASK", "%u")
|
63
|
+
c.const("NOTE_SECONDS", "%u")
|
64
|
+
c.const("NOTE_USECONDS", "%u")
|
65
|
+
c.const("NOTE_NSECONDS", "%u")
|
66
|
+
c.const("NOTE_ABSOLUTE", "%u")
|
67
|
+
c.const("NOTE_TRACK", "%u")
|
68
|
+
c.const("NOTE_TRACKERR", "%u")
|
69
|
+
c.const("NOTE_CHILD", "%u")
|
70
|
+
end
|
71
|
+
|
72
|
+
eval kqc.to_ruby
|
73
|
+
|
74
|
+
KQ_FILTERS = {
|
75
|
+
:read => EVFILT_READ,
|
76
|
+
:write => EVFILT_WRITE,
|
77
|
+
:file => EVFILT_VNODE,
|
78
|
+
:process => EVFILT_PROC,
|
79
|
+
:signal => EVFILT_SIGNAL
|
80
|
+
}
|
81
|
+
|
82
|
+
KQ_FLAGS = {
|
83
|
+
:add => EV_ADD,
|
84
|
+
:enable => EV_ENABLE,
|
85
|
+
:disable => EV_DISABLE,
|
86
|
+
:delete => EV_DELETE,
|
87
|
+
:oneshot => EV_ONESHOT,
|
88
|
+
:clear => EV_CLEAR
|
89
|
+
}
|
90
|
+
|
91
|
+
KQ_FFLAGS = {
|
92
|
+
:delete => NOTE_DELETE,
|
93
|
+
:write => NOTE_WRITE,
|
94
|
+
:extend => NOTE_EXTEND,
|
95
|
+
:attrib => NOTE_ATTRIB,
|
96
|
+
:link => NOTE_LINK,
|
97
|
+
:rename => NOTE_RENAME,
|
98
|
+
:revoke => NOTE_REVOKE,
|
99
|
+
:exit => NOTE_EXIT,
|
100
|
+
:fork => NOTE_FORK,
|
101
|
+
:exec => NOTE_EXEC
|
102
|
+
}
|
103
|
+
|
104
|
+
# Leopard has these, Tiger does not.
|
105
|
+
KQ_FLAGS[:receipt] = EV_RECEIPT if const_defined?("EV_RECEIPT")
|
106
|
+
KQ_FFLAGS[:signal] = NOTE_SIGNAL if const_defined?("NOTE_SIGNAL")
|
107
|
+
KQ_FFLAGS[:reap] = NOTE_REAP if const_defined?("NOTE_REAP")
|
108
|
+
|
109
|
+
# Had to write a wrapper for EV_SET since its a macro
|
110
|
+
attach_function :ev_set, :wrap_evset, [:pointer, :uint, :short, :ushort, :uint, :int, :pointer], :void
|
111
|
+
# Attach directly to kqueue function, no wrapper needed
|
112
|
+
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
|
115
|
+
|
116
|
+
# We provide the raw C interface above. Now we OO-ify it.
|
117
|
+
|
118
|
+
# Creates a new kqueue object. Will raise an error if the operation fails.
|
119
|
+
def initialize
|
120
|
+
@fds = {}
|
121
|
+
@pids = {}
|
122
|
+
@kqfd = kqueue
|
123
|
+
raise SystemCallError.new("Error creating kqueue descriptor", get_errno) unless @kqfd > 0
|
124
|
+
end
|
125
|
+
|
126
|
+
# Generic method for adding events. This simply calls the proper add_foo method specified by the type symbol.
|
127
|
+
# Example:
|
128
|
+
# kq.add(:process, pid, :events => [:fork])
|
129
|
+
# calls -> kq.add_process(pid, events => [:fork])
|
130
|
+
def add(type, target, options={})
|
131
|
+
case type
|
132
|
+
when :socket
|
133
|
+
add_socket(target, options)
|
134
|
+
when :file
|
135
|
+
add_file(target, options)
|
136
|
+
when :process
|
137
|
+
add_process(target, options)
|
138
|
+
when :signal
|
139
|
+
add_signal(target, options)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Add events on a file to the Kqueue. kqueue requires that a file actually be opened (to get a descriptor)
|
144
|
+
# before it can be monitored. You can pass a File object here, or a String of the pathname, in which
|
145
|
+
# case we'll try to open the file for you. In either case, a File object will be returned as the :target
|
146
|
+
# in the event returned by #poll. Valid events here are as follows, using descriptions from the kqueue man pages:
|
147
|
+
#
|
148
|
+
# * :delete - "The unlink() system call was called on the file referenced by the descriptor."
|
149
|
+
# * :write - "A write occurred on the file referenced by the descriptor."
|
150
|
+
# * :extend - "The file referenced by the descriptor was extended."
|
151
|
+
# * :attrib - "The file referenced by the descriptor had its attributes changed."
|
152
|
+
# * :link - "The link count on the file changed."
|
153
|
+
# * :rename - "The file referenced by the descriptor was renamed."
|
154
|
+
# * :revoke - "Access to the file was revoked via revoke(2) or the underlying fileystem was unmounted."
|
155
|
+
#
|
156
|
+
# Example:
|
157
|
+
# irb(main):001:0> require 'ktools'
|
158
|
+
# => true
|
159
|
+
# irb(main):002:0> file = Tempfile.new("kqueue-test")
|
160
|
+
# => #<File:/tmp/kqueue-test20090417-602-evm5wc-0>
|
161
|
+
# irb(main):003:0> kq = Kqueue.new
|
162
|
+
# => #<Kernel::Kqueue:0x4f0aec @kqfd=5, @fds={}>
|
163
|
+
# irb(main):004:0> kq.add(:file, file, :events => [:write, :delete])
|
164
|
+
# => true
|
165
|
+
# irb(main):005:0> kq.poll
|
166
|
+
# => []
|
167
|
+
# irb(main):006:0> file.delete
|
168
|
+
# => #<File:/tmp/kqueue-test20090417-602-evm5wc-0>
|
169
|
+
# irb(main):007:0> kq.poll
|
170
|
+
# => [{:type=>:file, :target=>#<File:/tmp/kqueue-test20090417-602-evm5wc-0>, :event=>:delete}]
|
171
|
+
# irb(main):008:0> file.close and kq.close
|
172
|
+
# => nil
|
173
|
+
def add_file(file, options={})
|
174
|
+
fflags, flags = options.values_at :events, :flags
|
175
|
+
raise ArgumentError.new("must specify which file events to watch for") unless fflags
|
176
|
+
|
177
|
+
file = file.kind_of?(File) || file.kind_of?(Tempfile) ? file : File.open(file, 'r')
|
178
|
+
|
179
|
+
k = Kevent.new
|
180
|
+
flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
|
181
|
+
fflags = fflags.inject(0){|m,i| m | KQ_FFLAGS[i] }
|
182
|
+
ev_set(k, file.fileno, EVFILT_VNODE, EV_ADD | flags, fflags, 0, nil)
|
183
|
+
|
184
|
+
if kevent(@kqfd, k, 1, nil, 0, nil) == -1
|
185
|
+
return false
|
186
|
+
else
|
187
|
+
@fds[file.fileno] = {:target => file, :kevent => k}
|
188
|
+
return true
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Add events to a socket-style descriptor (socket or pipe). Supported events are:
|
193
|
+
#
|
194
|
+
# * :read - The descriptor has become readable.
|
195
|
+
# * :write - The descriptor has become writeable.
|
196
|
+
#
|
197
|
+
# See the kqueue manpage for how behavior differs depending on the descriptor types.
|
198
|
+
# In general, you shouldn't have to worry about it.
|
199
|
+
#
|
200
|
+
# Example:
|
201
|
+
# irb(main):001:0> require 'ktools'
|
202
|
+
# => true
|
203
|
+
# irb(main):002:0> r, w = IO.pipe
|
204
|
+
# => [#<IO:0x4fa90c>, #<IO:0x4fa880>]
|
205
|
+
# irb(main):003:0> kq = Kqueue.new
|
206
|
+
# => #<Kernel::Kqueue:0x4f43a4 @kqfd=6, @fds={}>
|
207
|
+
# irb(main):004:0> kq.add(:socket, r, :events => [:read, :write])
|
208
|
+
# => true
|
209
|
+
# irb(main):005:0> kq.poll
|
210
|
+
# => []
|
211
|
+
# irb(main):006:0> w.write "foo"
|
212
|
+
# => 3
|
213
|
+
# irb(main):007:0> kq.poll
|
214
|
+
# => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
|
215
|
+
# irb(main):008:0> [r, w, kq].each {|i| i.close}
|
216
|
+
def add_socket(io, options={})
|
217
|
+
filters, flags = options.values_at :events, :flags
|
218
|
+
flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
|
219
|
+
filters = filters ? filters.inject(0){|m,i| m | KQ_FILTERS[i] } : EVFILT_READ | EVFILT_WRITE
|
220
|
+
|
221
|
+
k = Kevent.new
|
222
|
+
ev_set(k, io.fileno, filters, EV_ADD | flags, 0, 0, nil)
|
223
|
+
|
224
|
+
if kevent(@kqfd, k, 1, nil, 0, nil) == -1
|
225
|
+
return false
|
226
|
+
else
|
227
|
+
@fds[io.fileno] = {:target => io, :kevent => k}
|
228
|
+
return true
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Add events for a process. Takes a process id and and options hash. Supported events are:
|
233
|
+
# * :exit - The process has exited
|
234
|
+
# * :fork - The process has created a child process via fork(2) or similar call.
|
235
|
+
# * :exec - The process executed a new process via execve(2) or similar call.
|
236
|
+
# * :signal - The process was sent a signal. Status can be checked via waitpid(2) or similar call.
|
237
|
+
# * :reap - The process was reaped by the parent via wait(2) or similar call.\
|
238
|
+
#
|
239
|
+
# Note: SIGNAL and REAP do not appear to exist in OSX older than Leopard.
|
240
|
+
#
|
241
|
+
# Example:
|
242
|
+
# irb(main):001:0> require 'ktools'
|
243
|
+
# => true
|
244
|
+
# irb(main):002:0> kq = Kqueue.new
|
245
|
+
# => #<Kernel::Kqueue:0x14f55b4 @kqfd=4, @pids={}, @fds={}>
|
246
|
+
# irb(main):003:0> fpid = fork{ sleep }
|
247
|
+
# => 616
|
248
|
+
# irb(main):004:0> kq.add(:process, fpid, :events => [:exit])
|
249
|
+
# => true
|
250
|
+
# irb(main):005:0> Process.kill('TERM', fpid)
|
251
|
+
# => 1
|
252
|
+
# irb(main):006:0> kq.poll.first
|
253
|
+
# => {:event=>:exit, :type=>:process, :target=>616}
|
254
|
+
#
|
255
|
+
def add_process(pid, options={})
|
256
|
+
flags, fflags = options.values_at :flags, :events
|
257
|
+
flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
|
258
|
+
fflags = fflags.inject(0){|m,i| m | KQ_FFLAGS[i] }
|
259
|
+
|
260
|
+
k = Kevent.new
|
261
|
+
ev_set(k, pid, EVFILT_PROC, EV_ADD | flags, fflags, 0, nil)
|
262
|
+
|
263
|
+
if kevent(@kqfd, k, 1, nil, 0, nil) == -1
|
264
|
+
return false
|
265
|
+
else
|
266
|
+
@pids[pid] = {:target => pid, :kevent => k}
|
267
|
+
return true
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# 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).
|
272
|
+
#
|
273
|
+
# Using a timeout will block for the duration of the timeout. Under Ruby 1.9.1, we use rb_thread_blocking_region() under the
|
274
|
+
# 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
|
275
|
+
# will block the whole interpreter (all threads) until it returns.
|
276
|
+
#
|
277
|
+
# This call returns an array of hashes, similar to the following:
|
278
|
+
# => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
|
279
|
+
#
|
280
|
+
# * :type - will be the type of event target, i.e. an event set with #add_file will have :type => :file
|
281
|
+
# * :target - the 'target' or 'subject' of the event. This can be a File, IO, process or signal number.
|
282
|
+
# * :event - the event that occurred on the target. This is one of the symbols you passed as :events => [:foo] when adding the event.
|
283
|
+
def poll(timeout=0.0)
|
284
|
+
k = Kevent.new
|
285
|
+
t = Timespec.new
|
286
|
+
t[:tv_sec] = timeout.to_i
|
287
|
+
t[:tv_nsec] = ((timeout - timeout.to_i) * 1e9).to_i
|
288
|
+
|
289
|
+
case kevent(@kqfd, nil, 0, k, 1, t)
|
290
|
+
when -1
|
291
|
+
[errno]
|
292
|
+
when 0
|
293
|
+
[]
|
294
|
+
else
|
295
|
+
[process_event(k)]
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def process_event(k) #:nodoc:
|
300
|
+
res = case k[:filter]
|
301
|
+
when EVFILT_VNODE
|
302
|
+
h = @fds[k[:ident]]
|
303
|
+
return nil if h.nil?
|
304
|
+
event = if k[:fflags] & NOTE_DELETE == NOTE_DELETE
|
305
|
+
:delete
|
306
|
+
elsif k[:fflags] & NOTE_WRITE == NOTE_WRITE
|
307
|
+
:write
|
308
|
+
elsif k[:fflags] & NOTE_EXTEND == NOTE_EXTEND
|
309
|
+
:extend
|
310
|
+
elsif k[:fflags] & NOTE_ATTRIB == NOTE_ATTRIB
|
311
|
+
:attrib
|
312
|
+
elsif k[:fflags] & NOTE_LINK == NOTE_LINK
|
313
|
+
:link
|
314
|
+
elsif k[:fflags] & NOTE_RENAME == NOTE_RENAME
|
315
|
+
:rename
|
316
|
+
elsif k[:fflags] & NOTE_REVOKE == NOTE_REVOKE
|
317
|
+
:revoke
|
318
|
+
end
|
319
|
+
delete(:file, k[:ident]) if event == :delete || event == :revoke
|
320
|
+
{:target => h[:target], :type => :file, :event => event}
|
321
|
+
when EVFILT_READ
|
322
|
+
h = @fds[k[:ident]]
|
323
|
+
return nil if h.nil?
|
324
|
+
{:target => h[:target], :type => :socket, :event => :read}
|
325
|
+
when EVFILT_WRITE
|
326
|
+
h = @fds[k[:ident]]
|
327
|
+
return nil if h.nil?
|
328
|
+
{:target => h[:target], :type => :socket, :event => :write}
|
329
|
+
when EVFILT_PROC
|
330
|
+
h = @pids[k[:ident]]
|
331
|
+
return nil if h.nil?
|
332
|
+
event = if k[:fflags] & NOTE_EXIT == NOTE_EXIT
|
333
|
+
:exit
|
334
|
+
elsif k[:fflags] & NOTE_FORK == NOTE_FORK
|
335
|
+
:fork
|
336
|
+
elsif k[:fflags] & NOTE_EXEC == NOTE_EXEC
|
337
|
+
:exec
|
338
|
+
elsif Kqueue.const_defined?("NOTE_SIGNAL") and k[:fflags] & NOTE_SIGNAL == NOTE_SIGNAL
|
339
|
+
:signal
|
340
|
+
elsif Kqueue.const_defined?("NOTE_REAP") and k[:fflags] & NOTE_REAP == NOTE_REAP
|
341
|
+
:reap
|
342
|
+
end
|
343
|
+
delete(:process, k[:ident]) if event == :exit
|
344
|
+
{:target => h[:target], :type => :process, :event => event}
|
345
|
+
end
|
346
|
+
|
347
|
+
delete(res[:type], res[:target]) if k[:flags] & EV_ONESHOT == EV_ONESHOT
|
348
|
+
res
|
349
|
+
end
|
350
|
+
|
351
|
+
# Stop generating events for the given type and event target, ie:
|
352
|
+
# kq.delete(:process, 6244)
|
353
|
+
def delete(type, target)
|
354
|
+
ident = target.respond_to?(:fileno) ? target.fileno : target
|
355
|
+
container = case type
|
356
|
+
when :socket
|
357
|
+
@fds
|
358
|
+
when :file
|
359
|
+
@fds
|
360
|
+
when :process
|
361
|
+
@pids
|
362
|
+
end
|
363
|
+
h = container[ident]
|
364
|
+
return false if h.nil?
|
365
|
+
k = h[:kevent]
|
366
|
+
ev_set(k, k[:ident], k[:filter], EV_DELETE, k[:fflags], 0, nil)
|
367
|
+
kevent(@kqfd, k, 1, nil, 0, nil)
|
368
|
+
container.delete(ident)
|
369
|
+
return true
|
370
|
+
end
|
371
|
+
|
372
|
+
# Close the kqueue descriptor. This essentially shuts down your kqueue and renders all active events on this kqueue removed.
|
373
|
+
def close
|
374
|
+
IO.for_fd(@kqfd).close
|
375
|
+
end
|
376
|
+
|
377
|
+
end
|
378
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Kernel
|
2
|
+
extend FFI::Library
|
3
|
+
ffi_lib Dir.glob(File.dirname(File.expand_path(__FILE__)) + "/../ktools.*").select{|x| x =~ /\.so$/ || x =~ /\.bundle$/}.first
|
4
|
+
|
5
|
+
# Tells us from Ruby whether or not we have built with support for these libraries
|
6
|
+
%w[epoll kqueue inotify netlink].each do |m|
|
7
|
+
attach_function "have_#{m}".to_sym, [], :int
|
8
|
+
define_method("have_#{m}?") { (self.send "have_#{m}") > 0 ? true : false }
|
9
|
+
end
|
10
|
+
|
11
|
+
attach_function :get_errno, [], :int
|
12
|
+
|
13
|
+
# Returns the current system errno as a Ruby Errno object
|
14
|
+
def errno
|
15
|
+
SystemCallError.new(get_errno)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/tests/test_epoll.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'lib/ktools'
|
2
|
+
require 'bacon'
|
3
|
+
|
4
|
+
include Kernel::Epoll
|
5
|
+
|
6
|
+
describe "the epoll interface" do
|
7
|
+
|
8
|
+
it "should return a valid epoll file descriptor" do
|
9
|
+
@epfd = epoll_create(10)
|
10
|
+
@epfd.class.should.equal Fixnum
|
11
|
+
@epfd.should.be > 0
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should set an event for readable status of a descriptor, and retrieve the event when it occurs" do
|
15
|
+
r, w = IO.pipe
|
16
|
+
ev = Epoll_event.new
|
17
|
+
ev[:events] = EPOLLIN
|
18
|
+
ev[:data] = Epoll_data.new
|
19
|
+
ev[:data][:fd] = r.fileno
|
20
|
+
ev[:data][:u32] = 12345
|
21
|
+
epoll_ctl(@epfd, EPOLL_CTL_ADD, r.fileno, ev).should.equal 0
|
22
|
+
rev = Epoll_event.new
|
23
|
+
epoll_wait(@epfd, rev, 1, 50).should.equal 0
|
24
|
+
w.write "foo"
|
25
|
+
epoll_wait(@epfd, rev, 1, 50).should.equal 1
|
26
|
+
rev[:events].should.equal EPOLLIN
|
27
|
+
rev[:data][:fd].should.equal r.fileno
|
28
|
+
rev[:data][:u32].should.equal 12345
|
29
|
+
w.close
|
30
|
+
r.close
|
31
|
+
w.should.be.closed
|
32
|
+
r.should.be.closed
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should close the epoll file descriptor" do
|
36
|
+
i = IO.for_fd(@epfd)
|
37
|
+
i.should.not.be.closed
|
38
|
+
i.close
|
39
|
+
i.should.be.closed
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'lib/ktools'
|
2
|
+
require 'bacon'
|
3
|
+
|
4
|
+
describe "the kqueue interface" do
|
5
|
+
|
6
|
+
it "should return a valid kqueue file descriptor" do
|
7
|
+
@kqfd = Kqueue::kqueue
|
8
|
+
@kqfd.class.should.equal Fixnum
|
9
|
+
@kqfd.should.be > 0
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should use the raw C API to set an event for file watching, and retrieve the event when it occurs" do
|
13
|
+
file = Tempfile.new("kqueue-test")
|
14
|
+
k = Kqueue::Kevent.new
|
15
|
+
k.should.not.be.nil
|
16
|
+
Kqueue::ev_set(k, file.fileno, Kqueue::EVFILT_VNODE, Kqueue::EV_ADD | Kqueue::EV_CLEAR, Kqueue::NOTE_WRITE, 0, nil)
|
17
|
+
Kqueue::kevent(@kqfd, k, 1, nil, 0, nil)
|
18
|
+
File.open(file.path, 'w'){|x| x.puts 'foo'}
|
19
|
+
n = Kqueue::Kevent.new
|
20
|
+
res = Kqueue::kevent(@kqfd, nil, 0, n, 1, nil)
|
21
|
+
res.should.be > -1
|
22
|
+
n[:ident].should.equal file.fileno
|
23
|
+
n[:filter].should.equal Kqueue::EVFILT_VNODE
|
24
|
+
n[:fflags].should.equal Kqueue::NOTE_WRITE
|
25
|
+
file.close
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should close the kqueue file descriptor" do
|
29
|
+
i = IO.for_fd(@kqfd)
|
30
|
+
i.should.not.be.closed
|
31
|
+
i.close
|
32
|
+
i.should.be.closed
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should add file events using the ruby API" do
|
36
|
+
file = Tempfile.new("kqueue-test")
|
37
|
+
kq = Kqueue.new
|
38
|
+
kq.add(:file, file, :events => [:write, :delete]).should.be.true
|
39
|
+
|
40
|
+
kq.poll.should.be.empty
|
41
|
+
File.open(file.path, 'w'){|x| x.puts 'foo'}
|
42
|
+
|
43
|
+
res = kq.poll.first
|
44
|
+
res.class.should.equal Hash
|
45
|
+
res[:target].class.should.equal Tempfile
|
46
|
+
res[:target].fileno.should.equal file.fileno
|
47
|
+
res[:type].should.equal :file
|
48
|
+
res[:event].should.equal :write
|
49
|
+
|
50
|
+
kq.poll.should.be.empty
|
51
|
+
file.delete
|
52
|
+
|
53
|
+
res2 = kq.poll.first
|
54
|
+
res2[:target].class.should.equal Tempfile
|
55
|
+
res2[:target].fileno.should.equal file.fileno
|
56
|
+
res2[:type].should.equal :file
|
57
|
+
res2[:event].should.equal :delete
|
58
|
+
|
59
|
+
file.close
|
60
|
+
kq.close
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should add events for socket-style descriptors, then delete them, using the Ruby API" do
|
64
|
+
r, w = IO.pipe
|
65
|
+
kq = Kqueue.new
|
66
|
+
kq.add(:socket, r, :events => [:read]).should.be.true
|
67
|
+
|
68
|
+
kq.poll.should.be.empty
|
69
|
+
w.write "foo"
|
70
|
+
|
71
|
+
res = kq.poll.first
|
72
|
+
res[:target].class.should.equal IO
|
73
|
+
res[:target].fileno.should.equal r.fileno
|
74
|
+
res[:type].should.equal :socket
|
75
|
+
res[:event].should.equal :read
|
76
|
+
|
77
|
+
kq.poll.should.be.empty
|
78
|
+
kq.delete(:socket, r).should.be.true
|
79
|
+
w.write "foo"
|
80
|
+
kq.poll.should.be.empty
|
81
|
+
|
82
|
+
[r,w,kq].each{|i| i.close}
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should add events for a process using the Ruby API" do
|
86
|
+
kq = Kqueue.new
|
87
|
+
# Watch for ourself to fork
|
88
|
+
kq.add(:process, Process.pid, :events => [:fork]).should.be.true
|
89
|
+
|
90
|
+
fpid = fork{ at_exit {exit!}; sleep }
|
91
|
+
|
92
|
+
res = kq.poll(1).first
|
93
|
+
res[:target].should.equal Process.pid
|
94
|
+
res[:type].should.equal :process
|
95
|
+
res[:event].should.equal :fork
|
96
|
+
|
97
|
+
# Watch for the child to exit and kill it
|
98
|
+
kq.add(:process, fpid, :events => [:exit])
|
99
|
+
sleep 0.5
|
100
|
+
Process.kill('TERM', fpid)
|
101
|
+
|
102
|
+
res2 = kq.poll(1).first
|
103
|
+
res2[:target].should.equal fpid
|
104
|
+
res2[:type].should.equal :process
|
105
|
+
res2[:event].should.equal :exit
|
106
|
+
|
107
|
+
kq.poll.should.be.empty
|
108
|
+
|
109
|
+
kq.close
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ktools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jake Douglas
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-21 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ffi
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Bringing common kernel APIs into Ruby using FFI
|
26
|
+
email: jakecdouglas@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions:
|
30
|
+
- Rakefile
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- ktools.gemspec
|
35
|
+
- README
|
36
|
+
- Rakefile
|
37
|
+
- lib/ktools.rb
|
38
|
+
- lib/ktools/ktools.rb
|
39
|
+
- lib/ktools/epoll.rb
|
40
|
+
- lib/ktools/kqueue.rb
|
41
|
+
- ext/ktools.c
|
42
|
+
- ext/ktools.h
|
43
|
+
- ext/epoll.c
|
44
|
+
- ext/epoll.h
|
45
|
+
- ext/kqueue.c
|
46
|
+
- ext/kqueue.h
|
47
|
+
- tests/test_epoll.rb
|
48
|
+
- tests/test_kqueue.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://www.github.com/yakischloba/ktools
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project: ktools
|
71
|
+
rubygems_version: 1.3.1
|
72
|
+
signing_key:
|
73
|
+
specification_version: 2
|
74
|
+
summary: Bringing common kernel APIs into Ruby using FFI
|
75
|
+
test_files: []
|
76
|
+
|