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
add a Selector to the select module #61057
Comments
Recently, the multiprocessing and telnetlib modules have been patched to use poll() instead of select() when available (mainly for the FD_SETSIZE limitation): This leads to code duplication (just have a look at the commits to convince yourself), and also it highlights the fact that the select module is too low-level: right now, there's no easy way to do portable and efficient I/O multiplexing. What's more, /dev/poll and epoll() support have been added recently, which can make one puzzled by the myriad of available options. Therefore, to reduce code duplication, and make writing portable and efficient code easier, I suggest the addition of a new select.Selector() class, which offers a - hopefully - simple and consistent API.
You'll find a tentative implementation attached, with tests (but no docs). I'm also attaching two patches, one for multiprocessing.connection and the other for telnetlib, to show the before/after. I'd like to have your feedback, especially on those points:
|
I think you have a point. Did you know about the tulip project? http://code.google.com/p/tulip/source/browse/tulip/unix_events.py#76 It has a PollsterBase class and a SelectPollster(PollsterBase) so the idea is to have a Poller(and you call poll()) but select can be used underneath. Since tulip will be merged to stdlib, maybe you can work on tulip itself. Current tulip Pollers don't return (fd, event) but I have a fork that does for Poll, EPoll, KQueue and WindowsPollster: https://bitbucket.org/felipecruz/tulip/ The selector class doesn't have this change (return fd, mask tuple) right now. |
tulip is much bigger than simply a select / poll wrapper. It would probably make more sense for tulip to reuse the abstraction which is proposed here. |
I've had a look, and indeed it has its own Pollster wrapper.
Indeed, I think it would be useful. I'm not sure what's best, accept events masks or have distinct methods |
Oh, I noticed that EpollPollster never closes the underlying epoll FD: |
Hi Antonie, What you said also makes sense to me. There is one problem(?) that _map_events() is called for every event(for Poll and EPoll) and this can make things slower (I didn't tested it). Also, does it needs to be thread-safe? |
Here's a new version closer to Tulip's one. |
Here's a new version adressing Guido's comments (except for kqueue, for which I'll add support later when I can test it). I'm also attaching a benchmark to compare the implementations: as noted by Guido, the complexity of select/poll/epoll are the theoretical ones: in real life, the syscall's cost is probably dwarfed by objects creation, etc. Here's a run, for two ready FDs among 1018: So this can be interesting when a large number of FDs are monitored. For sake of cmpleteness, for a sparse allocation (i.e. just a couple FDS allocated near FD_SETSIZE), there's not much gain: Two points I'm not sure about:
|
Would it make sense to explore this in the Tulip project first? It could be a new module, tulip/selector.py. (Heck, I'll even give you commit privileges in Tulip.) Also, I've heard (but don't know from personal experience) that Jython supports select but it takes only lists of socket objects, not file descriptors (which don't exist in Java). Finally, what about Windows? |
I've posted a review on Rietveld. Not sure the notification e-mail was sent since I got a weird response from the server.
You could add an optional argument to the select() method?
I would say return SELECT_ERR.
The difference is impressive indeed. |
Is this currently being written from scratch? It shouldn't be IMO. https://github.com/facebook/tornado/tree/master/tornado/platform http://code.google.com/p/pyftpdlib/source/browse/trunk/pyftpdlib/ioloop.py?spec=svn1144&r=1135#266 ...and modify the central polling method so that it returns a list of (fd, events) instead of calling handler methods. Exploring this into Tulip would be important IMO, as it would help figuring out whether the API is good enough to support what this component aims to achieve: being a generic poller upon which to build async IO loops. |
Giampaolo, there are many details in the Tornado and pyftpdlib I/O loops that may or may not be intentional. I wrote the Tulip pollster from scratch but am looking to merge in relevant knowledge and features from Tornado, Twisted and pyftpdlib. AFAIK the code here is mostly adapted from Tulip. Please suggest specific features from Tornado or pyftpdlib that are currently missing. However I don't think we should just copy over the existing 3rd party code (if only because we're trying to write pure Python 3.3 code here, without historical baggage). |
Interesting benchmark. There is no gain for epoll with a large number of $ ./python selector_bench.py -r 2 -m 1000 -t pipe
Trying with 2 ready FDs out of 1000, type pipe
<class 'select.EpollSelector'>
0.004317363000154728
<class 'select.PollSelector'>
0.08897221900042496
<class 'select.SelectSelector'>
0.1558117479999055
$ ./python selector_bench.py -r 100 -m 1000 -t pipe
Trying with 100 ready FDs out of 1000, type pipe
<class 'select.EpollSelector'>
0.11067074099992169
<class 'select.PollSelector'>
0.18777027299984184
<class 'select.SelectSelector'>
0.30157805000089866
$ ./python selector_bench.py -r 1000 -m 1000 -t pipe
Trying with 1000 ready FDs out of 1000, type pipe
<class 'select.EpollSelector'>
1.08963231099915
<class 'select.PollSelector'>
1.111169247998987
<class 'select.SelectSelector'>
8.283167362000313 |
Indeed, tornado and pyftpdlib implementations are much more complex,
That's indeed a good idea, there's no need to rush this in. |
Ross, the select() result for a large number of ready FDs was completely skewed because of a bug spotted by Antoine (complexity was much worse than it ought to be). With a large number of FDs, many of which are ready, select can be faster. % time seconds usecs/call calls errors syscall The systime is greater in epoll, but the systime is negligible, as show by 'time': real 0m14.856s The time is really spent in the interpreter. Note that it doesn't change anything when a small number of FDs are ready: $ ./python ~/selector_bench.py -r 10 -m 1000 -t socket
Trying with 10 ready FDs out of 1000, type socket
<class 'select.EpollSelector'>
0.05238822099636309
<class 'select.PollSelector'>
0.25595822899776977
<class 'select.SelectSelector'>
0.5156362060006359
""" |
Ah, that makes a little more sense now. |
Here's a new version, with the following changes:
|
@guido: agreed.
I don't think this is a good idea, particularly during this early stage.
That's true only if SO_OOBINLINE has been pre-emptively set against the socket.
I like this change as it avoids maintaining a separate fd->handler map. Will add line-by-line comments by using the review tool. |
Inline comments: |
POLLERR and POLLNVAL semantics can be really different from Also, that's exactly what the Linux kernel does for select(): This means that "connection lost" could never be returned on platforms But I'm definitely open on this (I've based the behavior on libevent
Thanks, I didn't know: since the POSIX man pages was rather evasive on |
Mmmm... I would be tempted to think the same but the fact that both Tornado and Twisted distinguish between READ and ERROR events is suspicious.
Yes. |
I think that this needs extensive tests that verify the behavior of many end cases, including under duress (e.g. when there are too many connections for the kernel to handle). That would seem the only way to make sure that the code is reliable across platforms. It is likely that you could borrow some ideas for test scenarios from Twisted. |
Will do. I'm adding a new version taking into account some of Giampaolo's remarks. Also, the API now also allows passing a file descriptor or any object To sum up, the API is: def register(self, fileobj, events, data=None):
"""Register a file object.
def unregister(self, fileobj):
"""Unregister a file object.
def modify(self, fileobj, events, data=None):
"""Change a registered file object monitored events or attached data.
def select(self, timeout=None):
"""Perform the actual selection, until some monitored file objects are
ready or a timeout expires.
Selector.select() output looks a lot like poll()/epoll() except for |
The EpollSelector and PollSelector implementations are basically identical. You might want to refactor these to share more code. |
I've noticed a bug present in Tulip, pyftpdlib and tornado (and this Note that the default value used by epoll() is probably too low (we Wait for events. timeout in seconds (float) Which confuses users into thinking that the number of returned events
Yes, but I find the current code much easier to read: epoll() and |
I am trying to use this module in Tulip instead of its pollster implementation, and am running into a problem. Tulip defines separate add_reader()/add_writer() methods, which call to the pollster's register_reader()/register_writer(), respectively. The translation of this to the selector's API is somewhat complex; I would have to keep track of whether I already have a reader or writer registered, and then decide whether to call register() or modify(). If you don't want to change the API back to separate register_*() methods for readers and writers, perhaps you can add a method that tells me, for a given fileobj, whether it is registered, and with which poll flags (SELECT_IN/OUT/both) and the user data? Also, I need a method that gives me the number of registered FDs. |
Does that look OK? def get_info(self, fileobj):
"""Return information about a registered file object.
def registered_count(self):
"""Return the number of registered file objects.
|
I can probably help with that. |
I'm playing with devpoll on Open Solaris and it seems it works exactly as poll(). Basically a copy & paste of PollSelector class with a different self._poll attribute will suffice. |
On Fri, Aug 30, 2013 at 6:34 AM, Charles-François Natali
I disagree. In this case it is almost certain that there is already
Yeah, and Tulip's retry handles this just fine. I also noticed that you are hardcoding a dependency on
But still I agree with Giampaolo that the decorator doesn't feel
Hm, it may be a while before everyone is comfortable with that change. |
2013/8/31 Guido van Rossum <report@bugs.python.org>:
Well, I'll change it, but: All user code that wants to handle EINTR or correct timeout (which And other places in the stdlib either don't handle EINTR properly, or Also, I don't really see the problem with retrying upon EINTR, since:
So in short, I don't see how that could be a nuisance, but I can |
Oh, and FWIW, that's also what Java does: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5100121 (I know some people don't like Java, but I've found that they have |
I may be missing something here but isn't the whole point of EINTR to interrupt a potentially long running syscall? |
Not exactly. The point is to signal (!) that a signal was received. Also, from what I understand, it is more reliable to use a wakeup fd |
What do you mean? Tornado simply retries on EINTR: |
No. One reason why EINTR can occur so often in CPython is because we set |
Doesn't sound so bad to me. It's about the same guarantee as sleep().
No, they'll use a framework with an event loop like Tulip that takes I find it easier to reason about the correctness of the framework's (1) I have to convince myself that the code wrapped by your decorator (2) I have to remember that if a signal handler is called that (3) I have to prove that your decorator uses the same clock as my framework. (4) I have to prove that your code does the same thing if the process
The selector is a helper for higher-level frameworks. I think the |
Here's a patch returning [] on EINTR. I've tested it on Linux, Windows, FreeBSD, OpenSolaris and OS-X. |
Hmm... Shouldn't it simply let the InterruptedError bubble up? |
Hm... I like the [] return just fine, it matches what Tulip's selector did. The framework should be re-checking the clock anyway -- it's possible that select() returned late too... |
New changeset e4d45315c38c by Charles-François Natali in branch 'default': |
Great! Congrats Charles-François for the new module! I tried to |
New changeset 6b14ebe0f7ac by Victor Stinner in branch 'default': |
Agreed, this was a great feat of implementation and API design. Is there anything left to do or can we close this issue? (I looked at the Proactor code in Tulip again and I think it is not quite as easy to isolate it, so I'm not requesting that Proactors be added to the stdlib at this time.) |
It would be nice to mention the new selectors module in the documentation of the select module. |
Feel free to make documentation commits :) They don't necessarily have |
New changeset 142ff216e4b4 by Victor Stinner in branch 'default': |
Ok, done. I also created the issue bpo-18923: "Use the new selectors module in the subprocess module". |
I feel this can be closed then :) Thanks, Charles-François! |
Add support for select.devpoll on Solaris. |
I usually wait until the test is run on the buildbots to check I didn't break anything: apparently that's OK, so we can close.
Indeed (I mentioned the select module in selectors' doc, but the contrary is certainly useful to point users to this new module), thanks for your commit. Where were you when I asked for documentation review ;-) |
I have realized just now that at least epoll() and kqueue() selectors could take advantage and define their own modify() method (there was even a TODO I totally missed). |
As a side note, in the general case, there's more than a performance But since selectors use level-triggered notification, that's not an |
On 05/09/2013 9:28am, Charles-François Natali wrote:
Even with edge-triggered notification, doesn't registering an fd check >>> import os, select
>>> r, w = os.pipe()
>>> p = select.epoll()
>>> os.write(w, b"h")
>>> p.register(r, select.EPOLLIN | select.EPOLLET)
>>> p.poll(0)
[(3, 1)]
>>> p.poll(0)
[] Otherwise it would be very difficult to edge-triggered notification. |
Hum... |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: