socket
nimony/lib/std/socket.nim
const ReadChunk: int64How much is asked for per
read, and the buffer's initial size and growth step.const MaxBuffered: int64Ceiling on one socket's read buffer. Past this the peer is made to wait rather than us to allocate: a protocol that needs a whole message resident enforces its own, smaller limit long before this one.
type PeerAddr = object raw: Sockaddr_storage
proc family(p: PeerAddr): int64AF_INET,AF_INET6, or0for an address nothing filled in. Read throughSockAddr, whose declaration already knows whether this platform puts a length byte in front of the family.proc isV4(p: PeerAddr): boolproc isV6(p: PeerAddr): boolproc port(p: PeerAddr): int64The peer's port, or
0if there is no address. Network byte order read as two bytes, so no host-endianness assumption is needed.proc addIp(s: var string; p: PeerAddr)Append the address without the port. Appends nothing for an unfilled one.
proc ip(p: PeerAddr): stringThe address without the port:
"127.0.0.1","::1",""for none.proc $(p: PeerAddr): stringAddress and port in the form everything else writes them:
1.2.3.4:80for v4,[::1]:80for v6 — the brackets are not decoration, they are what keeps the port's colon apart from the address's.proc readAsync(fd: int32; buf: pointer not nil; len: int64; dl: Deadline): int64One
read, parked on the ring. The bytes read,0at end of stream, or negative on error —IoTimedOutwhen the deadline arrived first.resultis initialised before its address is taken because the ring writes through that pointer when it completes, and the compiler will not hand out the address of something it cannot prove is initialised.proc writeAsync(fd: int32; buf: pointer not nil; len: int64; dl: Deadline): int64One
write, parked on the ring.proc acceptAsync(listenFd: int32; peer: var PeerAddr; dl: Deadline): int64One
accept, parked on the ring. The accepted fd, or negative on error —IoTimedOutwhen the deadline arrived first.peeris filled with who connected, at no extra syscall: the kernel wrote the address as part of the accept and this is only the hand-off. It is left untouched when the accept fails.The returned fd is not non-blocking yet —
setNonBlockingit before handing it to aSocket, or every subsequent op on it blocks a lane.type Socket = object fd: int32 deadline: Deadline rbuf: seq rlen: int64 rpos: int64 wbuf: seq
proc =destroy(s: Socket)Closing is not something a caller has to remember. A
Socketowns its fd, so the fd goes when the socket does — the leak that mattered was never the buffers, it was the descriptor on the error path nobody wrote.Like the explicit
close, this must run on the thread that submitted the socket's in-flight ops, because the ring's slot arenas are per-lane. That is what a socket belonging to its lane for life buys: it also dies there.proc =wasMoved(s: var Socket)The fd went with the destination, so this one must not close it.
-1rather than the default zeroing:0is a perfectly good descriptor — stdin — and a moved-from socket that closes it is the kind of bug that surfaces somewhere else entirely.proc =copy(dest: var Socket; src: Socket)Move-only: two sockets over one fd means two owners of one descriptor, and whichever is destroyed first closes it under the other.
proc initSocket(fd: int32; deadline: Deadline): Socketfdmust already be non-blocking. The deadline has no default: a socket with no budget is one a quiet peer can hold forever.proc budget(s: Socket; dl: Deadline): DeadlineThe deadline an operation actually runs under: the socket's, or a tighter one the caller supplied.
earliermeans a caller can never widen the budget by passing a later instant.proc renew(s: var Socket; deadline: Deadline)Start a fresh budget. A kept-alive connection serves many exchanges and each gets its own, or the first would spend the whole socket's allowance on behalf of all of them.
proc close(s: var Socket)Close now rather than at the end of the scope. Idempotent, and the destructor then has nothing left to do — which is what a protocol above wants when "the peer said goodbye" happens well before the socket's owner goes out of scope.
proc buffered(s: Socket): int64Bytes read but not yet consumed.
proc peek(s: Socket): openArrayThe bytes read but not yet consumed, as a view into the buffer. This is what a parser is handed: it neither slices nor owns anything, and what it does not consume stays put for the next read to extend.
proc consume(s: var Socket; n: int64)Drop the first
nbytes ofpeek— what the parser just used.proc fill(s: var Socket; dl: Deadline): int64One read into the buffer. The bytes added, or
0at end of stream — which is a peer that has finished, not a failure, so it is a value and not a raise. Compacts first, then grows up toMaxBuffered.Raises
FullErrorwhen the buffer is at its ceiling and still full: the peer has sent more of something than we agreed to hold, and growing further would let it pick our memory use.TimeoutErrorwhen the deadline arrived first,IOErrorotherwise.proc take(s: var Socket; dest: var openArray; limit: int64): int64Copy up to
limitbytes of what is already buffered intodest, and consume them. No IO, so no deadline: this is the half of a read that a protocol doing its own framing wants afterfillhas put bytes in.proc read(s: var Socket; dest: var openArray; dl: Deadline): int64Fill
dest, starting with whatever is already buffered. The bytes copied — fewer than asked for only at end of stream. Raises whatfillraises.proc toErr(n: int64): ErrorCodeWhat a negative ring result means. Exported because a protocol above may have to decide for itself whether a
0— the peer closing — is an end or a truncation, and wants the same mapping for the rest.proc writeAll(s: var Socket; buf: pointer not nil; len: int64; dl: Deadline)Write every byte or raise. A short write is normal — the peer's window is not our business — so it loops rather than reporting partial success that a caller would have to unpick. There is no partial success to report: either every byte went or this raised.
proc write(s: var Socket; data: openArray; dl: Deadline)Send bytes already in memory. Nothing is copied.
proc scratch(s: var Socket; n: int64): var openArrayA writable staging area of at least
nbytes, kept between calls so serializing a message does not allocate per message. Valid until the nextscratch;flushis what sends it.proc flush(s: var Socket; n: int64; dl: Deadline)Send the first
nbytes of the staging area in one write. One write and not several because a frame split across writes is a frame a peer can be left half-way through.