lib/socketry/tcp/socket.rb in socketry-0.2.0 vs lib/socketry/tcp/socket.rb in socketry-0.3.0

- old
+ new

@@ -96,10 +96,12 @@ remote_sockaddr = ::Socket.sockaddr_in(remote_port, remote_addr.to_s) # Note: `exception: false` for Socket#connect_nonblock is only supported in Ruby 2.3+ begin socket.connect_nonblock(remote_sockaddr) + rescue Errno::ECONNREFUSED => ex + raise Socketry::ConnectionRefusedError, "connection to #{remote_addr}:#{remote_port} refused", ex.backtrace rescue Errno::EINPROGRESS, Errno::EALREADY # Earlier JRuby 9.x versions do not seem to correctly support Socket#wait_writable in this case # Newer versions seem to behave correctly retry if IO.select(nil, [socket], nil, time_remaining(timeout)) @@ -160,30 +162,58 @@ end # Read a partial amounth of data, blocking until it becomes available # # @param size [Fixnum] number of bytes to attempt to read + # @param outbuf [String] an output buffer to read data into + # @param timeout [Numeric] Number of seconds to wait for read operation to complete # @raise [Socketry::Error] an I/O operation failed - # @return [String] + # @return [String, :eof] bytes read, or :eof if socket closed while reading def readpartial(size, outbuf: nil, timeout: @read_timeout) set_timeout(timeout) begin while (result = read_nonblock(size, outbuf: outbuf)) == :wait_readable - next if @socket.wait_readable(read_timeout) + next if @socket.wait_readable(time_remaining(timeout)) raise TimeoutError, "read timed out after #{timeout} seconds" end ensure clear_timeout(timeout) end result || :eof end + # Read all of the data in a given string to a socket unless timeout or EOF + # + # @param size [Fixnum] number of bytes to attempt to read + # @param outbuf [String] an output buffer to read data into + # @param timeout [Numeric] Number of seconds to wait for read operation to complete + # @raise [Socketry::Error] an I/O operation failed + # @return [String, :eof] bytes read, or :eof if socket closed while reading + def read(size, outbuf: String.new, timeout: @write_timeout) + outbuf.clear + deadline = lifetime + timeout if timeout + + begin + until outbuf.size == size + time_remaining = deadline - lifetime if deadline + raise Socketry::TimeoutError, "read timed out after #{timeout} seconds" if timeout && time_remaining <= 0 + + chunk = readpartial(size - outbuf.size, timeout: time_remaining) + return :eof if chunk == :eof + + outbuf << chunk + end + end + + outbuf + end + # Perform a non-blocking write operation # - # @param data [String] number of bytes to attempt to read + # @param data [String] data to write to the socket # @raise [Socketry::Error] an I/O operation failed # @return [Fixnum, :wait_writable] number of bytes written, or :wait_writable if op would block def write_nonblock(data) ensure_connected @socket.write_nonblock(data, exception: false) @@ -194,25 +224,52 @@ raise Socketry::Error, ex.message, ex.backtrace end # Write a partial amounth of data, blocking until it's completed # - # @param data [String] number of bytes to attempt to read + # @param data [String] data to write to the socket + # @param timeout [Numeric] Number of seconds to wait for write operation to complete # @raise [Socketry::Error] an I/O operation failed - # @return [Fixnum, :wait_writable] number of bytes written, or :wait_writable if op would block + # @return [Fixnum, :eof] number of bytes written, or :eof if socket closed during writing def writepartial(data, timeout: @write_timeout) set_timeout(timeout) begin while (result = write_nonblock(data)) == :wait_writable - next if @socket.wait_writable(read_timeout) + next if @socket.wait_writable(time_remaining(timeout)) raise TimeoutError, "write timed out after #{timeout} seconds" end ensure clear_timeout(timeout) end result || :eof + end + + # Write all of the data in a given string to a socket unless timeout or EOF + # + # @param data [String] data to write to the socket + # @param timeout [Numeric] Number of seconds to wait for write operation to complete + # @raise [Socketry::Error] an I/O operation failed + # @return [Fixnum] number of bytes written, or :eof if socket closed during writing + def write(data, timeout: @write_timeout) + total_written = data.size + deadline = lifetime + timeout if timeout + + begin + until data.empty? + time_remaining = deadline - lifetime if deadline + raise Socketry::TimeoutError, "write timed out after #{timeout} seconds" if timeout && time_remaining <= 0 + + bytes_written = writepartial(data, timeout: time_remaining) + return :eof if bytes_written == :eof + + break if bytes_written == data.bytesize + data = data.byteslice(bytes_written..-1) + end + end + + total_written end # Check whether Nagle's algorithm has been disabled # # @return [true] Nagle's algorithm has been explicitly disabled