-
-
Notifications
You must be signed in to change notification settings - Fork 858
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Python 3.8 asyncio.Stream where possible #369
Conversation
|
||
def write(self, data: bytes) -> typing.Awaitable[None]: | ||
self.writer.write(data) | ||
return _OptionalAwait(self.drain) |
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.
await
-ing on the drain
call is not exactly the same as how things work in Python 3.8 when you await
on Stream.write()
it first tries a fast-path to flush data and then falls back to a full drain.
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.
Apparently drain() was deprecated in 3.8 too, and it is now recommended to await stream.write()
. Should we switch to this latter API? This means we'd need to refactor AsyncioBackend.write()
a bit, and potentially use a manual buffering mechanism for .write_no_block()
. (There's an example of this in the trio backend: #276.)
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.
I was hoping this could tie into #341 quite nicely maybe?
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.
Yes, actually it does. :)
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.
Just dropping in to say that this is an awesome change, thank you so much! :)
|
||
async def close(self) -> None: | ||
self.stream_writer.close() | ||
# FIXME: We should await on this call, but need a workaround for this first: | ||
# https://github.com/aio-libs/aiohttp/issues/3535 |
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.
With the await
, the test_start_tls_on_socket_stream
test fails transiently with the error mentioned in this thread in about 1 out of 5-30 test runs (thanks, pytest-repeat
) for me on Python 3.7.2, 3.7.4, and 3.8.0b4.
There is a possible fix (a custom exception handler set on the event loop) in the thread, but it's fairly complicated and I'm not sure how to integrate it nicely. This is no worse than we had so I think it can be fixed in a separate PR.
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.
Wow, that thread doesn't look good. 😨
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.
Is it OK to not await this .close()
call, though (since we did await it before)? Will it close itself in the background in pure asyncio magic?
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.
since we did await it before
afaics, we don't/never have await-ed on the close
/wait_closed
?
We should probably await
the close, though. I've looked at this a bit more and read the aiohttp thread a bit more thoroughly and...
- The SSL error occurs sporadically when
await
-ing onStreamWriter.wait_closed()
(under the hood, Python 3.8 (for now) callsStream.wait_closed()
when you awaitStream.close()
) - Python 3.6 doesn't have
wait_closed
so this doesn't apply. - This seems to only apply to OpenSSL 1.1.1+ since that is when this particular type of error was added.
The best I've come up with is something like:
async def close(self) -> None:
try:
await self.stream.close()
except ssl.SSLError as e: # pragma: no cover
if e.reason == "KRB5_S_INIT":
logger.debug("Ignoring asyncio SSL KRB5_S_INIT error on stream close")
return
raise
Thoughts?
I'm not sure exactly what state the socket is in when that error is raised 😕. Once we try to close and get that error there doesn't seem to be anything we can do without just getting that error again.
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.
Thanks, all these details are very helpful. :)
Since the 3.8 docs it’s only possible to await on .close() (which I find kind of weird, having in mind all those « coroutine was never awaited » exceptions that usually causes), I’m okay with keeping it as it is currently. As you said, we actually never waited for the socket to close before (and if we did, we’d have probably only encountered this issue earlier?).
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.
One (not very good) reason to include this try
/except
is that without it I don't think this change has 100% test coverage since wait_closed
is never called.
|
||
def write(self, data: bytes) -> typing.Awaitable[None]: | ||
self.writer.write(data) | ||
return _OptionalAwait(self.drain) |
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.
I was hoping this could tie into #341 quite nicely maybe?
If you're good with this @florimondmanca we can merge? |
Note to myself that I’ll have to deal with the changes in #341 once this get merged. :) |
Crackin work, thank you! ✨ To the team in general - can we peddle just a wee bit slower on getting this one in? We need to unify I'd quite like to approach this, by tackling the Also in cases where we need to have version-specific branches I typically try to isolate those strictly all into a |
@tomchristie The reader/writer pair is very much specific to asyncio — our stream interface which concurrency backends manipulate is already a single interface. As a matter of fact, I don’t think this PR changes any of that. Instead, it basically reimplements asyncio.Stream for < 3.8 so that we can use that to implement the existing Stream interface in the asyncio backend. So is there anything I’m missing about existing reader/writer interfaces? |
Ah okay makes sense - I’m getting a bit back up to speed and probably need to take another look over. |
@JayH5 This looks like it needs some conflict resolution with master before it can land. Mind taking a look? :) |
git didn't have any problem merging master. I guess GitHub must use a simpler merging algorithm 🤷♂ |
Thanks for much for this contribution @JayH5! 🙌 Hope to see more work in our complex async machinery from you. :) |
Not a big problem, but from my POV we've moved too fast on merging this. 🏎🌬 (Even without the change in Python 3.8 I was expecting to take a fuller review onto it, first.) Given #256 (comment) should we raise an issue to just plain revert this, or something else? |
Fixes #256 (although there is still one warning somewhere in
websockets
).On Python < 3.8, this wraps the
asyncio.StreamReader
/asyncio.StreamWriter
objects in aStreamCompat
type with a similar interface to the newasyncio.Stream
. On Python >= 3.8,asyncio.Stream
is used instead.Similarly, on Python < 3.8
loop.open_connection()
is used, but on >=3.8loop.connect()
is used.