From: naruse@... Date: 2018-06-02T08:46:57+00:00 Subject: [ruby-core:87358] [Ruby trunk Feature#13396] Net::HTTP has no write timeout Issue #13396 has been updated by naruse (Yui NARUSE). I just noticed that just use write_nonblock can solve this ticket: ```diff diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index 7ec636b384..2a806caeb6 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -77,6 +77,12 @@ class OpenTimeout < Timeout::Error; end class ReadTimeout < Timeout::Error; end + ## + # WriteTimeout, a subclass of Timeout::Error, is raised if a chunk of the + # response cannot be read within the read_timeout. + + class WriteTimeout < Timeout::Error; end + class BufferedIO #:nodoc: internal use only def initialize(io, read_timeout: 60, continue_timeout: nil, debug_output: nil) @@ -237,9 +243,32 @@ def writing def write0(*strs) @debug_output << strs.map(&:dump).join if @debug_output - len = @io.write(*strs) - @written_bytes += len - len + case len = @io.write_nonblock(*strs, exception: false) + when Integer + orig_len = len + strs.each_with_index do |str, i| + len -= str.bytesize + if len == 0 + if strs.size == i+1 + @written_bytes += orig_len + return orig_len + else + strs = strs[i+1..] # rest + break + end + elsif len < 0 + strs = strs[i..] # str and rest + strs[0] = str[len, -len] + break + else # len > 0 + # next + end + end + # continue looping + when :wait_writable + @io.to_io.wait_writable(@write_timeout) or raise Net::ReadTimeout + # continue looping + end while true end # ``` ---------------------------------------- Feature #13396: Net::HTTP has no write timeout https://bugs.ruby-lang.org/issues/13396#change-72348 * Author: byroot (Jean Boussier) * Status: Assigned * Priority: Normal * Assignee: naruse (Yui NARUSE) * Target version: ---------------------------------------- When sending a large request to an unresponsive server, `Net::HTTP` can hang pretty much forever. ```ruby # server.rb require 'socket' server = TCPServer.new('localhost', 2345) loop do socket = server.accept end ``` ```ruby # client.rb require 'net/http' connection = Net::HTTP.new('localhost', 2345) connection.open_timeout = 1 connection.read_timeout = 3 connection.start post = Net::HTTP::Post.new('/') body = (('a' * 1023) + "\n") * 5_000 post.body = body puts "Sending #{body.bytesize} bytes" connection.request(post) ``` The above code will hang forever on all system I tested it on (OSX / Linux 3.19). The issue only trigger once the request body is above a certain threshold. That threshold depends on the system, I assume it's due to the system's TCP settings, but a request over 4MB will trigger the issue consistently. I assume it happens when the request is bigger than the socket buffer. It's stuck on the following path: ``` /net/protocol.rb:211:in `write': Interrupt /net/protocol.rb:211:in `write0' /net/protocol.rb:185:in `block in write' /net/protocol.rb:202:in `writing' /net/protocol.rb:184:in `write' /net/http/generic_request.rb:188:in `send_request_with_body' /net/http/generic_request.rb:121:in `exec' /net/http.rb:1435:in `block in transport_request' /net/http.rb:1434:in `catch' /net/http.rb:1434:in `transport_request' /net/http.rb:1407:in `request' ``` I tried setting `setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, ...)` on the client socket, but without success. However adding a `Timeout.timeout` call around `req.exec` did work. -- https://bugs.ruby-lang.org/ Unsubscribe: