100% with you. That's why I wrote it isn't "really" possible. It is
possible, just under limited conditions. IIRC, one could also use the COMET
chunking technique to faciliate this - though that would likely cause some
client side issues. If you're doing a direct browser-to-server connection,
one can somewhat monitor the situation as you described - but most
deployments use proxies, gateways, load balancers, wsgi, etc. It's also
possible the client has an open connection / keepalive into your LAN, but
one of the services on your network timed out and "dropped" the
connection. From the user's vantage, the connection is active; but from
the server's vantage it's dropped.
In any event, my point should have been more clear: "Detecting client
disconnects" isn't a **standard** concept across any web frameworks or
technologies, and this isn't a deficiency of Pyramid. It's possible to
somewhat detect, but it's not something I've ever seen natively supported
in a framework - whether it's Python, Go, C, Java, Ruby, PHP, etc - because
of how applications are often deployed in layered environments.
On Tuesday, March 8, 2022 at 10:28:05 PM UTC-5 Bert JW Regeer wrote:
> I would disagree with this statement, but only narrowly, once you start
> talking about proxies and reverse proxies and things of that nature, it
> becomes much harder because the client isn’t directly connected anymore. In
> most of todays environments you are right that it is really hard to know if
> a remote client went away or if a request was actually successfully
> returned to the remote client (and not just any intermediary
> proxies/servers that may be buffering the request).
>
> It is hard to do in Python based threaded servers because even though the
> code is running in a thread, and the main thread knows about the connection
> being dropped, there’s no way for the main thread to cancel the running of
> the worker thread and notify it. pthread_cancel does not work, and there is
> no good way to signal to the thread to stop running code or to interrupt
> it, especially during heavy computation. HTTP/1.1 also makes this somewhat
> more difficult, in that the only way for the main thread to know is to
> continue telling the kernel it wants to read from the socket the client is
> connected on, with HTTP pipelining the client can send multiple requests at
> once, and we’d have to buffer those requests, otherwise the call to
> select()/poll() becomes a busy loop because each time we call
> select()/poll() the OS would tell us the socket is ready for reading.
> Thankfully HTTP pipelining these days is very rare because of incredibly
> poor support for having multiple requests in flight, while the response
> being returned would close the connection due to an error (the client would
> have to retry any in-flight requests that were pipelined but not replied
> to).
>
> You can emulate it somewhat by checking to see during various points of
> computation whether the client has gone away, and then manually acting upon
> it, and waitress has support for that. It is not enabled by default because
> of the HTTP pipelining issue, and the issue of spinning on select(), but it
> is configurable to attempt to buffer up to X requests by setting the flag
> `channel_request_lookahead` to something that is non-zero.
>
> There’s just no predefined way to do it across WSGI servers, nor does
> pyramid_tm provide any helpers for it since it can’t add those checks for
> you as you are generating your response. This is a waitress extension.
>
> The feature was introduced in this PR:
>
> https://github.com/Pylons/waitress/pull/310
>
> I don’t think there’s good example of how to use it in the documentation,
> but here’s a quick and dirty example:
>
> import logging
> import time
>
> log = logging.getLogger(__name__)
>
>
> def application(environ, start_response):
> check = environ["waitress.client_disconnected"]
>
> for i in range(10):
> # do some computation
> log.debug("Starting computation for %d", i)
> time.sleep(2)
> log.debug("Completed computation for %d", i)
>
> if check():
> log.debug("Remote client went away, processed %d items", i + 1)
> break
>
> start_response(
> "200 OK",
> [
> ("Content-Type", "application/octet-stream"),
> ],
> )
>
> return [b"work completed"]
>
>
> if __name__ == "__main__":
> import waitress
>
> logging.basicConfig(
> format="%(asctime)-15s %(levelname)-8s %(name)s %(message)s",
> level=logging.DEBUG,
> )
>
> waitress.serve(application, channel_request_lookahead=5)
>
> Now start this process, and then run curl but hit Ctrl + C on curl a
> second or two after you start curl:
>
> You should see something like the following:
>
> python client_disconnected.py
> 2022-03-08 20:12:01,081 INFO waitress Serving on http://0.0.0.0:8080
> 2022-03-08 20:12:04,206 DEBUG __main__ Starting computation for 0
> 2022-03-08 20:12:06,211 DEBUG __main__ Completed computation for 0
> 2022-03-08 20:12:06,211