class SleepyPenguin::Epoll
The Epoll
class provides high-level access to epoll(7) functionality in the Linux 2.6 and later kernels. It provides fork and GC-safety for Ruby objects stored within the IO
object and may be passed as an argument to IO.select.
Constants
- CLOEXEC
specifies whether close-on-exec flag is set for
Epoll.new
- CTL_ADD
registers a target
IO
object via epoll_ctl- CTL_DEL
unregisters a target
IO
object via epoll_ctl- CTL_MOD
modifies the registration of a target
IO
object via epoll_ctl- ERR
watch for errors, there is no need to specify this, it is always monitored when an
IO
is watched- ET
notifications are only Edge Triggered, see epoll(7)
- EXCLUSIVE
Sets an exclusive wakeup mode for the epoll object that is being attached to the target
IO
. This avoids thundering herd scenarios when the same targetIO
is shared among multiple epoll objects. Available since Linux 4.5- HUP
watch for hangups, there is no need to specify this, it is always monitored when an
IO
is watched- IN
watch for read/recv operations
- ONESHOT
unwatch the descriptor once any event has fired
- OUT
watch for write/send operations
- PRI
watch for urgent read(2) data
- RDHUP
Watch a specified io for shutdown(SHUT_WR) on the remote-end. Available since Linux 2.6.17.
- WAKEUP
This prevents system suspend while event is ready. This requires the caller to have the CAP_BLOCK_SUSPEND capability Available since Linux 3.5
Public Class Methods
Creates a new Epoll
object with an optional flags
argument. flags
may currently be :CLOEXEC
or 0
(or nil
).
# File lib/sleepy_penguin/epoll.rb, line 9 def initialize(create_flags = nil) @io = SleepyPenguin::Epoll::IO.new(create_flags) @mtx = Mutex.new @events = [] @marks = [] @pid = $$ @create_flags = create_flags @copies = { @io => self } end
Public Instance Methods
Starts watching a given io
object with events
which may be an Integer bitmask or Array representing arrays to watch for.
# File lib/sleepy_penguin/epoll.rb, line 77 def add(io, events) fd = io.to_io.fileno events = __event_flags(events) @mtx.synchronize do __ep_check @io.epoll_ctl(CTL_ADD, io, events) @events[fd] = events @marks[fd] = io end 0 end
Closes an existing Epoll
object and returns memory back to the kernel. Raises IOError if object is already closed.
# File lib/sleepy_penguin/epoll.rb, line 194 def close @mtx.synchronize do @copies.delete(@io) @io.close end end
Returns whether or not an Epoll
object is closed.
# File lib/sleepy_penguin/epoll.rb, line 205 def closed? @mtx.synchronize do @io.closed? end end
Disables an IO
object from being watched.
# File lib/sleepy_penguin/epoll.rb, line 93 def del(io) fd = io.to_io.fileno @mtx.synchronize do __ep_check @io.epoll_ctl(CTL_DEL, io, 0) @events[fd] = @marks[fd] = nil end 0 end
This method is deprecated and will be removed in sleepy_penguin 4.x
Stops an io
object from being monitored. This is like Epoll#del
but returns nil
on ENOENT instead of raising an error. This is useful for apps that do not care to track the status of an epoll object itself.
This method is deprecated and will be removed in sleepy_penguin 4.x
# File lib/sleepy_penguin/epoll.rb, line 114 def delete(io) fd = io.to_io.fileno @mtx.synchronize do __ep_check cur_io = @marks[fd] return if nil == cur_io || cur_io.to_io.closed? @io.epoll_ctl(CTL_DEL, io, 0) @events[fd] = @marks[fd] = nil end io rescue Errno::ENOENT, Errno::EBADF end
Returns the events currently watched for in current Epoll
object. Mostly used for debugging.
# File lib/sleepy_penguin/epoll.rb, line 235 def events_for(io) fd = __fileno(io) @mtx.synchronize do __ep_check @events[fd] end end
backwards compatibility, to be removed in 4.x
Returns whether or not a given IO
is watched and prevented from being garbage-collected by the current Epoll
object. This may include closed IO
objects.
# File lib/sleepy_penguin/epoll.rb, line 252 def include?(io) fd = __fileno(io) @mtx.synchronize do __ep_check @marks[fd] ? true : false end end
Returns the given IO
object currently being watched for. Different IO
objects may internally refer to the same process file descriptor. Mostly used for debugging.
# File lib/sleepy_penguin/epoll.rb, line 222 def io_for(io) fd = __fileno(io) @mtx.synchronize do __ep_check @marks[fd] end end
Changes the watch for an existing IO
object based on events
. Returns zero on success, will raise SystemError on failure.
# File lib/sleepy_penguin/epoll.rb, line 132 def mod(io, events) events = __event_flags(events) fd = io.to_io.fileno @mtx.synchronize do __ep_check @io.epoll_ctl(CTL_MOD, io, events) @marks[fd] = io # may be a different object with same fd/file @events[fd] = events end end
This method is deprecated and will be removed in sleepy_penguin 4.x
Used to avoid exceptions when your app is too lazy to check what state a descriptor is in, this sets the epoll descriptor to watch an io
with the given events
events
may be an array of symbols or an unsigned Integer bit mask:
-
events = [ :IN, :ET ]
-
events =
SleepyPenguin::Epoll::IN
|SleepyPenguin::Epoll::ET
See constants in Epoll
for more information.
This method is deprecated and will be removed in sleepy_penguin 4.x
# File lib/sleepy_penguin/epoll.rb, line 160 def set(io, events) fd = io.to_io.fileno @mtx.synchronize do __ep_check cur_io = @marks[fd] if cur_io == io cur_events = @events[fd] return 0 if (cur_events & ONESHOT) == 0 && cur_events == events begin @io.epoll_ctl(CTL_MOD, io, events) rescue Errno::ENOENT warn "epoll event cache failed (mod -> add)\n" @io.epoll_ctl(CTL_ADD, io, events) @marks[fd] = io end else begin @io.epoll_ctl(CTL_ADD, io, events) rescue Errno::EEXIST warn "epoll event cache failed (add -> mod)\n" @io.epoll_ctl(CTL_MOD, io, events) end @marks[fd] = io end @events[fd] = events end 0 end
Epoll
objects may be watched by IO.select and similar methods
# File lib/sleepy_penguin/epoll.rb, line 40 def to_io @mtx.synchronize do __ep_check @io end end
Calls epoll_wait(2) and yields Integer events
and IO
objects watched for. maxevents
is the maximum number of events to process at once, lower numbers may prevent starvation when used by epoll_wait in multiple threads. Larger maxevents
reduces syscall overhead for single-threaded applications. maxevents
defaults to 64 events. timeout
is specified in milliseconds, nil
(the default) meaning it will block and wait indefinitely.
As of sleepy_penguin 3.5.0+, it is possible to nest wait
calls within the same thread.
# File lib/sleepy_penguin/epoll.rb, line 57 def wait(maxevents = 64, timeout = nil) # snapshot the marks so we do can sit this thread on epoll_wait while other # threads may call epoll_ctl. People say RCU is a poor man's GC, but our # (ab)use of GC here is inspired by RCU... snapshot = @mtx.synchronize do __ep_check @marks.dup end # we keep a snapshot of @marks around in case another thread closes # the io while it is being transferred to userspace. We release mtx # so another thread may add events to us while we're sleeping. @io.epoll_wait(maxevents, timeout) { |events, io| yield(events, io) } ensure # hopefully Ruby does not optimize this array away... snapshot.clear end