-
-
Notifications
You must be signed in to change notification settings - Fork 22
Allow closing connection pools and responses #4
Conversation
with self.lock: | ||
# Copy pointers to all values, then wipe the mapping | ||
values = list(itervalues(self._container)) | ||
self._container.clear() | ||
|
||
if self.dispose_func: | ||
for value in values: | ||
self.dispose_func(value) | ||
await self.dispose_func(value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I think RecentlyUsedContainer
will need a larger rewrite, because dispose_func
also gets called in other places... and in particular from __setitem__
and __delitem__
, which can't be marked async. Fortunately, it seems to be a private interface, so I guess we can give it a new interface and update the callers. (I'm guessing urllib3 doesn't actually use the full MutableMapping
interface here...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the attribute PoolManager.pools
I guess is nominally public (no underscore). I think our options are to either add an underscore, or else go back and make closing things synchronous after all. It doesn't seem like it's intended to be a public interface, so maybe adding an underscore makes sense, dunno. Since we're still in proof-of-concept mode we should probably pick whichever is easiest for now.
Oh, here's another complication of asynchronous close: it can raise an exception. Trio's convention is that this means that the close still happened, and this is really the only workable convention (see), so we don't have to worry about this causing problems for that particular connection. But the caller still needs to do something sensible with that exception. The tricky thing here is that async def _aclose_all(resources):
if not resources:
return
resource = resources.pop()
try:
await resource.aclose()
finally:
await _aclose_all(resources) the nice thing about this is that it invokes Python's implicit exception chaining, so that if multiple things raise exceptions you get some sensible result. The downside is that because it uses recursion, it's possible to run out of stack, so it's not really suitable when you're potentially dealing with a lot of items. It is possible to do an iterative version of this, but it's really ugly. If we only cared about trio, the We could go back to synchronous def close(self):
s = self._stream
# Unwind any layers of SSLStream (in practice it's either 0 or 1, but why not future-proof things...)
while hasattr(s, "transport_stream"):
s = s.transport_stream
# Now that we have a SocketStream, close the underlying socket
s.socket.close() (See Now that we've run into the issues with closing multiple connections at once and the need to rewrite I'm going to bed now. Maybe things will look different in the morning :-) |
Sounds good to me! Based on your explanations, I believe a synchronous close makes more sense here. Another small argument against the recursive |
There are arguments either way, but the arguments for async close are largely aesthetic, and the arguments against are that it creates some tricky problems with exception handling, and extra churn in urllib3 interfaces. For more details see: https://github.com/njsmith/urllib3/pull/4#issuecomment-354729966
No description provided.