diff options
| author | Thijs Schreijer <thijs@thijsschreijer.nl> | 2026-07-30 19:22:17 +0200 |
|---|---|---|
| committer | Thijs Schreijer <thijs@thijsschreijer.nl> | 2026-07-30 22:06:04 +0200 |
| commit | 73b0780321acb14bab6707006f5136ea9998951d (patch) | |
| tree | 30c5d0cff89913185df7f5fb7ebe1636827cfd58 /docs | |
| parent | e13de2013749961edaa126697f3290d3dca91823 (diff) | |
| download | luasocket-73b0780321acb14bab6707006f5136ea9998951d.tar.gz luasocket-73b0780321acb14bab6707006f5136ea9998951d.tar.bz2 luasocket-73b0780321acb14bab6707006f5136ea9998951d.zip | |
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.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/tcp.html | 58 |
1 files changed, 55 insertions, 3 deletions
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 <b><tt>nil</tt></b> followed by an error message. | |||
| 351 | <!-- receive ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | 351 | <!-- receive ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> |
| 352 | 352 | ||
| 353 | <p class="name" id="receive"> | 353 | <p class="name" id="receive"> |
| 354 | client:<b>receive(</b>[pattern [, prefix]]<b>)</b> | 354 | client:<b>receive(</b>[pattern [, prefix [, maxsize]]]<b>)</b> |
| 355 | </p> | 355 | </p> |
| 356 | 356 | ||
| 357 | <p class="description"> | 357 | <p class="description"> |
| @@ -380,14 +380,23 @@ of bytes from the socket.</li> | |||
| 380 | of any received data before return. | 380 | of any received data before return. |
| 381 | </p> | 381 | </p> |
| 382 | 382 | ||
| 383 | <p class="parameters"> | ||
| 384 | <tt>Maxsize</tt> is an optional positive integer bounding the number of | ||
| 385 | payload bytes the call may accumulate, <em>including</em> <tt>prefix</tt>. | ||
| 386 | Omitted or <tt><b>nil</b></tt> means unlimited. | ||
| 387 | </p> | ||
| 388 | |||
| 383 | <p class="return"> | 389 | <p class="return"> |
| 384 | If successful, the method returns the received pattern. In case of error, | 390 | If successful, the method returns the received pattern. In case of error, |
| 385 | the method returns <tt><b>nil</b></tt> followed by an error | 391 | the method returns <tt><b>nil</b></tt> followed by an error |
| 386 | message, followed by a (possibly empty) string containing | 392 | message, followed by a (possibly empty) string containing |
| 387 | the partial that was received. The error message can be | 393 | the partial that was received. The error message can be |
| 388 | the string '<tt>closed</tt>' in case the connection was | 394 | the string '<tt>closed</tt>' in case the connection was |
| 389 | closed before the transmission was completed or the string | 395 | closed before the transmission was completed, the string |
| 390 | '<tt>timeout</tt>' in case there was a timeout during the operation. | 396 | '<tt>timeout</tt>' in case there was a timeout during the operation, or, |
| 397 | when <tt>maxsize</tt> was given, the string '<tt>oversized</tt>' in case | ||
| 398 | the pattern did not complete within <tt>maxsize</tt> bytes -- in which case | ||
| 399 | the third return value holds exactly <tt>maxsize</tt> bytes. | ||
| 391 | </p> | 400 | </p> |
| 392 | 401 | ||
| 393 | <p class="note"> | 402 | <p class="note"> |
| @@ -399,6 +408,49 @@ functions should return <tt><b>nil</b></tt> on error. Thus it was changed | |||
| 399 | too. | 408 | too. |
| 400 | </p> | 409 | </p> |
| 401 | 410 | ||
| 411 | <p class="note"> | ||
| 412 | <b>Note on <tt>maxsize</tt></b>: passing a <tt>maxsize</tt> that is smaller | ||
| 413 | than 1, a <tt>prefix</tt> whose length is greater than or equal to | ||
| 414 | <tt>maxsize</tt>, or, for a numeric <tt>pattern</tt>, a byte count greater | ||
| 415 | than <tt>maxsize</tt>, all raise a Lua error rather than returning | ||
| 416 | <tt><b>nil</b></tt> plus a message -- these are caller logic errors, and | ||
| 417 | they are detected before any byte is read from the socket. To drain and | ||
| 418 | discard an oversized line while keeping memory bounded and the stream | ||
| 419 | aligned: | ||
| 420 | </p> | ||
| 421 | |||
| 422 | <pre class="example"> | ||
| 423 | local data, err, part | ||
| 424 | repeat | ||
| 425 | data, err, part = client:receive("*l", "", 4096) | ||
| 426 | until err ~= "oversized" | ||
| 427 | </pre> | ||
| 428 | |||
| 429 | <p class="note"> | ||
| 430 | To instead retry and eventually get the whole thing, carry the partial | ||
| 431 | forward as <tt>prefix</tt> and grow <tt>maxsize</tt>: | ||
| 432 | </p> | ||
| 433 | |||
| 434 | <pre class="example"> | ||
| 435 | local data, err, part = client:receive("*l", nil, 4096) | ||
| 436 | if err == "oversized" then | ||
| 437 | data, err, part = client:receive("*l", part, 65536) -- larger cap, or this raises | ||
| 438 | end | ||
| 439 | </pre> | ||
| 440 | |||
| 441 | <p class="note"> | ||
| 442 | Retrying with <tt>prefix</tt> set to the previous partial result and an | ||
| 443 | <em>unchanged</em> <tt>maxsize</tt> raises the length-check error above by | ||
| 444 | design -- otherwise it would be a zero-progress spin: no I/O, no timeout, | ||
| 445 | no error, just CPU. A <tt>timeout</tt> partial is always strictly shorter | ||
| 446 | than <tt>maxsize</tt>, so it is always safe to feed straight back as | ||
| 447 | <tt>prefix</tt> with the same <tt>maxsize</tt>. Finally, note that | ||
| 448 | <tt>maxsize</tt> bounds the payload <em>returned</em>, not necessarily the | ||
| 449 | bytes taken off the wire: for the <tt>*l</tt> pattern the discarded CR | ||
| 450 | characters and the line terminator mean more bytes may have been consumed | ||
| 451 | than the returned length suggests. | ||
| 452 | </p> | ||
| 453 | |||
| 402 | <!-- send +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | 454 | <!-- send +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> |
| 403 | 455 | ||
| 404 | <p class="name" id="send"> | 456 | <p class="name" id="send"> |
