Skip to content

TCP Socket Listen: Accept Queue [ESTABLISHED CONNECTIONS] - #17

Open
Abishekcs wants to merge 5 commits into
rage-rb:masterfrom
Abishekcs:TCP/kernel-accept-queue
Open

TCP Socket Listen: Accept Queue [ESTABLISHED CONNECTIONS]#17
Abishekcs wants to merge 5 commits into
rage-rb:masterfrom
Abishekcs:TCP/kernel-accept-queue

Conversation

@Abishekcs

@Abishekcs Abishekcs commented Aug 15, 2026

Copy link
Copy Markdown
Member

Issue (Rage): rage-rb/rage#379

What this PR does ?

Adds a way to check how many connections are currently waiting in a listening socket's accept queue, by port number, on Linux.

When a client connects, the kernel completes the TCP handshake before the app gets involved, and the holds the finished connection in the accept queue until the app calls accept(). This queue has a fixed max size, the backlog, bounded by somaxconn, and if it fills up, new connections get dropped from the SYN Queue or reset before the app ever sees them. Checking its depth is a useful health signal, if it stays near the limit, the app isn't accepting fast enough.

fio_accept_queue_backlog_port(port) queries the kernel directly, matching by port instead of requiring a facil.io connection handle. It checks both IPv4 and IPv6m and sums the depths if more than one listening socket shares the port. A Ruby binding, Iodine.accept_quque_backlog(port), is included.

Refrences

Testing

  • I tried using this on codebase using rage (this implementation was just for testing).
test.mp4
  • An also tried with a simple code which only uses Iodine and creates a simple TCP connection
image
require "iodine"
require "socket"

Iodine.workers = 1

Iodine.on_state(:on_start) do
  # 1. a plain TCPServer with 5 queued connections — no iodine involvement
  ts = TCPServer.new("127.0.0.1", 0)
  ts_port = ts.addr[1]
  socks = 5.times.map { TCPSocket.new("127.0.0.1", ts_port) }
  sleep 0.2
  p [:tcpserver_queued, Iodine.accept_queue_backlog(ts_port)]
  socks.each(&:close)
  ts.close

  # 2. iodine's own idle listener
  p [:iodine_idle, Iodine.accept_queue_backlog(3000)]

  # 3. no listener anywhere on a free port
  p [:nothing_listening, Iodine.accept_queue_backlog(45678)]

  # 4. two SO_REUSEPORT listeners on one port → depths should SUM
  def listener(port)
    s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
    s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
    s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, 1)
    s.bind(Socket.sockaddr_in(port, "127.0.0.1"))
    s.listen(128)
    s
  end
  s1 = listener(0)
  sp = s1.local_address.ip_port
  s2 = listener(sp)
  c1 = 3.times.map { TCPSocket.new("127.0.0.1", sp) }
  c2 = 2.times.map { TCPSocket.new("127.0.0.1", sp) }
  sleep 0.2
  p [:reuseport_sum, Iodine.accept_queue_backlog(sp)]
  (c1 + c2).each(&:close)
  s1.close
  s2.close

  Iodine.run { Iodine.stop }
end

Iodine.listen(:port => 3000, :handler => Proc.new { [200, {}, ["ok"]] })
Iodine.start

Query the kernel's TCP accept-queue depth (accepted-but-unconsumed connections) for a listening socket identified by port number, instead of a facil.io uuid. Uses Linux's netlink/inet_diag (NETLINK_INET_DIAG) interface to dump TCP_LISTEN sockets and match by bound port, summing across matches to handle dual-stack
(IPv4/IPv6) and SO_REUSEPORT listeners on the same port.

Linux-only; compiled out on other platforms since netlink has no equivalent there.
@Abishekcs
Abishekcs force-pushed the TCP/kernel-accept-queue branch from 245a59d to 16666c2 Compare August 15, 2026 18:20
…y port

Adds fio_accept_queue_backlog_port(port), which asks the kernel directly how many already-connected clients are sitting in a listening socket's accept queue, waiting for the app to pick them up looked up by port number instead of a facil.io connection handle. This means you can check it without already having a live uuid for that socket.

Also renames the existing uuid-based function from
fio_accept_queue_depth to fio_accept_queue_backlog, to match the kernel's own term for this value (the accept backlog).

On the Ruby side, adds Iodine.accept_queue_backlog(port), which returns the queue length as an integer, or nil if nothing is listening on that port (or if this isn't running on Linux the underlying kernel query only exists there).
…y port

On Linux, adds fio_accept_queue_backlog_port(port), which asks the kernel directly how many already-connected clients are sitting in a listening socket's accept queue, waiting for the app to pick them up looked up by port number rather than needing an existing facil.io connection handle.

This works by opening a netlink socket and asking the kernel's socket-diagnostics interface (the same one `ss`/`netstat` use) to list all TCP sockets currently in the LISTEN state, for both IPv4 and IPv6, then matching by port. If more than one listening socket is bound to that port (dual-stack, or multiple worker processes sharing a port), their queue counts are added together.

Returns -1 if nothing is listening on the port, or if the query fails. Not available on non-Linux platforms, since this relies on a Linux-only kernel interface (netlink).
Covers the main behaviors: nil when nothing is listening on the port, 0 for a listener with no pending connections, a positive count once clients connect but before the app accepts them, the count tracking exactly as more clients connect, and it dropping back to 0 once the app accepts everything.

Also checks input handling: a numeric string port works the same as an Integer, and invalid ports (0, negative, above 65535) raise the expected errors.

Linux only, since the underlying kernel query (netlink) doesn't exist elsewhere the whole spec is skipped on other platforms.
@Abishekcs Abishekcs changed the title [WIP]: TCP Socket Listen: Accept Queue TCP Socket Listen: Accept Queue Aug 17, 2026
@Abishekcs Abishekcs changed the title TCP Socket Listen: Accept Queue TCP Socket Listen: Accept Queue [ESTABLISHED CONNECTIONS] Aug 17, 2026
@Abishekcs
Abishekcs marked this pull request as ready for review August 17, 2026 10:24
@Abishekcs
Abishekcs marked this pull request as draft August 17, 2026 10:27
@Abishekcs Abishekcs changed the title TCP Socket Listen: Accept Queue [ESTABLISHED CONNECTIONS] [WIP]: TCP Socket Listen: Accept Queue [ESTABLISHED CONNECTIONS] Aug 17, 2026
@Abishekcs Abishekcs changed the title [WIP]: TCP Socket Listen: Accept Queue [ESTABLISHED CONNECTIONS] TCP Socket Listen: Accept Queue [ESTABLISHED CONNECTIONS] Aug 17, 2026
@Abishekcs
Abishekcs marked this pull request as ready for review August 17, 2026 10:38
@Abishekcs

Copy link
Copy Markdown
Member Author

@rsamoilov PR is ready for review. Please review it whenever you are free. Thank you!

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.

1 participant