From: naruse@... Date: 2017-05-20T18:48:03+00:00 Subject: [ruby-core:81311] [Ruby trunk Feature#13396][Assigned] Net::HTTP has no write timeout Issue #13396 has been updated by naruse (Yui NARUSE). Status changed from Open to Assigned Assignee set to naruse (Yui NARUSE) The concept Net::HTTP#write_timeout sounds fine. > However adding a Timeout.timeout call around req.exec did work. Timeou.timeout should be avoided because it sometimes caused trouble and now eliminated. (see also https://github.com/ruby/ruby/pull/899) > I would also like to add native timeout support to IO.copy_stream Agree. > Note: I checked your commit c789ab8df5c8d6e0643ccd481f748014f0066345 You may know, https://github.com/ruby/ruby/pull/1575.patch and https://github.com/ruby/ruby/pull/1575.diff are also available. They are written as LINK rel="alternate" in the HTML. > > 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. > Under Linux, SO_SNDTIMEO won't work if the the socket was set to nonblocking which would cause Ruby to wait with ppoll (select on other platforms). Like this? Maybe though IO#write seems to be kept as is. ```diff @@ -1340,6 +1348,17 @@ io_binwrite(VALUE str, const char *ptr, long len, rb_io_t *fptr, int nosync) n -= r; errno = EAGAIN; } +#ifdef SO_SNDTIMEO + if (errno == EAGAIN && is_socket(fptr->fd, fptr->pathv)) { + /* Even if EAGAIN, it may be caused by timeout */ + struct timeval timeout; + socklen_t sz = sizeof(timeout); + if (getsockopt(fptr->fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, &sz) == 0 && + timeout.tv_sec !=0 || timeout.tv_usec != 0) { + return r; + } + } +#endif if (r == -2L) return -1L; if (rb_io_wait_writable(fptr->fd)) { ``` ---------------------------------------- Feature #13396: Net::HTTP has no write timeout https://bugs.ruby-lang.org/issues/13396#change-64992 * 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: