Wednesday, July 15, 2026 2:10:37 AM
> socket.rb
# frozen_string_literal: true

module ESM
  module Arma
    class Socket
      IGNORED_IO_ERRORS = [
        "closed stream",
        "stream closed in another thread",
        "uninitialized stream"
      ].freeze

      attr_reader :address

      delegate :close, to: :@socket

      def initialize(socket)
        @socket = socket
        @address = socket.local_address.inspect_sockaddr
      end

      def shutdown(...)
        @socket.shutdown(...)
      rescue Errno::ENOTCONN
      rescue IOError => e
        return if ignored_io_error?(e)
        raise
      end

      def close(...)
        @socket.close(...)
      rescue Errno::ENOTCONN
      rescue IOError => e
        return if ignored_io_error?(e)
        raise
      end

      def readable?(timeout = 3)
        wait_readable(timeout).first.size > 0
      end

      def writeable?(timeout = 3)
        wait_writeable(timeout).second.size > 0
      end

      private

      #
      # Blocks until the socket is readable
      #
      # @param timeout [Integer] Timeout in seconds
      #
      # @returns [Array] The sockets grouped by state [readable, writeable, errored]
      #
      def wait_readable(timeout = 5)
        IO.select([@socket], nil, nil, timeout) || [[], [], []]
      rescue IOError
        [[], [], []]
      end

      #
      # Blocks until the socket is writeable
      #
      # @param timeout [Integer] Timeout in seconds
      #
      # @returns [Array] The sockets grouped by state [readable, writeable, errored]
      #
      def wait_writeable(timeout = 5)
        IO.select(nil, [@socket], nil, timeout) || [[], [], []]
      rescue IOError
        [[], [], []]
      end

      def ignored_io_error?(error)
        IGNORED_IO_ERRORS.include?(error.message)
      end
    end
  end
end
All opinions represented herein are my own
- © 2024 - 2026 itsthedevman
- build 43aa0d7