Hello nimmers!
Long time lurker, first time poster.
I had a random problem at $dayjob that I decided would be a fun and easy fit to use nim to solve.
There's a daemon that streams line data over a TCP socket at an interval. There are no delimiters in the stream, and it's an unknown amount of data per interval. After putting something together fairly quickly, I hit an exception after a few hours:
Error: unhandled exception: Too many open files [OSError]
Sure enough, I appear to be leaking closed sockets with each connection.
test 57537 root 4u IPv4 0xfffff80024e4b410 0t0 TCP *:* (CLOSED)
test 57537 root 5u IPv4 0xfffff8001a1e7000 0t0 TCP *:* (CLOSED)
test 57537 root 6u IPv4 0xfffff8001ad2a410 0t0 TCP *:* (CLOSED)
test 57537 root 7u IPv4 0xfffff800142bd820 0t0 TCP *:* (CLOSED)
test 57537 root 8u IPv4 0xfffff80003e07410 0t0 TCP *:* (CLOSED)
test 57537 root 9u IPv4 0xfffff80003e07000 0t0 TCP *:* (CLOSED)
.... (etc etc etc)
I've gutted everything down to the simplest test code that reproduces the problem.
import net
var server = newSocket()
server.set_sock_opt( OptReuseAddr, true )
server.bind_addr( Port(14865) )
server.listen()
while true:
var
client = newSocket()
address = ""
result = ""
server.acceptAddr( client, address ) # block for a client
try:
result = client.recv_line( timeout=500 ) & "\n"
while result != "":
result = result & client.recv_line( timeout=500 ) & "\n"
except TimeoutError:
echo "Done receiving data from " & address
finally:
client.close
echo address & " said: " & result
Am I misunderstanding something about close()? (I thought that freed the FD to the kernel?) Something else naive I'm missing (some sockopt)?
Help/ideas/cluebats all equally appreciated. This is Nim 0.17.2, same behavior on both Linux and FreeBSD.
You're creating a new socket FD for the client and acceptAddr then overwrites it with another FD without closing the socket you created.
I've seen this gotcha happen to at least two people now. Please report it as an issue on github :)
Bingo! Thanks for the quick reply.
I was using the example at https://nim-lang.org/docs/net.html, so maybe this is just a docs update ticket.
I can't simply declare the var type in advance without an assignment:
var client: Socket
server.acceptAddr( client, address )
...
Traceback (most recent call last)
test.nim(21) test
net.nim(867) close
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
However, I can create the socket and immediately close it.
var client = newSocket()
client.close
server.acceptAddr( client, address )
... which works, without leaking, though definitely isn't an obvious path. Fixed.
What is the "right" way do do this?
This example is actually wrong. You should just use new instead of newSocket, this is described in the docs for accept/acceptAddr (https://nim-lang.org/docs/net.html#acceptAddr,Socket,Socket,string).
Edit: I created an issue to fix this gotcha btw: https://github.com/nim-lang/Nim/issues/7227