[ruby-core:112903] [Ruby master Bug#19412] Socket starts queueing and not responding after a certain amount of requests
From:
"brodock (Gabriel Mazetto) via ruby-core" <ruby-core@...>
Date:
2023-03-15 15:33:09 UTC
List:
ruby-core #112903
Issue #19412 has been updated by brodock (Gabriel Mazetto).
I've also tried to build something similar to "hey" in ruby and couldn=B4t =
make it crash (this is likely because of the GVL, we can=B4t get real concu=
rrency):=20
```ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'thread'
end
require 'socket'
require 'thread/pool'
require 'thread/channel'
PORT =3D 8080
LISTEN =3D '127.0.0.1'
NTIMES =3D 80000
CONCURRENCY =3D 512
pool =3D Thread.pool(CONCURRENCY)
channel =3D Thread.channel
NTIMES.times do |t|
pool.process do
socket =3D TCPSocket.new(LISTEN, PORT)
socket.write("HTTP/1.1 GET /\n")
socket.shutdown(Socket::SHUT_WR)
socket.read
channel.send([:success])
rescue Exception =3D> e
channel.send([:error, e])
ensure
socket&.close
end
end
output_thread =3D Thread.new {
success =3D 0
errors =3D []
while data =3D channel.receive
case data[0]
when :success
success +=3D 1
puts "Completed #{success} requests" if success > 0 && (success % (NT=
IMES / 10) =3D=3D 0)
when :error
errors << data[1]
when :eof
puts "Finished with #{errors.size} errors"
errors.each { puts _1 }
break
end
end
}
pool.shutdown
channel.send([:eof])
output_thread.join
```
----------------------------------------
Bug #19412: Socket starts queueing and not responding after a certain amoun=
t of requests
https://bugs.ruby-lang.org/issues/19412#change-102421
* Author: brodock (Gabriel Mazetto)
* Status: Open
* Priority: Normal
* ruby -v: ruby 3.2.1 (2023-02-08 revision 31819e82c8) [arm64-darwin22]
* Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN
----------------------------------------
Here is a an example code:
```ruby
require 'socket'
PORT =3D 8080
BACKLOG =3D 50
LISTEN =3D '127.0.0.1'
def handle_connection(connection, _addrinfo)
request =3D connection.gets
puts request
connection.write "HTTP/1.1 200\r\n"
connection.write "Content-Type: text/html\r\n"
connection.write "\r\n"
connection.write "Hello world! Current time is #{Time.now}"
ensure
connection.close
end
begin
socket =3D Socket.new(:INET, :STREAM)
socket.bind(Addrinfo.tcp(LISTEN, PORT))
socket.listen(BACKLOG)
puts "Requested queue size: #{BACKLOG} bigger then limit: #{Socket::SOMAX=
CONN}" if BACKLOG > Socket::SOMAXCONN
loop do
listening, =3D IO.select([socket])
io, =3D listening
connection, addrinfo =3D io.accept
handle_connection(connection, addrinfo)
end
ensure
socket.close
end
```
This tries to simulate a TCP server that responds as if it was an HTTP serv=
er.
The amount of requests it can sustain seems to depend on the OS. On a Linux=
machine running ubuntu 20.04 I get something around 7.6K to 7.8K until it =
stops responding.
```
$ uname -a
Linux ... 5.17.5-x86_64-linode154 #1 SMP PREEMPT Mon May 2 15:07:22 EDT 202=
2 x86_64 x86_64 x86_64 GNU/Linux
$ ab -n 20000 -c 50 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 2000 requests
Completed 4000 requests
Completed 6000 requests
apr_pollset_poll: The timeout specified has expired (70007)
Total of 7883 requests completed
```
on MacOS Ventura I get around 16K:
```
$ uname -a
Darwin ... 22.2.0 Darwin Kernel Version 22.2.0: Fri Nov 11 02:04:44 PST 202=
2; root:xnu-8792.61.2~4/RELEASE_ARM64_T8103 arm64
$ ab -n 20000 -c 50 http://127.0.0.1:8080/ =20
=
=20
This is ApacheBench, Version 2.3 <$Revision: 1901567 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 2000 requests
Completed 4000 requests
Completed 6000 requests
Completed 8000 requests
Completed 10000 requests
Completed 12000 requests
Completed 14000 requests
Completed 16000 requests
apr_socket_recv: Operation timed out (60)
Total of 16375 requests completed
```
in both cases when that limit reaches, if I abort the program (CTRL-C) and =
try to run it again it takes a while until the port is released:
```
...
GET / HTTP/1.0
GET / HTTP/1.0
GET / HTTP/1.0
^Csocketserver.rb:29:in `select': Interrupt
from socketserver.rb:29:in `block in <main>'
from socketserver.rb:28:in `loop'
from socketserver.rb:28:in `<main>'
$ ruby socketserver.rb =
=
=20
socketserver.rb:23:in `bind': Address already in use - bind(2) for 127.0.0.=
1:8080 (Errno::EADDRINUSE)
from socketserver.rb:23:in `<main>'
```
After killing the process it seems no process is holding the port:
```
lsof -wni tcp:8080
```
Running the command above does not return anything (it does when the progra=
m is still running).
I think we may be failing to release something when interacting with the So=
cket on syscalls/kernel level and we endup filling up some queue/buffer tha=
t eventually gets freed.
--=20
https://bugs.ruby-lang.org/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-c=
ore.ml.ruby-lang.org/