Subscribing to connection lost / failed transaction event?

6 views
Skip to first unread message

Andrew Free

unread,
Feb 17, 2022, 7:45:32 PMFeb 17
to pylons-discuss
Is there a way to subscribe to any events of a dropped/lost connection?

For example, if the user closed the browser window in the middle of a request. I am using pyramid_tm and having a hard time finding a method for this. I just want to run some code based on the request object state in the event that the response doesn't make it back to the client and the transaction does not complete. I've looked into the exception_view_config and this doesn't appear to help. Would a tween be the best way to handle this?
Thanks. 

Bert JW Regeer

unread,
Feb 17, 2022, 8:35:25 PMFeb 17
to pylons-...@googlegroups.com
No, this is not possible *.

* Except under some very narrow circumstances, but none that are easy to use or directly supported in Pyramid

--
You received this message because you are subscribed to the Google Groups "pylons-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pylons-discus...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pylons-discuss/cab03e28-c370-4dcb-917a-7b5d36e7a86fn%40googlegroups.com.

signature.asc

Jonathan Vanasco

unread,
Mar 7, 2022, 3:05:08 PMMar 7
to pylons-discuss
Just to clarify the above comment, this concept isn't really possible with any internet technology in general, not just Pyramid. 

Bert JW Regeer

unread,
Mar 8, 2022, 10:28:05 PMMar 8
to pylons-...@googlegroups.com
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 DEBUG __main__ Starting computation for 1
2022-03-08 20:12:08,215 DEBUG __main__ Completed computation for 1
2022-03-08 20:12:08,216 DEBUG __main__ Remote client went away, processed 2 items
2022-03-08 20:12:08,217 INFO waitress Client disconnected while serving /

An app developer who knows that the clients are always going to be directly connected, can add code similar to the above in their response code and do these checks manually during their computation, and if they raise an error, pyramid_tm will appropriate abort the transaction, and pyramid should run the exception view machinery (although that response will never make it back to the client, it should be possible to use it at that point to do any extra cleanup or whatnot though)

Hopefully Andrew Free this helps somewhat, in that it is possible, it’s just extra code you have to write and be aware of, it is not something that comes for free, and requires that you use waitress, and it requires that you set the `channel_request_lookhead` flag, and it requires that you know your clients are directly connected.

Caveat emptor.

Thanks,
Bert JW Regeer
> To view this discussion on the web visit https://groups.google.com/d/msgid/pylons-discuss/113341b5-529b-4bb8-b1e8-5a3d28ce028dn%40googlegroups.com.

signature.asc

Jonathan Vanasco

unread,
Mar 15, 2022, 12:05:15 PMMar 15
to pylons-discuss
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.
Reply all
Reply to author
Forward
0 new messages