Leaking filedescriptors in Plug.Static through a Phoenix app

I am experiencing a similar issue with a live app.

I have a leak of around 100 file descriptors every 24h and all of them for exactly the same file, our JS bundle, app-{digest}.js, that’s being served by Plug.Static.

As far as I know, I don’t use symlinks for anything. On a local dev machine, I build the file with webpack, run mix phx.digest, build a release of the Phoenix app with Distillery and deliver it to the live host with edeliver.

I have an nginx-based load balancer in front of my app, but that’s managed by an external company, so I don’t have direct access to its logs.

Based on the app logs, I get a lot of bogus traffic from bots.

I tried recreating this situation against a fresh Phoenix app with static files locally, but without any success.

However, I was able to reproduce this against my staging environment. Running this code once gives me (reliably, every time) +2 leaked file descriptors (as calculated with ls /proc/$PID/fd | wc -l):

sock = TCPSocket.new('my-staging.com', 443)
sslsock = OpenSSL::SSL::SSLSocket.new(sock)
sslsock.connect
sslsock.write("GET /js/app-{digest}.js?vsn=d HTTP/1.1\r\nHost: my-staging.com\r\nConnection: close\r\n\r\n")
sslsock.close
sock.close

(It’s in Ruby, sorry).

When I test like that against my staging, the leak also happens:

  • for one more .js file, js/admin-{digest}.js (but bots don’t have access to pages that load this file)

It doesn’t happen:

  • for the other 3 .js files that I have except for app and admin
  • for my CSS bundle, /css/app-{digest}.css
  • if I read the socket before closing (sslsock.read)