Skip to content

Fix unable to open StatsD socket. reason: :emfile#55

Open
marcos-sandim wants to merge 1 commit intorkallos:mainfrom
marcos-sandim:fix-open-socket-leak
Open

Fix unable to open StatsD socket. reason: :emfile#55
marcos-sandim wants to merge 1 commit intorkallos:mainfrom
marcos-sandim:fix-open-socket-leak

Conversation

@marcos-sandim
Copy link
Copy Markdown

If StatsD is not available and the packet transmission fails, Peep.StatsD.send_packets/3 removes the socket from the state but does not close it.

This leads to a situation where we try to open a new socket every few seconds and don't close any of them and end up hitting the maximum file descriptors limit.

This change simply closes the socket, freeing up the file descriptor.

Error:

[error] 2025-09-12 17:15:57.855 unable to open StatsD socket. reason: :emfile
        {line=150 pid=<0.440.0> file=lib/peep/statsd.ex domain=elixir application=peep mfa=Peep.Statsd.try_to_open_socket/1 }

If StatsD is not available and the packet transmission fails,
`Peep.StatsD.send_packets/3` removes the socket from the state but does
not close it.

This leads to a situation where we try to open a new socket every few
seconds and don't close any of them, and end up hitting the maximum file
descriptors limit.

This change simply closes the socket, freeing up the file descriptor.

Error:

```
[error] 2025-09-12 17:15:57.855 unable to open StatsD socket. reason: :emfile
        {line=150 pid=<0.440.0> file=lib/peep/statsd.ex domain=elixir application=peep mfa=Peep.Statsd.try_to_open_socket/1 }
```
@rkallos
Copy link
Copy Markdown
Owner

rkallos commented Oct 6, 2025

Thanks for the PR!

I don't see much harm in merging your PR, but I was unable to write a test showcasing the bug that your change claims to fix.

To the best of my knowledge, when a Peep process terminates, the Peep process's UDP socket also gets closed, because it was the UDP socket's owner/controlling process.

This test reliably passes on my machine:

test "UDP socket is closed when Peep process terminates" do
  name = :"#{__MODULE__}_udp_socket_test"

  counter = Metrics.counter("#{name}.counter", event_name: [name, :counter])

  {:ok, options} =
    [
      name: name,
      metrics: [counter],
      statsd: [
        host: "localhost",
        port: 8125,
        flush_interval_ms: 1000
      ]
    ]
    |> Peep.Options.validate()

  {:ok, pid} = GenServer.start(Peep, options, name: options.name)

  state = :sys.get_state(pid)

  assert state.statsd_state != nil
  assert state.statsd_state.socket != nil

  socket = state.statsd_state.socket

  assert match?({:ok, _}, :inet.port(socket))

  GenServer.stop(pid, :shutdown)

  assert match?({:error, :einval}, :inet.port(socket))
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants