This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: tcpserver should document non-threaded long-living connections
Type: enhancement Stage:
Components: Documentation Versions: Python 3.2
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: docs@python Nosy List: docs@python, martin.panter, shevek
Priority: normal Keywords:

Created on 2011-11-06 09:10 by shevek, last changed 2022-04-11 14:57 by admin.

Messages (6)
msg147147 - (view) Author: Bas Wijnen (shevek) Date: 2011-11-06 09:10
http://docs.python.org/py3k/library/socketserver.html says:

The solution is to create a separate process or thread to handle each request; the ForkingMixIn and ThreadingMixIn mix-in classes can be used to support asynchronous behaviour.

There is another way, which doesn't bring multi-threading hell over you: keep a copy of the file descriptor and use it when events occur. I recall that this suggestion used to be in the documentation as well, but I can't find it anymore. It would be good to add this suggestion to the documentation.

However, there is a thing you must know before you can use this approach: tcpserver calls shutdown() on the socket when handle() returns. This means that the network connection is closed. Even dup2() doesn't keep it open (it lets you keep a file descriptor, but it returns EOF). The solution for this is to override shutdown_request of your handler to only call close_request (or not call anything at all) for sockets which must remain open. That way, as long as there is a reference to the socket, the network connection will not be shut down. Optionally the socket can be shutdown() explicitly when you're done with the connection.

Something like the paragraph above would be useful in the documentation IMO.
msg221787 - (view) Author: Mark Lawrence (BreamoreBoy) * Date: 2014-06-28 13:32
Am I barking up the wrong tree, or should the docs now refer to the new asyncio module aka Tulip when mentioning "asynchronous behaviour"?
msg260534 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2016-02-19 22:53
Bas: I think you need to provide more information. Are you talking about protocols like HTTP, where multiple high-level (HTTP-level) requests are made over a single low-level (socket-level) connection? Some example code might help.

Overriding shutdown_request() sounds like a bad idea. I think you eventually would want to truly shutdown the connection. Perhaps it is better to override process_request() and avoid it calling shutdown() until it is ready.
msg260546 - (view) Author: Bas Wijnen (shevek) Date: 2016-02-20 01:49
For example, I have some programs which are HTTP servers that implement WebSockets.  For regular web page requests, it is acceptable if the connection is closed after the page is sent.  But for a WebSocket it isn't: the whole point of that protocol is to allow the server to send unsolicited messages to the browser.  If the connection is closed, it cannot do that.  The documentation currently suggests that the only way to avoid handle() closing the connection is to not return.  That means that the only way to do other things is by using multi-processing or (shiver) multi-threading.

My suggestion is to add a short explanation about how connections can be kept open after handle() returns, so that a single threaded event loop can be used.  Of course the socket must then be manually closed when the program is done with it.

If I understand you correctly, overriding process_request would allow handle() to return without closing the socket.  That does sound better than overriding shutdown_request.

In the current documentation (same link as before, now for version 3.5.1), there is no mention at all about close() or shutdown() of the accepted sockets.  The only information on the subject is that if you want asynchronous handling of data, you must start a new thread or process.  This is not true, and in many cases it is not what I would recommend.  I think event loops are much easier to maintain and debug than multi-process applications, and infinitely easier than multi-threading applications.  I don't mind that other people disagree, and I'm not suggesting that those ways of handling should be removed (multi-process has its place, and I can't convince everyone that multi-threading is evil).  What I'm saying is that the ability to use an event loop to handle asynchronous data with this class deserves to be mentioned as well.

Obviously, it does then need to have the explanation about what to override to make it work.  My suggestion is simply what I ended up with after seeing it fail and reading the code.  If what you describe is the recommended way, please say that instead.  My point is that the scenario should presented as an option, I don't have an opinion on what it should say.
msg260558 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2016-02-20 04:49
Thanks for the fast response!

There is a paragraph right at the end of <https://docs.python.org/3/library/socketserver.html#server-creation-notes> that mentions this technique: “. . . maintain an explicit table of partially finished requests . . .”. Perhaps that is the suggestion that you couldn’t find before.

I once wrote an RTSP proxy that uses a related technique. It still handles each high-level RSTP request or RTP packet synchronously, but uses the “selectors” module to switch between the various connections and listening sockets after each high-level request had been handled. Code at <https://repo.or.cz/python-iview.git/blob/refs/heads/rtsp:/iview/utils.py#l318>.

Do you want to propose some specific additions or a patch to the documentation?
msg260562 - (view) Author: Bas Wijnen (shevek) Date: 2016-02-20 08:48
Thank you for your fast response as well.

I overlooked that paragraph indeed.  It doesn't mention anything about avoiding a socket shutdown however.  Keeping a list of requests isn't very useful if all the sockets in the list are closed. ;-)

So I would indeed suggest an addition: I would change this paragraph:

These four classes process requests synchronously; each request must be completed before the next request can be started. This isn’t suitable if each request takes a long time to complete, because it requires a lot of computation, or because it returns a lot of data which the client is slow to process. The solution is to create a separate process or thread to handle each request; the ForkingMixIn and ThreadingMixIn mix-in classes can be used to support asynchronous behaviour.

into:

By default, these four classes process requests synchronously; each request must be completed before the next request can be started. This isn’t suitable if each request takes a long time to complete, because it requires a lot of computation, or because it returns a lot of data which the client is slow to process, or because the information that should be sent to the client isn't available yet when the request is made. One possible solution is to create a separate process or thread to handle each request; the ForkingMixIn and ThreadingMixIn mix-in classes can be used to support asynchronous behaviour. Another option is to store the socket for later use, and disable the shutting down of the socket by overriding process_request with an function that only calls finish_request, and not shutdown_request. In that case, the socket must be shut down by the program when it is done with it.

At the end of the last paragraph you refer to, it should also be mentioned that the program must do something to prevent the socket from being shut down.  In the description of process_request, it would probably also be useful to add that the default implementation (as opposed to *MixIn) calls shutdown_request() after finish_request().
History
Date User Action Args
2022-04-11 14:57:23adminsetgithub: 57563
2016-02-20 08:48:52sheveksetmessages: + msg260562
2016-02-20 06:24:20BreamoreBoysetnosy: - BreamoreBoy
2016-02-20 04:49:16martin.pantersetmessages: + msg260558
2016-02-20 01:49:31sheveksetstatus: pending -> open

messages: + msg260546
2016-02-19 22:53:46martin.pantersetstatus: open -> pending

messages: + msg260534
2015-01-17 03:50:47martin.pantersetnosy: + martin.panter
2014-06-28 13:32:35BreamoreBoysetnosy: + BreamoreBoy
messages: + msg221787
2011-11-06 09:10:11shevekcreate