_serve_handle wraps its whole body in an untargeted handler:
catch e
e isa InterruptException && rethrow()
return nothing # client closed / malformed: drop it
finally
close(conn)
end
The comment names the case it was written for — a client that hung up. But it catches everything, and the two are indistinguishable afterwards, on the wire and in the log.
Measured
Against a live Pinax.serve on 73f2f77, one raw socket per request, stdout and stderr captured around each:
| request |
bytes received |
server output |
GET /data.bin (control) |
1146 |
— |
GET /empty.bin + Range: bytes=0-0 |
0 |
nothing |
GET /data.bin + Range: bytes=999999999999999999999999-1 |
0 |
nothing |
Both zero-byte rows are real exceptions — a BoundsError from data[1:1] on an empty vector, and an OverflowError from parse(Int, …). Neither is logged anywhere. A client sees exactly what it sees when it disconnects first: a closed socket.
Why this is separate from #108
#108 is the arithmetic: the suffix-range misreading and the empty-file slice. Fixing it removes these two inputs. It does not change what happens to the next exception in this block — a SystemError from a file that is unlinked between isfile and read, an OutOfMemoryError on a large asset, a bug introduced by a later edit. Each of those will also close the connection silently.
The asymmetry inside the same file is the tell: serve prints its URL, warns when the directory has no index.html, and answers a missing path with 404 Not Found: <rel> — it is not a quiet component by design. Only the failure path is quiet.
Fix
Log before discarding, and keep discarding:
catch e
e isa InterruptException && rethrow()
e isa Base.IOError || @error "Pinax.serve: request failed" exception=(e, catch_backtrace()) target=rel
return nothing
finally
close(conn)
end
IOError is the genuine hung-up-client case and stays silent; everything else says so once. The 403 containment branch is worth the same treatment — it currently blocks a request without recording which one.
_serve_handlewraps its whole body in an untargeted handler:The comment names the case it was written for — a client that hung up. But it catches everything, and the two are indistinguishable afterwards, on the wire and in the log.
Measured
Against a live
Pinax.serveon73f2f77, one raw socket per request, stdout and stderr captured around each:GET /data.bin(control)GET /empty.bin+Range: bytes=0-0GET /data.bin+Range: bytes=999999999999999999999999-1Both zero-byte rows are real exceptions — a
BoundsErrorfromdata[1:1]on an empty vector, and anOverflowErrorfromparse(Int, …). Neither is logged anywhere. A client sees exactly what it sees when it disconnects first: a closed socket.Why this is separate from #108
#108 is the arithmetic: the suffix-range misreading and the empty-file slice. Fixing it removes these two inputs. It does not change what happens to the next exception in this block — a
SystemErrorfrom a file that is unlinked betweenisfileandread, anOutOfMemoryErroron a large asset, a bug introduced by a later edit. Each of those will also close the connection silently.The asymmetry inside the same file is the tell:
serveprints its URL, warns when the directory has noindex.html, and answers a missing path with404 Not Found: <rel>— it is not a quiet component by design. Only the failure path is quiet.Fix
Log before discarding, and keep discarding:
IOErroris the genuine hung-up-client case and stays silent; everything else says so once. The 403 containment branch is worth the same treatment — it currently blocks a request without recording which one.