ktools 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|