crystal icon indicating copy to clipboard operation
crystal copied to clipboard

Using a logger causes infinite spam logs when file descriptor limit is reached in `HTTP::Server`

Open syeopite opened this issue 5 months ago • 5 comments
trafficstars

Bug Report

Using the logger to print any kind of message (Log.setup doesn't seem to trigger this) results in the HTTP::Server infinitely spamming logs whenever the file descriptor limit is reached.

require "http/server"

Log.info { "test" }

server = HTTP::Server.new do | context |
  loop do
    context.response.puts ":keepalive #{Time.utc.to_unix}"
    context.response.puts
    context.response.flush

    sleep 2.seconds
  end
end

server.listen 12345

Results in

2025-06-01T19:01:40.364067Z  ERROR - http.server: Error while connecting a new socket
accept: Too many open files (Socket::Error)
...

2025-06-01T19:01:40.364070Z  ERROR - http.server: Error while connecting a new socket
accept: Too many open files (Socket::Error)
...

2025-06-01T19:01:40.364074Z  ERROR - http.server: Error while connecting a new socket
accept: Too many open files (Socket::Error)
...
and so on

When the logger isn't present, the program just crashes instantly and only two exceptions are logged.

Crystal 1.16.0 (2025-04-11)

LLVM: 19.1.7
Default target: x86_64-pc-linux-gnu

Here's a simple script that can trigger this bug in the above code

Simple request to spam request a server to trigger this
require "http"
require "wait_group"

ADDRESS = URI.parse("http://localhost:12345")

def make_client
  client = HTTP::Client.new(ADDRESS)
  client.get("/") do | response |
    until response.body_io.closed?
      response.body_io.gets
    end
  end
ensure
  client.try &.close
end

5.times do
  clients_opened = [] of HTTP::Client
  # Create in batches as to not trigger our own file descriptor limit
  250.times { spawn { make_client } }
end

sleep

syeopite avatar Jun 01 '25 19:06 syeopite