From 73b0780321acb14bab6707006f5136ea9998951d Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Thu, 30 Jul 2026 19:22:17 +0200 Subject: feat(receive): add maxsize argument to bound memory usage client:receive("*l") and receive("*a") are unbounded: a peer that never sends a newline, or never closes, makes LuaSocket buffer until the process runs out of memory (e.g. src/http.lua reading response headers in a loop). Add an optional maxsize argument that caps the payload a single call may accumulate, including prefix. - Hoist all argument validation ahead of timeout_markstart() so bad calls (maxsize < 1, #prefix >= maxsize, numeric pattern > maxsize) raise before any I/O and leave the socket untouched. - recvline/recvall take a budget and return a new internal BUF_OVERSIZED code, surfaced to Lua as the "oversized" error alongside "timeout"/"closed", with the partial held in the 3rd return value. - recvraw is left untouched: argument checks make the cap unreachable for numeric patterns. - Preserve three invariants: a timeout partial is always shorter than maxsize (safe to retry as prefix), completion beats the cap for *a, and no bytes are lost or skipped on overflow. - tcp.c, unixstream.c and serial.c all share this code path unchanged. Adds test coverage (argument errors, *l/*a boundaries, timeout/close at the cap, the drain idiom, numeric patterns, unix-stream mirror) and documents the new argument, error, and recovery idioms in docs/tcp.html. --- docs/tcp.html | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/tcp.html b/docs/tcp.html index a26228d..4c6c6bc 100644 --- a/docs/tcp.html +++ b/docs/tcp.html @@ -351,7 +351,7 @@ method returns nil followed by an error message.

-client:receive([pattern [, prefix]]) +client:receive([pattern [, prefix [, maxsize]]])

@@ -380,14 +380,23 @@ of bytes from the socket. of any received data before return.

+

+Maxsize is an optional positive integer bounding the number of +payload bytes the call may accumulate, including prefix. +Omitted or nil means unlimited. +

+

If successful, the method returns the received pattern. In case of error, the method returns nil followed by an error message, followed by a (possibly empty) string containing the partial that was received. The error message can be the string 'closed' in case the connection was -closed before the transmission was completed or the string -'timeout' in case there was a timeout during the operation. +closed before the transmission was completed, the string +'timeout' in case there was a timeout during the operation, or, +when maxsize was given, the string 'oversized' in case +the pattern did not complete within maxsize bytes -- in which case +the third return value holds exactly maxsize bytes.

@@ -399,6 +408,49 @@ functions should return nil on error. Thus it was changed too.

+

+Note on maxsize: passing a maxsize that is smaller +than 1, a prefix whose length is greater than or equal to +maxsize, or, for a numeric pattern, a byte count greater +than maxsize, all raise a Lua error rather than returning +nil plus a message -- these are caller logic errors, and +they are detected before any byte is read from the socket. To drain and +discard an oversized line while keeping memory bounded and the stream +aligned: +

+ +
+local data, err, part
+repeat
+    data, err, part = client:receive("*l", "", 4096)
+until err ~= "oversized"
+
+ +

+To instead retry and eventually get the whole thing, carry the partial +forward as prefix and grow maxsize: +

+ +
+local data, err, part = client:receive("*l", nil, 4096)
+if err == "oversized" then
+    data, err, part = client:receive("*l", part, 65536)   -- larger cap, or this raises
+end
+
+ +

+Retrying with prefix set to the previous partial result and an +unchanged maxsize raises the length-check error above by +design -- otherwise it would be a zero-progress spin: no I/O, no timeout, +no error, just CPU. A timeout partial is always strictly shorter +than maxsize, so it is always safe to feed straight back as +prefix with the same maxsize. Finally, note that +maxsize bounds the payload returned, not necessarily the +bytes taken off the wire: for the *l pattern the discarded CR +characters and the line terminator mean more bytes may have been consumed +than the returned length suggests. +

+

-- cgit v1.2.3-55-g6feb From e912d157b242e661dca37d0a8269202dda974ba7 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Mon, 17 Aug 2026 07:39:55 +0200 Subject: chore(docs): some cleanup --- docs/tcp.html | 52 ++++++---------------------------------------------- 1 file changed, 6 insertions(+), 46 deletions(-) (limited to 'docs') diff --git a/docs/tcp.html b/docs/tcp.html index 4c6c6bc..05bf1df 100644 --- a/docs/tcp.html +++ b/docs/tcp.html @@ -391,58 +391,18 @@ If successful, the method returns the received pattern. In case of error, the method returns nil followed by an error message, followed by a (possibly empty) string containing the partial that was received. The error message can be -the string 'closed' in case the connection was -closed before the transmission was completed, the string -'timeout' in case there was a timeout during the operation, or, +the string 'closed' in case the connection was +closed before the transmission was completed. +'timeout' indicates there was a timeout during the operation. And when maxsize was given, the string 'oversized' in case -the pattern did not complete within maxsize bytes -- in which case -the third return value holds exactly maxsize bytes. +the pattern did not complete within maxsize bytes (in which case +the third return value holds exactly maxsize bytes).

-

-Important note: This function was changed severely. It used -to support multiple patterns (but I have never seen this feature used) and -now it doesn't anymore. Partial results used to be returned in the same -way as successful results. This last feature violated the idea that all -functions should return nil on error. Thus it was changed -too. -

- -

-Note on maxsize: passing a maxsize that is smaller -than 1, a prefix whose length is greater than or equal to -maxsize, or, for a numeric pattern, a byte count greater -than maxsize, all raise a Lua error rather than returning -nil plus a message -- these are caller logic errors, and -they are detected before any byte is read from the socket. To drain and -discard an oversized line while keeping memory bounded and the stream -aligned: -

- -
-local data, err, part
-repeat
-    data, err, part = client:receive("*l", "", 4096)
-until err ~= "oversized"
-
- -

-To instead retry and eventually get the whole thing, carry the partial -forward as prefix and grow maxsize: -

- -
-local data, err, part = client:receive("*l", nil, 4096)
-if err == "oversized" then
-    data, err, part = client:receive("*l", part, 65536)   -- larger cap, or this raises
-end
-
-

Retrying with prefix set to the previous partial result and an unchanged maxsize raises the length-check error above by -design -- otherwise it would be a zero-progress spin: no I/O, no timeout, -no error, just CPU. A timeout partial is always strictly shorter +design. A timeout partial is always strictly shorter than maxsize, so it is always safe to feed straight back as prefix with the same maxsize. Finally, note that maxsize bounds the payload returned, not necessarily the -- cgit v1.2.3-55-g6feb