Class: Kernel::Epoll

Inherits:
Object
  • Object
show all
Extended by:
FFI::Library
Defined in:
lib/ktools/epoll.rb

Defined Under Namespace

Classes: Epoll_data, Epoll_event

Constant Summary collapse

EP_FLAGS =
{
  :read => EPOLLIN,
  :write => EPOLLOUT,
  :hangup => EPOLLHUP,
  :priority => EPOLLPRI,
  :edge => EPOLLET,
  :oneshot => EPOLLONESHOT,
  :error => EPOLLERR
}

Instance Method Summary collapse

Constructor Details

#initialize(size = 1024) ⇒ Epoll

Creates a new epoll event queue. Takes an optional size parameter (default 1024) that is a hint to the kernel about how many descriptors it will be handling. Read man epoll_create for details on this. Raises an error if the operation fails.

Raises:

  • (SystemCallError)


65
66
67
68
69
70
# File 'lib/ktools/epoll.rb', line 65

def initialize(size=1024)
  @fds = {}
  @epfd = epoll_create(size)
  raise SystemCallError.new("Error creating epoll descriptor", get_errno) unless @epfd > 0
  @epfd = IO.for_fd(@epfd)
end

Instance Method Details

#add(type, target, options = {}) ⇒ Object

Generic method for adding events. This simply calls the proper add_foo method specified by the type symbol. Example:

ep.add(:socket, sock, :events => [:read])
calls -> ep.add_socket(sock, events => [:read])

Note: even though epoll only supports :socket style descriptors, we keep this for consistency with other APIs.



78
79
80
81
82
83
84
85
# File 'lib/ktools/epoll.rb', line 78

def add(type, target, options={})
  case type
  when :socket
    add_socket(target, options)
  else
    raise ArgumentError.new("Epoll only supports socket style descriptors")
  end
end

#add_socket(target, options = {}) ⇒ Object

Add events to a socket-style descriptor (socket or pipe). Your target can be either an IO object (socket, pipe), or a file descriptor number.

Supported :events are:

  • :read - The descriptor has become readable.

  • :write - The descriptor has become writeable.

  • :priority - There is urgent data available for read operations.

  • :error - Error condition happened on the associated file descriptor. (Always active)

  • :hangup - Hang up happened on the associated file descriptor. (Always active)

  • :remote_hangup - Stream socket peer closed the connection, or shut down writing half of connection. (Missing from some kernel verions)

Supported :flags are:

  • :edge - Sets the Edge Triggered behavior for the associated file descriptor. (see manpage)

  • :oneshot - Sets the one-shot behaviour for the associated file descriptor. (Event only fires once)

Example:

irb(main):001:0> require 'ktools'
=> true
irb(main):002:0> r, w = IO.pipe
=> [#<IO:0x89be38c>, #<IO:0x89be378>]
irb(main):003:0> ep = Epoll.new
=> #<Kernel::Epoll:0x89bca3c @fds={}, @epfd=5>
irb(main):004:0> ep.add(:socket, r, :events => [:read])
=> true
irb(main):005:0> ep.poll
=> []
irb(main):006:0> w.write 'foo'
=> 3
irb(main):007:0> ep.poll
=> [{:target=>#<IO:0x89be38c>, :events=>[:read], :type=>:socket}]
irb(main):008:0> [r, w, ep].each{|x| x.close }


121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/ktools/epoll.rb', line 121

def add_socket(target, options={})
  fdnum = target.respond_to?(:fileno) ? target.fileno : target
  events = (options[:events] + (options[:flags] || [])).inject(0){|m,i| m | EP_FLAGS[i]}

  ev = Epoll_event.new
  ev[:events] = events
  ev[:data] = Epoll_data.new
  ev[:data][:fd] = fdnum

  if epoll_ctl(@epfd.fileno, EPOLL_CTL_ADD, fdnum, ev) == -1
    return false
  else
    @fds[fdnum] = {:target => target, :event => ev}
    return true
  end
end

#closeObject



198
199
200
# File 'lib/ktools/epoll.rb', line 198

def close
  @epfd.close
end

#delete(type, target) ⇒ Object

Stop generating events for the given type and event target, ie:

ep.delete(:socket, sock)

Note: even though epoll only supports :socket style descriptors, we keep this for consistency with other APIs.



189
190
191
192
193
194
195
196
# File 'lib/ktools/epoll.rb', line 189

def delete(type, target)
  ident = target.respond_to?(:fileno) ? target.fileno : target
  h = @fds[ident]
  return false if h.nil?
  epoll_ctl(@epfd.fileno, EPOLL_CTL_DEL, ident, h[:event])
  @fds.delete(ident)
  return true
end

#poll(timeout = 0.0) ⇒ Object

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

Using a timeout will block the current thread for the duration of the timeout. We use select() on the epoll descriptor and then call epoll_wait() with 0 timeout, instead of blocking the whole interpreter with epoll_wait().

This call returns an array of hashes, similar to the following:

=> [{:type=>:socket, :target=>#<IO:0x4fa90c>, :events=>[:read]}]
  • :type - will be the type of event target, i.e. an event set with #add_socket will have :type => :socket

  • :target - the ‘target’ or ‘subject’ of the event. This can be a File, IO, process or signal number.

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

Note: even though epoll only supports :socket style descriptors, we keep :type for consistency with other APIs.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/ktools/epoll.rb', line 151

def poll(timeout=0.0)
  ev = Epoll_event.new

  r, w, e = IO.select([@epfd], nil, nil, timeout)

  if r.nil? || r.empty?
    return []
  else
    case epoll_wait(@epfd.fileno, ev, 1, 0)
    when -1
      [errno]
    when 0
      []
    else
      [process_event(ev)]
    end
  end
end

#process_event(ev) ⇒ Object

:nodoc:



170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/ktools/epoll.rb', line 170

def process_event(ev) #:nodoc:
  h = @fds[ev[:data][:fd]]
  return nil if h.nil?
  events = []
  events << :read if ev[:events] & EPOLLIN == EPOLLIN
  events << :write if ev[:events] & EPOLLOUT == EPOLLOUT
  events << :priority if ev[:events] & EPOLLPRI == EPOLLPRI
  events << :error if ev[:events] & EPOLLERR == EPOLLERR
  events << :hangup if ev[:events] & EPOLLHUP == EPOLLHUP
  events << :remote_hangup if Epoll.const_defined?("EPOLLRDHUP") and ev[:events] & EPOLLRDHUP == EPOLLRDHUP
  events << :oneshot if h[:event][:events] & EPOLLONESHOT == EPOLLONESHOT
  delete(:socket, h[:target]) if events.include?(:oneshot) || events.include?(:hangup) || events.include?(:remote_hangup)
  {:target => h[:target], :events => events, :type => :socket}
end