gh-102494: fix MemoryError when using selectors on Solaris#102495
gh-102494: fix MemoryError when using selectors on Solaris#102495kulikjak wants to merge 7 commits intopython:mainfrom
Conversation
|
For reference, documentation at https://docs.oracle.com/cd/E19253-01/816-5177/6mbbc4g9n/index.html for example. Notice that current array plays two roles:
Instead of allocating a huge array and clap it to a max size, I would suggest to keep a count of active file descriptors (registrations minus unregistrations) and resize the array as needed (maybe only growing it if needed, never shrinking it). I would suggest an initial size of 1024, growing 25% when needed. Shrinking would be nice too, but probably unimportant. (*) Would be quite interesting to investigate the kernel implementation. I guess that playing with a handful of fds would be enough to determine if the kernel gives back the active fds in a round robin, random or "start from the beginning until you fill the buffer" way. Some comments at https://github.com/illumos/illumos-gate/blob/2c76d75129011c98e79463bb84917b828f922a11/usr/src/uts/common/io/devpoll.c#L237 suggest that Solaris kernel gives back the active descriptor in a round robin way, so actually using a small (1024 entries?) buffer would be fine enough. See also code around line 293 and 629. This assumption needs to be tested, but the intention seems quite clear. If this assumption holds true and you want a highly concurrent/performant implementation, you could resize the array when the returned list of active file descriptors use the full array, signaling that a bigger array could reduce syscall traffic, although if you are dealing with Python, this kind of optimizations are probably overkill. PS: I would support a "port" interface, although Solaris is not a tier-1 platform nowadays for Python. |
|
Thank you for the detailed notes; I will look into it. I've also recently hit another issue with the Devpoll selector when working on Cheroot (cherrypy/cheroot#561), so it needs some love. |
|
Any progress? I support @jcea's idea. |
|
Uh, I am sorry - this one's gone missing from my todo list... Thanks for the review and extensive notes. Originally, I was under the assumption (I think - it's quite some time since I filled this) that we need as big of an array as is the number of watched descriptors, but we indeed don't. I played with devpoll a little and the active file descriptors are indeed returned in a round robin way (at least on Oracle Solaris), so we don't need to worry about starvation. I wonder whether we even need to resize the array. The performance would probably be slightly better in cases where We can also provide a new method to resize the buffer - most people would likely be happy with |
|
And as for the port interface, I'll keep that in mind. It certainly doesn't have the highest priority, and thus I don't know when I'll have some time to look into that, but it would be a nice addition. Thanks! |
|
So, I found the root cause of the problem I wrote about above (cherrypy/cheroot#561), and coincidentally it's relevant here. In my testing, I saw It happens when Py_BEGIN_ALLOW_THREADS
errno = 0;
poll_result = ioctl(self->fd_devpoll, DP_POLL, &dvp);
Py_END_ALLOW_THREADSright after Forcing a Because of that, I split the buffer into two - one for polling and one for registering/unregistering. The one for polling now has a limit of The register/unregister buffer has no specific requirements and will work no matter the size (128 seems like a good size ;)). Let me know what you think. Thanks! |
serhiy-storchaka
left a comment
There was a problem hiding this comment.
Can we have any tests? Is this issue reproducible on OpenIndiana?
Devpoll is tested with all the selector tests from If you mean a test for this issue (having one buffer being used by two different operations), I don't know how feasible that is. As for OpenIndiana, I didn't test that, but they are using the same patch as we do: |
|
I just realized that this PR is no longer about just the MemoryError (how it started), but also includes the "two buffers" change, which is pretty unrelated. I wonder whether I should split it into separate issues/PRs? |
When you set
ulimit -n unlimitedon Solaris and its derivatives (where/dev/pollis available) and import selectors, Python crashes with aMemoryErrorbecause there is no upper limit to the allocation size.This fix adds an arbitrary limit of 2^18 (which results in roughly ~4MB of memory).
Fixes #102494