-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
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
Inefficient ssl.SSLWantReadError exception slows down very common use-case #123954
Comments
How do you get this perf result? I want to learn about this, how to do perf for my code, thanks for your answer. |
I have updated description with instruction on how to run perf |
We cannot really do much with |
After investigation, it appears it would become quite difficult to optimize the exception construction unless we optimize something for exceptions in general. The problem is as follows:
Some questions:
cc @gpshead for another SSL expert. |
I had a couple of thoughts on how to optimize this:
It is calling PyUnicode_FromFormat in order to put file, line number into the error message, which doesn't have much practical benefits especially for SSLWantRead/SSLWantWrite |
No, because the objects should be freshly created. In addition, the exception might come from various places in ssl.c, and the message it contains depends on the underlying libssl (and what needs to be cached has mostly been cached I think).
Unfortunately, we cannot say that anyone would ever read the message. People that want to debug issues may want to read it (especially since it contains SSL-based dedicated messages, with the error's reason and library). This is something we should include in the message. It wouldn't be very nice to ask users to log the SSL error attributes in case of a failure (note that SSL exceptions inherit from OSError and those also include errnos & co, so we probably need to format them in a similar manner).
see above. |
Now, if you want to simplify the error messages for some SSL exceptions, I'd first suggest gathering some support on DPO: https://discuss.python.org/c/ideas/6. Depending on the result, we might proceed like that, but I'm not sure the community that is interested in those messages is actually present on DPO (so we would perhaps be asking those that don't need it) |
It is possible sometimes to predict SSLWantReadError by checking for I proposed such change for uvloop MagicStack/uvloop#629 In my particular case it works very well, my client and server exchange small messages. I have literally 0 SSLWantReadError and those checks are very fast comparing to SSLWantReadError construction. If I replace
|
I think a workaround like that for the common case is the most useful strategy today. (no informed opinion from me on if that is the right logic or not, but the idea is good) Some I do think a fast path for the WantRead/Write errors that raise a pre-created singleton with .library and .reason already set to None on the class is viable. If there is some reason a singleton exception instance can't be used, a specific subclass that skips a lot of attribute setting via pre-set class attributes could perhaps still help. These exceptions are a very specific purpose that should be obvious based on the call they are raised from. There is no practical need for extra information on their instances. We could possibly already avoid the PyObject_SetAttr calls today already by setting those to None on the base class itself and not calling SetAttr when the value would be None. |
ie: this exception creating path was clearly designed with informative error messages in mind, not rapid control flow alternate path logic. |
I converted the code to use the private UnicodeWriter with dedicated pre-allocation (this can be precomputed so that we don't over-allocate strings) and skip What I wanted to however do is change the exception type itself and make it a full-fledged heap type inheriting from I can continue tomorrow investigating this. |
I would be interested to know how much difference this makes on |
Bug report
Bug description:
Event loops like uvloop, asyncio use nonblocking ssl. They typically
when peers are exchanging relatively small messages, SSLObject.read is typically called 2 times . First call returns data, second - throws SSLWantReadError
perf shows that the second call is almost as expensive as the first call because of time spent on constructing new exception object.
Is it possible to optimize exception object creation for the second call?
I tried to avoid the second call by analyzing MemoryBIO.pending and SSLObject.pending values but they can't always reliably tell that we have to wait for more data.
For example, it is possible that incoming MemoryBIO.pending > 0, SSLObject.pending == 0. We call SSLObject.read and it throws because incoming MemoryBIO doesn't have the full ssl frame yet.
Example echo client that replicates internal logic in asyncio/uvloop:
Perf output:
To reproduce you would need some ssl echo server running on localhost 25000 port. After you have started it, run echo client code under perf.
Let it work for 15 seconds and then press Ctrl-C
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
The text was updated successfully, but these errors were encountered: