Skip to content
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

Create custom Selectors (Kqueue, Epoll, Poll, and Select) Backport #1001

Merged
merged 76 commits into from Nov 3, 2016

Conversation

sethmlarson
Copy link
Member

This PR adds all the select methods to a new util module called wait. Uses the same ordering used by selectors.py in the standard library to determine which selector should be used. Also factored in a way to allow easy patching and testing. Similar to #998 but uses a different way to break up the different methods to wait for I/O events. Also adds tests to exercise the selectors which can probably be extended.

@Lukasa
Copy link
Sponsor Contributor

Lukasa commented Oct 19, 2016

@SethMichaelLarson Rather than us doing this, would it be better simply to vendor Python 3.5's selectors module? Or, possibly, just have selectors as an optional dependency for using something other than select? I'm a bit inclined to want to take a general solution that someone else built rather than write our own custom code that is more likely to be partial or incomplete.

Thoughts?

@Lukasa Lukasa mentioned this pull request Oct 19, 2016
@sethmlarson
Copy link
Member Author

@Lukasa This is what asyncio does, while possible I think it may be overkill in this situation as a selector has much more overhead compared to a polling object. Asyncio has a different use-case for those selectors to what we're using them for.

@Lukasa
Copy link
Sponsor Contributor

Lukasa commented Oct 19, 2016

@SethMichaelLarson Do we have a good feel on what the overhead is?

@sethmlarson
Copy link
Member Author

sethmlarson commented Oct 19, 2016

@Lukasa Probably minimal but I can take a look if you're interested. I'm +1 on the vendoring selectors suggestion. In the future if we get to doing your suggestion of having IOCP being used for selecting on Windows we'll have to have our own separate module as well but for now this is the best solution.

@Lukasa
Copy link
Sponsor Contributor

Lukasa commented Oct 19, 2016

Heh, if we get to the point of doing IOCP we're going to be in a whole separate world of pain anyway. ;)

@sethmlarson
Copy link
Member Author

So I'll open a separate PR or just keep it in this one?

@Lukasa
Copy link
Sponsor Contributor

Lukasa commented Oct 19, 2016

Let's keep the work here and just retitle the PR a bit.

@sethmlarson
Copy link
Member Author

Sure thing, will re-title when I commit.

@sethmlarson
Copy link
Member Author

sethmlarson commented Oct 19, 2016

So a lot of talk has happened on this issue but took place in issue #1000 and I'm going to summarize this here.

Basically there is no easy option. The selectors standard library module does exactly what we want but is only available in Python 3.5+. The selectors34 module supports Python 2 and Python 3 but does not have the same functionality as selectors for Python 3.5 which is to restart the select in the case of a system interrupt which is the new behavior for system calls specified by PEP 475. We're left with no good options but to make this to be product level code in order to support this functionality. Due to difficulties with getting a CI with Solaris, @Lukasa and I have agreed to drop devpoll support in favor of using epoll on Solaris (devpoll is Solaris only) and kqueue is fine because it may be available on Mac OS. This will require setting up Mac OS on Travis as well.

The basic requirements for this issue are now:

  • Start with selectors as a base.
  • Remove devpoll support.
  • Catch EINTR errors and restart the syscall.
  • Create full test suite for this code.
  • Adding Mac OS to Travis build.
  • Add support for Python 2.6 skip decorators.
  • Add necessary code coverage fixture for multi-platform coverage collection.
  • Sync upstream and test the coverage of selectors.
  • Add better stress tests for the selectors from asyncio/test_selectors.
  • Add tests for checking for leaking fds.
  • Add shiv for socket.socketpair for testing on Windows + Python 3.5<
  • Get reviewed and merged.

Did I miss anything, @Lukasa ?

@sethmlarson sethmlarson changed the title Kqueue, Epoll, Devpoll, Poll, and Select added to Utils Create custom Selectors (Kqueue, Epoll, Poll, and Select) Backport Oct 19, 2016
@Lukasa
Copy link
Sponsor Contributor

Lukasa commented Oct 19, 2016

Nope, looks good. By the time we get to the last bullet we should come to a position on whether we want to use something like coveralls for test coverage across multiple platforms, but let's do one step at a time.

@sethmlarson
Copy link
Member Author

I'd recommend codecov over coveralls, just better functionality and integration with Github. /bikeshed 😉 But honestly, being able for it to comment on PRs is nice.

@Lukasa
Copy link
Sponsor Contributor

Lukasa commented Oct 19, 2016

Let's not put the cart before the horse. ;) Let's get kqueue support first, and then worry about testing it. =D

@sethmlarson
Copy link
Member Author

@Lukasa Agreed!

@sethmlarson
Copy link
Member Author

sethmlarson commented Oct 19, 2016

So I've done a little work and implemented SelectSelector to start us off as it's the simplest. The biggest part of commit 983a193 is the _syscall_wrapper function. This function is made to wrap system calls and to retry them if they fail in the middle of executing due to an EINTR error. Usage of this function is required within all system calls for this module as Python 3.4< does not handle this themselves, instead returning an empty list.

I've decided to keep the wait_for_read and wait_for_write as they're helpful for keeping actual library code minimal but until we have a test suite for selectors it's pointless to test them so I've reverted the test_utils.py to not have their tests. I will put the new test suite into a file called test_selectors.py

From this starting point it will be adding a test suite for all selectors and implementing KqueueSelector, EpollSelector and PollSelector which are all much more syscall intense than SelectSelector.

I'd like a review on this code before progressing further.

Copy link
Sponsor Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok cool, some feedback inline.

if hasattr(time, "monotime"):
monotonic = time.monotonic
else:
monotonic = time.time
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than do if hasattr...else, better to do try...except AttributeError.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, also noticed a mistake using "monotime" instead of "monotonic".

result = None
while expires is None:
current_time = monotonic()
if current_time > expires:
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With timeout=0 this may fail immediately, because expires is calculated based off an earlier call to monotonic(). We should probably allow one run with timeout=0, so you may want to have current_time updated once before this loop begins (and use it to calculate expires), and then update at the bottom of the loop.

Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should note as well that this guarantees that expires is None, and on Python 3 comparing a number to None raises a TypeError.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix this check, the next commit cycle will include tests so I can catch these trivial errors.

expires = monotonic() + timeout

result = None
while expires is None:
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted below, this loop condition cannot possibly be right. Presumably you mean while result is None.

Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, this doesn't allow for the possibility that the syscall may return None. Now, I think that's unlikely, but to be defensive against that possibility let's create a sentinel object that we use instead of None.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, complete mistype, can't believe I missed that one. I'll change that. And yes we can use a sentinel object just in case a syscall returns None.

self.close()


class _BaseSelectorImpl(BaseSelector):
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we don't need the utility of an ABC (and indeed given that BaseSelector is not an ABC in this codebase), do we need this separation between BaseSelector and _BaseSelectorImpl?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering this myself, probably not. I'll merge the two classes. This first revision was mostly to get the basics from selectors down.

if events & EVENT_READ:
self._readers.add(key.fd)
if events & EVENT_WRITE:
self._readers.add(key.fd)
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct.

elif 'EpollSelector' in globals():
DefaultSelector = EpollSelector
elif 'PollSelector' in globals():
DefaultSelector = PollSelector
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously all of this jazz raises NameErrors all over the place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't, this is the same method of doing things that's used in selectors.py. I can change to something else though.

@sethmlarson
Copy link
Member Author

Thanks for the initial review @Lukasa

@sethmlarson
Copy link
Member Author

sethmlarson commented Oct 19, 2016

@Lukasa Bit of information that I gleaned from PEP 475:
"(Note: the selector module already retries on InterruptedError , but it doesn't recompute the timeout yet)" Based on the PEP saying "yet", I assume the selector module should recompute the timeout. Currently with this implementation the same timeout is used on a retry but it would be made to use a timeout based on the initial timeout amount minus how much time was spent selecting before the interrupt. We should probably take a position on this.

I'm in favor of recalculating the timeout time for certain system calls, perhaps have a parameter called recalc_timeout in _syscall_wrapper that will recalculate timeout if it's passed via kwargs or is the last entry in args. Reason being that timeout times will be regular rather than randomly being up to twice as long based on a system interrupt.

Edit: _syscall_wrapper now recalculates the timeout for the syscall if it's told to.

@sethmlarson
Copy link
Member Author

So I added a lot of tests (inspired by asyncio's selectors tests) and I've got coverage up to 94% on Python3.5, the only area that isn't covered by a test is the system call catcher, but that'll get covered by Travis. I've also implemented the PollSelector, so now only EpollSelector and KqueueSelector remain.

Would appreciate another review at this point, especially if all tests pass on Travis.

@sethmlarson
Copy link
Member Author

@Lukasa Should other PRs and issues regarding this be closed now that this is finalized? So far I've found #589, #685, #700, #998, and maybe #681

@Lukasa
Copy link
Sponsor Contributor

Lukasa commented Nov 2, 2016

Well identified. Sadly I have no evidence that #681 will be helped by this PR, so we can't close that one.

@sethmlarson
Copy link
Member Author

@Lukasa Yeah I didn't know if this work would have any effect on that issue, I can try to recreate it and see what happens later.

@sethmlarson
Copy link
Member Author

Will today be the big day? 🎉

@Lukasa
Copy link
Sponsor Contributor

Lukasa commented Nov 3, 2016

Once we can get a green test run, you bet it will!

@sethmlarson sethmlarson closed this Nov 3, 2016
@sethmlarson sethmlarson reopened this Nov 3, 2016
@sethmlarson
Copy link
Member Author

sethmlarson commented Nov 3, 2016

WHY MUST THAT BUTTON BE THERE??? :(

@Lukasa
Copy link
Sponsor Contributor

Lukasa commented Nov 3, 2016

Just to punish you I'm afraid.

@sethmlarson
Copy link
Member Author

Now it's done after my silly mistake. :)

@Lukasa Lukasa merged commit ba92f65 into urllib3:master Nov 3, 2016
@Lukasa
Copy link
Sponsor Contributor

Lukasa commented Nov 3, 2016

Hooray, merged!

Lukasa added a commit to Lukasa/urllib3 that referenced this pull request Nov 4, 2016
Lukasa added a commit that referenced this pull request Nov 4, 2016
@sethmlarson sethmlarson deleted the wait_selectors branch November 7, 2016 22:15
kunteng added a commit to kunteng/requests that referenced this pull request Jan 16, 2017
Version 1.9.1 + merge ba92f65f2cf7ff65eb03ffc42b7ea065da8152ca

Fix urllib3/urllib3#1001
Dobatymo pushed a commit to Dobatymo/urllib3 that referenced this pull request Mar 16, 2022
Create custom Selectors (Kqueue, Epoll, Poll, and Select) Backport
Dobatymo pushed a commit to Dobatymo/urllib3 that referenced this pull request Mar 16, 2022
Dobatymo pushed a commit to Dobatymo/urllib3 that referenced this pull request Mar 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants