ktools 0.0.1 → 0.0.2

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 CHANGED
@@ -23,13 +23,14 @@ Synopsis:
23
23
  I plan to support the following kernel APIs:
24
24
 
25
25
  kqueue (works - see tests/test_kqueue.rb)
26
- epoll (works - see tests/test_epoll.rb. Needs Ruby wrapper.)
26
+ epoll (works - see tests/test_epoll.rb)
27
27
  inotify
28
28
  netlink
29
29
 
30
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.
31
+ as possible, and then write Rubyist-friendly wrappers around them.
32
+
33
+ Currently kqueue and epoll have Ruby wrapper APIs.
33
34
 
34
35
 
35
36
  To install:
data/Rakefile CHANGED
@@ -57,9 +57,12 @@ task :clean do
57
57
  end
58
58
 
59
59
  task :test do
60
+ require 'rubygems'
60
61
  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?
62
+ require 'bacon'
63
+ Bacon.summary_on_exit
64
+ load "tests/test_kqueue.rb" if Kernel.have_kqueue?
65
+ load "tests/test_epoll.rb" if Kernel.have_epoll?
63
66
  end
64
67
 
65
68
  task :objs do
@@ -1,12 +1,13 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "ktools"
3
- s.version = "0.0.1"
4
- s.date = "2009-04-21"
3
+ s.version = "0.0.2"
4
+ s.date = "2009-04-22"
5
5
  s.authors = ["Jake Douglas"]
6
6
  s.email = "jakecdouglas@gmail.com"
7
7
  s.rubyforge_project = "ktools"
8
8
  s.has_rdoc = true
9
9
  s.add_dependency('ffi')
10
+ s.add_dependency('bacon')
10
11
  s.summary = "Bringing common kernel APIs into Ruby using FFI"
11
12
  s.homepage = "http://www.github.com/yakischloba/ktools"
12
13
  s.description = "Bringing common kernel APIs into Ruby using FFI"
@@ -1,5 +1,5 @@
1
1
  module Kernel
2
- module Epoll
2
+ class Epoll
3
3
  extend FFI::Library
4
4
 
5
5
  class Epoll_data < FFI::Struct
@@ -11,7 +11,11 @@ module Kernel
11
11
 
12
12
  class Epoll_event < FFI::Struct
13
13
  layout :events, :uint32,
14
- :data, Epoll_data
14
+ :data, :pointer
15
+
16
+ def [] key
17
+ key == :data ? Epoll_data.new(super(key)) : super(key)
18
+ end
15
19
  end
16
20
 
17
21
  epc = FFI::ConstGenerator.new do |c|
@@ -26,6 +30,7 @@ module Kernel
26
30
  c.const("EPOLLMSG")
27
31
  c.const("EPOLLERR")
28
32
  c.const("EPOLLHUP")
33
+ c.const("EPOLLRDHUP")
29
34
  c.const("EPOLLONESHOT")
30
35
  c.const("EPOLLET")
31
36
  c.const("EPOLL_CTL_ADD")
@@ -35,11 +40,164 @@ module Kernel
35
40
 
36
41
  eval epc.to_ruby
37
42
 
43
+ EP_FLAGS = {
44
+ :read => EPOLLIN,
45
+ :write => EPOLLOUT,
46
+ :hangup => EPOLLHUP,
47
+ :priority => EPOLLPRI,
48
+ :edge => EPOLLET,
49
+ :oneshot => EPOLLONESHOT,
50
+ :error => EPOLLERR
51
+ }
52
+
53
+ EP_FLAGS[:remote_hangup] = EPOLLRDHUP if const_defined?("EPOLLRDHUP")
54
+
38
55
  # Attach directly to epoll_create
39
56
  attach_function :epoll_create, [:int], :int
40
57
  # Attach directly to epoll_ctl
41
58
  attach_function :epoll_ctl, [:int, :int, :int, :pointer], :int
42
59
  # Attach to the epoll_wait wrapper so we can use rb_thread_blocking_region when possible
43
60
  attach_function :epoll_wait, :wrap_epoll_wait, [:int, :pointer, :int, :int], :int
61
+
62
+ # Creates a new epoll event queue. Takes an optional size parameter (default 1024) that is a hint
63
+ # to the kernel about how many descriptors it will be handling. Read man epoll_create for details
64
+ # on this. Raises an error if the operation fails.
65
+ def initialize(size=1024)
66
+ @fds = {}
67
+ @epfd = epoll_create(size)
68
+ raise SystemCallError.new("Error creating epoll descriptor", get_errno) unless @epfd > 0
69
+ end
70
+
71
+ # Generic method for adding events. This simply calls the proper add_foo method specified by the type symbol.
72
+ # Example:
73
+ # ep.add(:socket, sock, :events => [:read])
74
+ # calls -> ep.add_socket(sock, events => [:read])
75
+ #
76
+ # Note: even though epoll only supports :socket style descriptors, we keep this for consistency with other APIs.
77
+ def add(type, target, options={})
78
+ case type
79
+ when :socket
80
+ add_socket(target, options)
81
+ else
82
+ raise ArgumentError.new("Epoll only supports socket style descriptors")
83
+ end
84
+ end
85
+
86
+ # Add events to a socket-style descriptor (socket or pipe). Your target can be either
87
+ # an IO object (socket, pipe), or a file descriptor number.
88
+ #
89
+ # Supported :events are:
90
+ #
91
+ # * :read - The descriptor has become readable.
92
+ # * :write - The descriptor has become writeable.
93
+ # * :priority - There is urgent data available for read operations.
94
+ # * :error - Error condition happened on the associated file descriptor. (Always active)
95
+ # * :hangup - Hang up happened on the associated file descriptor. (Always active)
96
+ # * :remote_hangup - Stream socket peer closed the connection, or shut down writing half of connection. (Missing from some kernel verions)
97
+ #
98
+ # Supported :flags are:
99
+ #
100
+ # * :edge - Sets the Edge Triggered behavior for the associated file descriptor. (see manpage)
101
+ # * :oneshot - Sets the one-shot behaviour for the associated file descriptor. (Event only fires once)
102
+ #
103
+ # Example:
104
+ #
105
+ # irb(main):001:0> require 'ktools'
106
+ # => true
107
+ # irb(main):002:0> r, w = IO.pipe
108
+ # => [#<IO:0x89be38c>, #<IO:0x89be378>]
109
+ # irb(main):003:0> ep = Epoll.new
110
+ # => #<Kernel::Epoll:0x89bca3c @fds={}, @epfd=5>
111
+ # irb(main):004:0> ep.add(:socket, r, :events => [:read])
112
+ # => true
113
+ # irb(main):005:0> ep.poll
114
+ # => []
115
+ # irb(main):006:0> w.write 'foo'
116
+ # => 3
117
+ # irb(main):007:0> ep.poll
118
+ # => [{:target=>#<IO:0x89be38c>, :event=>:read, :type=>:socket}]
119
+ # irb(main):008:0> [r, w, ep].each{|x| x.close }
120
+ def add_socket(target, options={})
121
+ fdnum = target.respond_to?(:fileno) ? target.fileno : target
122
+ events = (options[:events] + (options[:flags] || [])).inject(0){|m,i| m | EP_FLAGS[i]}
123
+
124
+ ev = Epoll_event.new
125
+ ev[:events] = events
126
+ ev[:data] = Epoll_data.new
127
+ ev[:data][:fd] = fdnum
128
+
129
+ if epoll_ctl(@epfd, EPOLL_CTL_ADD, fdnum, ev) == -1
130
+ return false
131
+ else
132
+ @fds[fdnum] = {:target => target, :event => ev}
133
+ return true
134
+ end
135
+ end
136
+
137
+ # 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
+ # Using a timeout will block for the duration of the timeout. Under Ruby 1.9.1, we use rb_thread_blocking_region() under the
140
+ # hood to allow other threads to run during this call. Prior to 1.9 though, we do not have native threads and hence this call
141
+ # will block the whole interpreter (all threads) until it returns.
142
+ #
143
+ # This call returns an array of hashes, similar to the following:
144
+ # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
145
+ #
146
+ # * :type - will be the type of event target, i.e. an event set with #add_socket will have :type => :socket
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.
149
+ #
150
+ # Note: even though epoll only supports :socket style descriptors, we keep :type for consistency with other APIs.
151
+ def poll(timeout=0.0)
152
+ timeout = (timeout * 1000).to_i
153
+ ev = Epoll_event.new
154
+ case epoll_wait(@epfd, ev, 1, timeout)
155
+ when -1
156
+ [errno]
157
+ when 0
158
+ []
159
+ else
160
+ [process_event(ev)]
161
+ end
162
+ end
163
+
164
+ def process_event(ev) #:nodoc:
165
+ h = @fds[ev[:data][:fd]]
166
+ return nil if h.nil?
167
+
168
+ event = if ev[:events] & EPOLLIN == EPOLLIN
169
+ :read
170
+ elsif ev[:events] & EPOLLOUT == EPOLLOUT
171
+ :write
172
+ elsif ev[:events] & ERPOLLPRI == EPOLLPRI
173
+ :priority
174
+ elsif ev[:events] & EPOLLERR == EPOLLERR
175
+ :error
176
+ elsif ev[:events] & EPOLLHUP == EPOLLHUP
177
+ :hangup
178
+ elsif Epoll.const_defined?("EPOLLRDHUP") and ev[:events] & EPOLLRDHUP == EPOLLRDHUP
179
+ :remote_hangup
180
+ end
181
+
182
+ delete(:socket, h[:target]) if ev[:events] & EPOLLONESHOT == EPOLLONESHOT
183
+ {:target => h[:target], :event => event, :type => :socket}
184
+ end
185
+
186
+ # Stop generating events for the given type and event target, ie:
187
+ # ep.delete(:socket, sock)
188
+ #
189
+ # Note: even though epoll only supports :socket style descriptors, we keep this for consistency with other APIs.
190
+ def delete(type, target)
191
+ ident = target.respond_to?(:fileno) ? target.fileno : target
192
+ h = @fds[ident]
193
+ return false if h.nil?
194
+ epoll_ctl(@epfd, EPOLL_CTL_DEL, ident, h[:event])
195
+ return true
196
+ end
197
+
198
+ def close
199
+ IO.for_fd(@epfd).close
200
+ end
201
+
44
202
  end
45
203
  end
@@ -115,7 +115,7 @@ module Kernel
115
115
 
116
116
  # We provide the raw C interface above. Now we OO-ify it.
117
117
 
118
- # Creates a new kqueue object. Will raise an error if the operation fails.
118
+ # Creates a new kqueue event queue. Will raise an error if the operation fails.
119
119
  def initialize
120
120
  @fds = {}
121
121
  @pids = {}
@@ -137,13 +137,18 @@ module Kernel
137
137
  add_process(target, options)
138
138
  when :signal
139
139
  add_signal(target, options)
140
+ else
141
+ raise ArgumentError.new("Unknown event type #{type}")
140
142
  end
141
143
  end
142
144
 
143
145
  # Add events on a file to the Kqueue. kqueue requires that a file actually be opened (to get a descriptor)
144
146
  # before it can be monitored. You can pass a File object here, or a String of the pathname, in which
145
147
  # 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:
148
+ # in the event returned by #poll. If you want to keep track of it yourself, you can just pass the file
149
+ # descriptor number (and that's what you'll get back.)
150
+ #
151
+ # Valid events here are as follows, using descriptions from the kqueue man pages:
147
152
  #
148
153
  # * :delete - "The unlink() system call was called on the file referenced by the descriptor."
149
154
  # * :write - "A write occurred on the file referenced by the descriptor."
@@ -174,22 +179,26 @@ module Kernel
174
179
  fflags, flags = options.values_at :events, :flags
175
180
  raise ArgumentError.new("must specify which file events to watch for") unless fflags
176
181
 
177
- file = file.kind_of?(File) || file.kind_of?(Tempfile) ? file : File.open(file, 'r')
182
+ file = file.kind_of?(String) ? File.open(file, 'r') : file
183
+ fdnum = file.respond_to?(:fileno) ? file.fileno : file
178
184
 
179
185
  k = Kevent.new
180
186
  flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
181
187
  fflags = fflags.inject(0){|m,i| m | KQ_FFLAGS[i] }
182
- ev_set(k, file.fileno, EVFILT_VNODE, EV_ADD | flags, fflags, 0, nil)
188
+ ev_set(k, fdnum, EVFILT_VNODE, EV_ADD | flags, fflags, 0, nil)
183
189
 
184
190
  if kevent(@kqfd, k, 1, nil, 0, nil) == -1
185
191
  return false
186
192
  else
187
- @fds[file.fileno] = {:target => file, :kevent => k}
193
+ @fds[fdnum] = {:target => file, :event => k}
188
194
  return true
189
195
  end
190
196
  end
191
197
 
192
- # Add events to a socket-style descriptor (socket or pipe). Supported events are:
198
+ # Add events to a socket-style descriptor (socket or pipe). Your target can be either
199
+ # an IO object (socket, pipe), or a file descriptor number.
200
+ #
201
+ # Supported events are:
193
202
  #
194
203
  # * :read - The descriptor has become readable.
195
204
  # * :write - The descriptor has become writeable.
@@ -213,18 +222,19 @@ module Kernel
213
222
  # irb(main):007:0> kq.poll
214
223
  # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
215
224
  # irb(main):008:0> [r, w, kq].each {|i| i.close}
216
- def add_socket(io, options={})
225
+ def add_socket(target, options={})
217
226
  filters, flags = options.values_at :events, :flags
218
227
  flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
219
228
  filters = filters ? filters.inject(0){|m,i| m | KQ_FILTERS[i] } : EVFILT_READ | EVFILT_WRITE
229
+ fdnum = target.respond_to?(:fileno) ? target.fileno : target
220
230
 
221
231
  k = Kevent.new
222
- ev_set(k, io.fileno, filters, EV_ADD | flags, 0, 0, nil)
232
+ ev_set(k, fdnum, filters, EV_ADD | flags, 0, 0, nil)
223
233
 
224
234
  if kevent(@kqfd, k, 1, nil, 0, nil) == -1
225
235
  return false
226
236
  else
227
- @fds[io.fileno] = {:target => io, :kevent => k}
237
+ @fds[fdnum] = {:target => target, :event => k}
228
238
  return true
229
239
  end
230
240
  end
@@ -263,7 +273,7 @@ module Kernel
263
273
  if kevent(@kqfd, k, 1, nil, 0, nil) == -1
264
274
  return false
265
275
  else
266
- @pids[pid] = {:target => pid, :kevent => k}
276
+ @pids[pid] = {:target => pid, :event => k}
267
277
  return true
268
278
  end
269
279
  end
@@ -362,7 +372,7 @@ module Kernel
362
372
  end
363
373
  h = container[ident]
364
374
  return false if h.nil?
365
- k = h[:kevent]
375
+ k = h[:event]
366
376
  ev_set(k, k[:ident], k[:filter], EV_DELETE, k[:fflags], 0, nil)
367
377
  kevent(@kqfd, k, 1, nil, 0, nil)
368
378
  container.delete(ident)
@@ -1,29 +1,24 @@
1
- require 'lib/ktools'
2
- require 'bacon'
3
-
4
- include Kernel::Epoll
5
-
6
1
  describe "the epoll interface" do
7
2
 
8
3
  it "should return a valid epoll file descriptor" do
9
- @epfd = epoll_create(10)
4
+ @epfd = Epoll::epoll_create(10)
10
5
  @epfd.class.should.equal Fixnum
11
6
  @epfd.should.be > 0
12
7
  end
13
8
 
14
9
  it "should set an event for readable status of a descriptor, and retrieve the event when it occurs" do
15
10
  r, w = IO.pipe
16
- ev = Epoll_event.new
17
- ev[:events] = EPOLLIN
18
- ev[:data] = Epoll_data.new
11
+ ev = Epoll::Epoll_event.new
12
+ ev[:events] = Epoll::EPOLLIN
13
+ ev[:data] = Epoll::Epoll_data.new
19
14
  ev[:data][:fd] = r.fileno
20
15
  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
16
+ Epoll::epoll_ctl(@epfd, Epoll::EPOLL_CTL_ADD, r.fileno, ev).should.equal 0
17
+ rev = Epoll::Epoll_event.new
18
+ Epoll::epoll_wait(@epfd, rev, 1, 50).should.equal 0
24
19
  w.write "foo"
25
- epoll_wait(@epfd, rev, 1, 50).should.equal 1
26
- rev[:events].should.equal EPOLLIN
20
+ Epoll::epoll_wait(@epfd, rev, 1, 50).should.equal 1
21
+ rev[:events].should.equal Epoll::EPOLLIN
27
22
  rev[:data][:fd].should.equal r.fileno
28
23
  rev[:data][:u32].should.equal 12345
29
24
  w.close
@@ -39,4 +34,36 @@ describe "the epoll interface" do
39
34
  i.should.be.closed
40
35
  end
41
36
 
37
+ it "should add socket events and retrieve them using the Ruby API" do
38
+ r, w = IO.pipe
39
+ ep = Epoll.new
40
+
41
+ ep.add(:socket, r, :events => [:read]).should.be.true
42
+ ep.poll.should.be.empty
43
+
44
+ w.write 'foo'
45
+ res = ep.poll.first
46
+
47
+ res[:event].should.equal :read
48
+ res[:target].fileno.should.equal r.fileno
49
+ res[:type].should.equal :socket
50
+
51
+ [r,w,ep].each{|i| i.close}
52
+ end
53
+
54
+ it "should delete events using the Ruby API" do
55
+ r, w = IO.pipe
56
+ ep = Epoll.new
57
+
58
+ ep.add(:socket, r, :events => [:read]).should.be.true
59
+ ep.poll.should.be.empty
60
+
61
+ w.write 'foo'
62
+ ep.delete(:socket, r).should.be.true
63
+
64
+ ep.poll.should.be.empty
65
+
66
+ [r,w,ep].each{|i| i.close}
67
+ end
68
+
42
69
  end
@@ -1,6 +1,3 @@
1
- require 'lib/ktools'
2
- require 'bacon'
3
-
4
1
  describe "the kqueue interface" do
5
2
 
6
3
  it "should return a valid kqueue file descriptor" do
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.1
4
+ version: 0.0.2
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-21 00:00:00 -07:00
12
+ date: 2009-04-22 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,6 +22,16 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: "0"
24
24
  version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: bacon
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
25
35
  description: Bringing common kernel APIs into Ruby using FFI
26
36
  email: jakecdouglas@gmail.com
27
37
  executables: []