# encoding: utf-8 module XS class Poller include XS::Util attr_reader :readables, :writables def initialize @items = XS::PollItems.new @raw_to_socket = {} @sockets = [] @readables = [] @writables = [] end # Checks each registered socket for selectability based on the poll items' # registered +events+. Will block for up to +timeout+ milliseconds # A millisecond is 1/1000 of a second, so to block for 1 second # pass the value "1000" to #poll. # # Pass "-1" or +:blocking+ for +timeout+ for this call to block # indefinitely. # # This method will return *immediately* when there are no registered # sockets. In that case, the +timeout+ parameter is not honored. To # prevent a CPU busy-loop, the caller of this method should detect # this possible condition (via #size) and throttle the call # frequency. # # @param timeout # # @return 0 when there are no readable/writeable registered sockets # @return number to indicate the number of readable or writable sockets # @return -1 when there is an error # When return code -1 use XS::Util.errno to get the related # error number. def poll timeout = :blocking unless @items.empty? timeout = adjust timeout items_triggered = LibXS.xs_poll @items.address, @items.size, timeout if Util.resultcode_ok?(items_triggered) update_selectables end items_triggered else 0 end end # The non-blocking version of #poll. See the #poll description for # potential exceptions. # # @return -1 when an error is encountered. # # When return code -1 check XS::Util.errno to determine the underlying cause. def poll_nonblock poll 0 end # Register the +sock+ for +events+. This method is idempotent meaning # it can be called multiple times with the same data and the socket # will only get registered at most once. Calling multiple times with # different values for +events+ will OR the event information together. # # @param socket # @param events # One of @XS::POLLIN@ and @XS::POLLOUT@ # # @return true if successful # @return false if not def register sock, events = XS::POLLIN | XS::POLLOUT, fd = 0 return false if (sock.nil? && fd.zero?) || events.zero? item = @items.get(@sockets.index(sock)) unless item @sockets << sock item = LibXS::PollItem.new if sock.kind_of?(XS::Socket) || sock.kind_of?(Socket) item[:socket] = sock.socket item[:fd] = 0 else item[:socket] = FFI::MemoryPointer.new(0) item[:fd] = fd end @raw_to_socket[item.socket.address] = sock @items << item end item[:events] |= events end # Deregister the +sock+ for +events+. When there are no events left, # this also deletes the socket from the poll items. # # @param socket # @param events # One of @XS::POLLIN@ and @XS::POLLOUT@ # # @return true if successful # @return false if not def deregister sock, events, fd = 0 return unless sock || !fd.zero? item = @items.get(@sockets.index(sock)) if item && (item[:events] & events) > 0 # change the value in place item[:events] ^= events delete sock if item[:events].zero? true else false end end # A helper method to register a +sock+ as readable events only. # # @param socket # # @return true if successful # @return false if not def register_readable sock register sock, XS::POLLIN, 0 end # A helper method to register a +sock+ for writable events only. # # @param socket # # @return true if successful # @return false if not def register_writable sock register sock, XS::POLLOUT, 0 end # A helper method to deregister a +sock+ for readable events. # # @param socket # # @return true if successful # @return false if not def deregister_readable sock deregister sock, XS::POLLIN, 0 end # A helper method to deregister a +sock+ for writable events. # # @param socket # # @return true if successful # @return false if not def deregister_writable sock deregister sock, XS::POLLOUT, 0 end # Deletes the +sock+ for all subscribed events. Called internally # when a socket has been deregistered and has no more events # registered anywhere. # # Can also be called directly to remove the socket from the polling # array. # # @param socket # # @return true if successful # @return false if not def delete sock unless (size = @sockets.size).zero? @sockets.delete_if { |socket| socket.socket.address == sock.socket.address } socket_deleted = size != @sockets.size item_deleted = @items.delete sock raw_deleted = @raw_to_socket.delete(sock.socket.address) socket_deleted && item_deleted && raw_deleted else false end end # Convenience method to return size of items array def size(); @items.size; end # Convenience method to inspect items array def inspect @items.inspect end # Convenience method to inspect poller def to_s(); inspect; end private # Create hash of items # # @param empty hash # # @return hash def items_hash hash @items.each do |poll_item| hash[@raw_to_socket[poll_item.socket.address]] = poll_item end end # Update readables and writeables def update_selectables @readables.clear @writables.clear @items.each do |poll_item| #FIXME: spec for sockets *and* file descriptors if poll_item.readable? @readables << (poll_item.socket.address.zero? ? poll_item.fd : @raw_to_socket[poll_item.socket.address]) end if poll_item.writable? @writables << (poll_item.socket.address.zero? ? poll_item.fd : @raw_to_socket[poll_item.socket.address]) end end end # Convert the timeout value to something usable by # the library. # # -1 or :blocking should be converted to -1. # # Users will pass in values measured as # milliseconds, so we need to convert that value to # microseconds for the library. # # @param timeout # # @return number def adjust timeout if :blocking == timeout || -1 == timeout -1 else timeout.to_i end end end end # module XS