I have found that a poorly behaving client can cause a hang in the
tcl-httpd. This hang prevents any other connections and results from
tcl-httpd getting stuck in a very tight loop. This hang should resolve if
the client closes the connection, but tcl-httpd will not be able to resolve
the problem itself.
This problem occurs when a post is done with something like the following
sequence:
set f [socket $server $port]
puts $f "POST $url HTTP/1.0"
puts $f "Content-length: [string length $body]"
puts $f ""
flush $f
after 200
puts $f [string range $body 0 100]
flush $f
after $hellFreezesOver
puts $f [string range $body 101 end]
flush $f
What happens is that the body is not read using the event loop because the
data is not ready after the headers are read. Instead, reading the body is
deferred. Then when Url_DecodeQuery is called, it attempts to read the rest
of the body by spinning in a loop that looks like this:
while {$Url(postlength) > 0} {
set Url(postlength) [Httpd_GetPostData $sock query]
}
Httpd_GetPostData ultimately causes read on a non-blocking socket. After
reading the first bit of body, this read returns nothing since the client
doesn't send anything more. Thus, tcl-httpd will spin infinitely by calling
read repeatedly with no effect. If the client finally sends the data or
closes the socket, the server will be released. The socket will not,
however, time out since the after cancel for that socket will never be
activated.
Older versions of tcl-httpd didn't have this problem since they read the
body of a transaction greedily in the event loop. If the client never sent
the data, then there would be no read events on that socket and the cancel
would eventually be called, clearing out the mess. This design was deemed
problematic since a page could never send results until they entire body was
received.
Does anyone have any suggestions for the best remedy here?