{"action":"created","comment":{"url":"https://api.github.com/repos/shazow/urllib3/pulls/comments/85501143","pull_request_review_id":6211575,"id":85501143,"diff_hunk":"@@ -0,0 +1,672 @@\n+from __future__ import with_statement\n+import os\n+import psutil\n+import signal\n+import socket\n+import sys\n+import time\n+import threading\n+\n+try:  # Python 2.6 unittest module doesn't have skip decorators.\n+    from unittest import skip, skipIf, skipUnless\n+    import unittest\n+except ImportError:\n+    from unittest2 import skip, skipIf, skipUnless\n+    import unittest2 as unittest\n+\n+try:  # Python 2.x doesn't define time.perf_counter.\n+    from time import perf_counter as get_time\n+except ImportError:\n+    from time import time as get_time\n+\n+try:  # Python 2.6 doesn't have the resource module.\n+    import resource\n+except ImportError:\n+    resource = None\n+\n+try:  # Windows doesn't support socketpair on Python 3.5<\n+    from socket import socketpair\n+except ImportError:\n+    from .socketpair_helper import socketpair\n+\n+from urllib3.util import selectors\n+\n+LONG_SELECT = 0.2\n+SHORT_SELECT = 0.01\n+\n+# Tolerance values for CI timer/speed fluctuations.\n+LOWER_TOLERANCE = 0.05\n+UPPER_TOLERANCE = 0.05\n+\n+# Travis CI detection, sometimes Travis is extremely slow.\n+if \"TRAVIS\" in os.environ:\n+    UPPER_TOLERANCE = 0.55\n+\n+\n+class AlarmThread(threading.Thread):\n+    def __init__(self, timeout):\n+        super(AlarmThread, self).__init__(group=None)\n+        self.setDaemon(True)\n+        self.timeout = timeout\n+        self.canceled = False\n+\n+    def cancel(self):\n+        self.canceled = True\n+\n+    def run(self):\n+        time.sleep(self.timeout)\n+        if not self.canceled:\n+            os.kill(os.getpid(), signal.SIGALRM)\n+\n+\n+class AlarmMixin(object):\n+    def set_alarm(self, timeout):\n+        if not hasattr(signal, \"SIGALRM\"):\n+            self.skipTest(\"Platform doesn't have signal.SIGALRM\")\n+        if not hasattr(self, \"alarm_thread\"):\n+            self.alarm_thread = None\n+        if self.alarm_thread is None:\n+            self.addCleanup(self.cancel_alarm)\n+\n+        self.alarm_thread = AlarmThread(timeout)\n+        self.alarm_thread.start()\n+\n+    def cancel_alarm(self):\n+        if getattr(self, \"alarm_thread\", None) is not None:\n+            self.alarm_thread.cancel()\n+            self.alarm_thread.join(0.0)\n+        self.alarm_thread = None\n+\n+    def make_alarm(self, handler, duration):\n+        sigalrm_handler = signal.signal(signal.SIGALRM, handler)\n+        self.addCleanup(signal.signal, signal.SIGALRM, sigalrm_handler)\n+        self.set_alarm(duration)\n+\n+\n+class TimerContext(object):\n+    def __init__(self, testcase, lower=None, upper=None ):\n+        self.testcase = testcase\n+        self.lower = lower\n+        self.upper = upper\n+        self.start_time = None\n+        self.end_time = None\n+\n+    def __enter__(self):\n+        self.start_time = get_time()\n+\n+    def __exit__(self, *args):\n+        self.end_time = get_time()\n+        total_time = self.end_time - self.start_time\n+        if self.lower is not None:\n+            self.testcase.assertGreaterEqual(total_time, self.lower * (1.0 - LOWER_TOLERANCE))\n+        if self.upper is not None:\n+            self.testcase.assertLessEqual(total_time, self.upper * (1.0 + UPPER_TOLERANCE))\n+\n+\n+class TimerMixin(object):\n+    def assertTakesTime(self, lower=None, upper=None):\n+        return TimerContext(self, lower=lower, upper=upper)\n+\n+\n+@skipUnless(selectors.HAS_SELECT, \"Platform doesn't have a selector\")\n+class WaitForIOTest(unittest.TestCase, AlarmMixin, TimerMixin):\n+    \"\"\" Tests for the higher level wait_for_* functions. \"\"\"\n+    TOLERANCE = 0.05\n+\n+    def make_socketpair(self):\n+        rd, wr = socketpair()\n+\n+        rd.settimeout(0.0)\n+        wr.settimeout(0.0)\n+\n+        self.addCleanup(rd.close)\n+        self.addCleanup(wr.close)\n+        return rd, wr\n+\n+    def test_selector_error(self):\n+        err = selectors.SelectorError(1)\n+        self.assertEqual(err.__repr__(), \"<SelectorError errno=1>\")\n+        self.assertEqual(err.__str__(), \"<SelectorError errno=1>\")\n+\n+    def test_wait_for_read_single_socket(self):\n+        rd, wr = self.make_socketpair()\n+        self.assertEqual([], selectors.wait_for_read(rd, timeout=0.001))\n+\n+    def test_wait_for_read_multiple_socket(self):\n+        rd, rd2 = self.make_socketpair()\n+        self.assertEqual([], selectors.wait_for_read([rd, rd2], timeout=0.001))\n+\n+    def test_wait_for_read_empty(self):\n+        self.assertEqual([], selectors.wait_for_read([], timeout=0.001))\n+\n+    def test_wait_for_write_single_socket(self):\n+        wr, wr2 = self.make_socketpair()\n+        self.assertEqual([wr], selectors.wait_for_write(wr, timeout=0.001))\n+\n+    def test_wait_for_write_multiple_socket(self):\n+        wr, wr2 = self.make_socketpair()\n+        result = selectors.wait_for_write([wr, wr2], timeout=0.001)\n+        # assertItemsEqual renamed in Python 3.x\n+        if hasattr(self, \"assertItemsEqual\"):\n+            self.assertItemsEqual([wr, wr2], result)\n+        else:\n+            self.assertCountEqual([wr, wr2], result)\n+\n+    def test_wait_for_write_empty(self):\n+        self.assertEqual([], selectors.wait_for_write([], timeout=0.001))\n+\n+    def test_wait_for_non_list_iterable(self):\n+        rd, wr = self.make_socketpair()\n+        iterable = {'rd': rd}.values()\n+        self.assertEqual([], selectors.wait_for_read(iterable, timeout=0.001))\n+\n+    def test_wait_timeout(self):\n+        rd, wr = self.make_socketpair()\n+        with self.assertTakesTime(lower=0.0, upper=0.1):\n+            selectors.wait_for_read([rd], timeout=0.01)\n+\n+    @skipUnless(hasattr(signal, \"alarm\"), \"Platform doesn't have signal.alarm()\")\n+    def test_interrupt_wait_for_read_no_event(self):\n+        rd, wr = self.make_socketpair()\n+\n+        self.make_alarm(lambda *args: None, SHORT_SELECT)\n+\n+        with self.assertTakesTime(lower=LONG_SELECT, upper=LONG_SELECT):\n+            self.assertEqual([], selectors.wait_for_read(rd, timeout=LONG_SELECT))\n+\n+    @skipUnless(hasattr(signal, \"alarm\"), \"Platform doesn't have signal.alarm()\")\n+    def test_interrupt_wait_for_read_with_event(self):\n+        rd, wr = self.make_socketpair()\n+\n+        self.make_alarm(lambda *args: wr.send(b'x'), SHORT_SELECT)\n+\n+        with self.assertTakesTime(lower=SHORT_SELECT, upper=LONG_SELECT):\n+            self.assertEqual([rd], selectors.wait_for_read(rd, timeout=LONG_SELECT))\n+        self.assertEqual(rd.recv(1), b'x')\n+\n+\n+@skipUnless(selectors.HAS_SELECT, \"Platform doesn't have a selector\")\n+class BaseSelectorTestCase(unittest.TestCase, AlarmMixin, TimerMixin):\n+    \"\"\" Implements the tests that each type of selector must pass. \"\"\"\n+    SELECTOR = selectors.DefaultSelector\n+    TOLERANCE = 0.05\n+\n+    def make_socketpair(self):\n+        rd, wr = socket.socketpair()\n+\n+        # Make non-blocking so we get errors if the\n+        # sockets are interacted with but not ready.\n+        rd.settimeout(0.0)\n+        wr.settimeout(0.0)\n+\n+        self.addCleanup(rd.close)\n+        self.addCleanup(wr.close)\n+        return rd, wr\n+\n+    def make_selector(self):\n+        s = self.SELECTOR()\n+        self.addCleanup(s.close)\n+        return s\n+\n+    def standard_setup(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        s.register(rd, selectors.EVENT_READ)\n+        s.register(wr, selectors.EVENT_WRITE)\n+        return s, rd, wr\n+\n+    def test_get_key(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+\n+        key = s.register(rd, selectors.EVENT_READ, \"data\")\n+        self.assertEqual(key, s.get_key(rd))\n+\n+        # Unknown fileobj\n+        self.assertRaises(KeyError, s.get_key, 999999)\n+\n+    def test_get_map(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+\n+        keys = s.get_map()\n+        self.assertFalse(keys)\n+        self.assertEqual(len(keys), 0)\n+        self.assertEqual(list(keys), [])\n+        key = s.register(rd, selectors.EVENT_READ, \"data\")\n+        self.assertIn(rd, keys)\n+        self.assertEqual(key, keys[rd])\n+        self.assertEqual(len(keys), 1)\n+        self.assertEqual(list(keys), [rd.fileno()])\n+        self.assertEqual(list(keys.values()), [key])\n+\n+        # Unknown fileobj\n+        self.assertRaises(KeyError, keys.__getitem__, 999999)\n+\n+        # Read-only mapping\n+        with self.assertRaises(TypeError):\n+            del keys[rd]\n+\n+    def test_register(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+\n+        # Ensure that the file is not yet added.\n+        self.assertEqual(0, len(s.get_map()))\n+        self.assertRaises(KeyError, lambda: s.get_map()[rd.fileno()])\n+        self.assertRaises(KeyError, s.get_key, rd)\n+        self.assertEqual(None, s._key_from_fd(rd.fileno()))\n+\n+        data = object()\n+        key = s.register(rd, selectors.EVENT_READ, data)\n+        self.assertIsInstance(key, selectors.SelectorKey)\n+        self.assertEqual(key.fileobj, rd)\n+        self.assertEqual(key.fd, rd.fileno())\n+        self.assertEqual(key.events, selectors.EVENT_READ)\n+        self.assertIs(key.data, data)\n+        self.assertEqual(1, len(s.get_map()))\n+        for fd in s.get_map():\n+            self.assertEqual(fd, rd.fileno())\n+\n+    def test_register_bad_event(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+\n+        self.assertRaises(ValueError, s.register, rd, 99999)\n+\n+    def test_register_negative_fd(self):\n+        s = self.make_selector()\n+        self.assertRaises(ValueError, s.register, -1, selectors.EVENT_READ)\n+\n+    def test_register_invalid_fileobj(self):\n+        s = self.make_selector()\n+        self.assertRaises(ValueError, s.register, \"string\", selectors.EVENT_READ)\n+\n+    def test_reregister_fd_same_fileobj(self):\n+        s, rd, wr = self.standard_setup()\n+        self.assertRaises(KeyError, s.register, rd, selectors.EVENT_READ)\n+\n+    def test_reregister_fd_different_fileobj(self):\n+        s, rd, wr = self.standard_setup()\n+        self.assertRaises(KeyError, s.register, rd.fileno(), selectors.EVENT_READ)\n+\n+    def test_context_manager(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+\n+        with s as sel:\n+            rd_key = sel.register(rd, selectors.EVENT_READ)\n+            wr_key = sel.register(wr, selectors.EVENT_WRITE)\n+            self.assertEqual(rd_key, sel.get_key(rd))\n+            self.assertEqual(wr_key, sel.get_key(wr))\n+\n+        self.assertRaises(RuntimeError, s.get_key, rd)\n+        self.assertRaises(RuntimeError, s.get_key, wr)\n+\n+    def test_unregister(self):\n+        s, rd, wr = self.standard_setup()\n+        s.unregister(rd)\n+\n+        self.assertRaises(KeyError, s.unregister, 99999)\n+\n+    def test_reunregister(self):\n+        s, rd, wr = self.standard_setup()\n+        s.unregister(rd)\n+\n+        self.assertRaises(KeyError, s.unregister, rd)\n+\n+    def test_unregister_after_fd_close(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        rdfd = rd.fileno()\n+        wrfd = wr.fileno()\n+        s.register(rdfd, selectors.EVENT_READ)\n+        s.register(wrfd, selectors.EVENT_WRITE)\n+\n+        rd.close()\n+        wr.close()\n+\n+        s.unregister(rdfd)\n+        s.unregister(wrfd)\n+\n+        self.assertEqual(0, len(s.get_map()))\n+\n+    def test_unregister_after_fileobj_close(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        s.register(rd, selectors.EVENT_READ)\n+        s.register(wr, selectors.EVENT_WRITE)\n+\n+        rd.close()\n+        wr.close()\n+\n+        s.unregister(rd)\n+        s.unregister(wr)\n+\n+        self.assertEqual(0, len(s.get_map()))\n+\n+    @skipUnless(os.name == \"posix\", \"Platform doesn't support os.dup2\")\n+    def test_unregister_after_reuse_fd(self):\n+        s, rd, wr = self.standard_setup()\n+        rdfd = rd.fileno()\n+        wrfd = wr.fileno()\n+\n+        rd2, wr2 = self.make_socketpair()\n+        rd.close()\n+        wr.close()\n+        os.dup2(rd2.fileno(), rdfd)\n+        os.dup2(wr2.fileno(), wrfd)\n+\n+        s.unregister(rdfd)\n+        s.unregister(wrfd)\n+\n+        self.assertEqual(0, len(s.get_map()))\n+\n+    def test_modify(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+\n+        key = s.register(rd, selectors.EVENT_READ)\n+\n+        # Modify events\n+        key2 = s.modify(rd, selectors.EVENT_WRITE)\n+        self.assertNotEqual(key.events, key2.events)\n+        self.assertEqual(key2, s.get_key(rd))\n+\n+        s.unregister(rd)\n+\n+        # Modify data\n+        d1 = object()\n+        d2 = object()\n+\n+        key = s.register(rd, selectors.EVENT_READ, d1)\n+        key2 = s.modify(rd, selectors.EVENT_READ, d2)\n+        self.assertEqual(key.events, key2.events)\n+        self.assertIsNot(key.data, key2.data)\n+        self.assertEqual(key2, s.get_key(rd))\n+        self.assertIs(key2.data, d2)\n+\n+        # Modify invalid fileobj\n+        self.assertRaises(KeyError, s.modify, 999999, selectors.EVENT_READ)\n+\n+    def test_empty_select(self):\n+        s = self.make_selector()\n+        self.assertEqual([], s.select(timeout=SHORT_SELECT))\n+\n+    def test_select_multiple_event_types(self):\n+        s = self.make_selector()\n+\n+        rd, wr = self.make_socketpair()\n+        key = s.register(rd, selectors.EVENT_READ | selectors.EVENT_WRITE)\n+\n+        self.assertEqual([(key, selectors.EVENT_WRITE)], s.select(0.001))\n+\n+        wr.send(b'x')\n+        time.sleep(0.01)  # Wait for the write to flush.\n+\n+        self.assertEqual([(key, selectors.EVENT_READ | selectors.EVENT_WRITE)], s.select(0.001))\n+\n+    def test_select_multiple_selectors(self):\n+        s1 = self.make_selector()\n+        s2 = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        key1 = s1.register(rd, selectors.EVENT_READ)\n+        key2 = s2.register(rd, selectors.EVENT_READ)\n+\n+        wr.send(b'x')\n+        time.sleep(0.01)  # Wait for the write to flush.\n+\n+        self.assertEqual([(key1, selectors.EVENT_READ)], s1.select(timeout=0.001))\n+        self.assertEqual([(key2, selectors.EVENT_READ)], s2.select(timeout=0.001))\n+\n+    def test_select_no_event_types(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        self.assertRaises(ValueError, s.register, rd, 0)\n+\n+    def test_select_many_events(self):\n+        s = self.make_selector()\n+        readers = []\n+        writers = []\n+        for i in range(32):\n+            rd, wr = self.make_socketpair()\n+            readers.append(rd)\n+            writers.append(wr)\n+            s.register(rd, selectors.EVENT_READ)\n+\n+        self.assertEqual(0, len(s.select(0.001)))\n+\n+        # Write a byte to each end.\n+        for wr in writers:\n+            wr.send(b'x')\n+\n+        # Give time to flush the writes.\n+        time.sleep(0.01)\n+\n+        ready = s.select(0.001)\n+        self.assertEqual(32, len(ready))\n+        for key, events in ready:\n+            self.assertEqual(selectors.EVENT_READ, events)\n+            self.assertIn(key.fileobj, readers)\n+\n+        # Now read the byte from each endpoint.\n+        for rd in readers:\n+            data = rd.recv(1)\n+            self.assertEqual(b'x', data)\n+\n+        self.assertEqual(0, len(s.select(0.001)))\n+\n+    def test_select_timeout_none(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        s.register(wr, selectors.EVENT_WRITE)\n+\n+        with self.assertTakesTime(upper=SHORT_SELECT):\n+            self.assertEqual(1, len(s.select(timeout=None)))\n+            self.assertEqual(1, len(s.select(timeout=None)))\n+            self.assertEqual(1, len(s.select(timeout=None)))\n+\n+    def test_select_timeout_ready(self):\n+        s, rd, wr = self.standard_setup()\n+\n+        with self.assertTakesTime(upper=SHORT_SELECT):\n+            self.assertEqual(1, len(s.select(timeout=0)))\n+            self.assertEqual(1, len(s.select(timeout=-1)))\n+            self.assertEqual(1, len(s.select(timeout=0.001)))\n+\n+    def test_select_timeout_not_ready(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        s.register(rd, selectors.EVENT_READ)\n+\n+        with self.assertTakesTime(upper=SHORT_SELECT):\n+            self.assertEqual(0, len(s.select(timeout=0)))\n+\n+        with self.assertTakesTime(lower=SHORT_SELECT):\n+            self.assertEqual(0, len(s.select(timeout=SHORT_SELECT)))\n+\n+    @skipUnless(hasattr(signal, \"alarm\"), \"Platform doesn't have signal.alarm()\")\n+    def test_select_timing(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        key = s.register(rd, selectors.EVENT_READ)\n+\n+        sigalrm_handler = signal.signal(signal.SIGALRM, lambda *args: wr.send(b'x'))\n+        self.addCleanup(signal.signal, signal.SIGALRM, sigalrm_handler)\n+\n+        # Start the timer for the interrupt.\n+        self.set_alarm(SHORT_SELECT)\n+\n+        with self.assertTakesTime(upper=LONG_SELECT):\n+            ready = s.select(LONG_SELECT)\n+        self.assertEqual([(key, selectors.EVENT_READ)], ready)\n+\n+    @skipUnless(hasattr(signal, \"alarm\"), \"Platform doesn't have signal.alarm()\")\n+    def test_select_interrupt_no_event(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        s.register(rd, selectors.EVENT_READ)\n+\n+        self.make_alarm(lambda *args: None, SHORT_SELECT)\n+\n+        # The x1.1 is because SelectSelector is really slow on CI.\n+        with self.assertTakesTime(lower=LONG_SELECT, upper=LONG_SELECT * 1.1):\n+            self.assertEqual([], s.select(LONG_SELECT))\n+\n+    @skipUnless(hasattr(signal, \"alarm\"), \"Platform doesn't have signal.alarm()\")\n+    def test_select_interrupt_with_event(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        s.register(rd, selectors.EVENT_READ)\n+        key = s.get_key(rd)\n+\n+        self.make_alarm(lambda *args: wr.send(b'x'), SHORT_SELECT)\n+\n+        with self.assertTakesTime(lower=SHORT_SELECT, upper=SHORT_SELECT):\n+            self.assertEqual([(key, selectors.EVENT_READ)], s.select(LONG_SELECT))\n+        self.assertEqual(rd.recv(1), b'x')\n+\n+    @skipUnless(hasattr(signal, \"alarm\"), \"Platform doesn't have signal.alarm()\")\n+    def test_select_multiple_interrupts_with_event(self):\n+        s = self.make_selector()\n+        rd, wr = self.make_socketpair()\n+        s.register(rd, selectors.EVENT_READ)\n+        key = s.get_key(rd)\n+\n+        def second_alarm(*args):\n+            wr.send(b'x')\n+\n+        def first_alarm(*args):\n+            self.set_alarm(SHORT_SELECT)\n+            signal.signal(signal.SIGALRM, second_alarm)\n+\n+        self.make_alarm(first_alarm, SHORT_SELECT)\n+\n+        with self.assertTakesTime(lower=SHORT_SELECT * 2, upper=SHORT_SELECT * 4):","path":"test/test_selectors.py","position":545,"original_position":545,"commit_id":"3b3582e2341478d1cbd3b48a36c172bc87c3b0bb","original_commit_id":"3b3582e2341478d1cbd3b48a36c172bc87c3b0bb","user":{"login":"Lukasa","id":1382556,"avatar_url":"https://avatars.githubusercontent.com/u/1382556?v=3","gravatar_id":"","url":"https://api.github.com/users/Lukasa","html_url":"https://github.com/Lukasa","followers_url":"https://api.github.com/users/Lukasa/followers","following_url":"https://api.github.com/users/Lukasa/following{/other_user}","gists_url":"https://api.github.com/users/Lukasa/gists{/gist_id}","starred_url":"https://api.github.com/users/Lukasa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Lukasa/subscriptions","organizations_url":"https://api.github.com/users/Lukasa/orgs","repos_url":"https://api.github.com/users/Lukasa/repos","events_url":"https://api.github.com/users/Lukasa/events{/privacy}","received_events_url":"https://api.github.com/users/Lukasa/received_events","type":"User","site_admin":false},"body":"Really? Is `*4` really needed?","created_at":"2016-10-28T09:45:26Z","updated_at":"2016-10-28T09:50:34Z","html_url":"https://github.com/shazow/urllib3/pull/1001#discussion_r85501143","pull_request_url":"https://api.github.com/repos/shazow/urllib3/pulls/1001","_links":{"self":{"href":"https://api.github.com/repos/shazow/urllib3/pulls/comments/85501143"},"html":{"href":"https://github.com/shazow/urllib3/pull/1001#discussion_r85501143"},"pull_request":{"href":"https://api.github.com/repos/shazow/urllib3/pulls/1001"}}},"pull_request":{"url":"https://api.github.com/repos/shazow/urllib3/pulls/1001","id":89934401,"html_url":"https://github.com/shazow/urllib3/pull/1001","diff_url":"https://github.com/shazow/urllib3/pull/1001.diff","patch_url":"https://github.com/shazow/urllib3/pull/1001.patch","issue_url":"https://api.github.com/repos/shazow/urllib3/issues/1001","number":1001,"state":"open","locked":false,"title":"Create custom Selectors (Kqueue, Epoll, Poll, and Select) Backport","user":{"login":"SethMichaelLarson","id":18519037,"avatar_url":"https://avatars.githubusercontent.com/u/18519037?v=3","gravatar_id":"","url":"https://api.github.com/users/SethMichaelLarson","html_url":"https://github.com/SethMichaelLarson","followers_url":"https://api.github.com/users/SethMichaelLarson/followers","following_url":"https://api.github.com/users/SethMichaelLarson/following{/other_user}","gists_url":"https://api.github.com/users/SethMichaelLarson/gists{/gist_id}","starred_url":"https://api.github.com/users/SethMichaelLarson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SethMichaelLarson/subscriptions","organizations_url":"https://api.github.com/users/SethMichaelLarson/orgs","repos_url":"https://api.github.com/users/SethMichaelLarson/repos","events_url":"https://api.github.com/users/SethMichaelLarson/events{/privacy}","received_events_url":"https://api.github.com/users/SethMichaelLarson/received_events","type":"User","site_admin":false},"body":"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.","created_at":"2016-10-19T03:01:50Z","updated_at":"2016-10-28T09:50:34Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6a5276fc156cc87b12ac316a445361c1cb46123d","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/shazow/urllib3/pulls/1001/commits","review_comments_url":"https://api.github.com/repos/shazow/urllib3/pulls/1001/comments","review_comment_url":"https://api.github.com/repos/shazow/urllib3/pulls/comments{/number}","comments_url":"https://api.github.com/repos/shazow/urllib3/issues/1001/comments","statuses_url":"https://api.github.com/repos/shazow/urllib3/statuses/3b3582e2341478d1cbd3b48a36c172bc87c3b0bb","head":{"label":"SethMichaelLarson:wait_selectors","ref":"wait_selectors","sha":"3b3582e2341478d1cbd3b48a36c172bc87c3b0bb","user":{"login":"SethMichaelLarson","id":18519037,"avatar_url":"https://avatars.githubusercontent.com/u/18519037?v=3","gravatar_id":"","url":"https://api.github.com/users/SethMichaelLarson","html_url":"https://github.com/SethMichaelLarson","followers_url":"https://api.github.com/users/SethMichaelLarson/followers","following_url":"https://api.github.com/users/SethMichaelLarson/following{/other_user}","gists_url":"https://api.github.com/users/SethMichaelLarson/gists{/gist_id}","starred_url":"https://api.github.com/users/SethMichaelLarson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SethMichaelLarson/subscriptions","organizations_url":"https://api.github.com/users/SethMichaelLarson/orgs","repos_url":"https://api.github.com/users/SethMichaelLarson/repos","events_url":"https://api.github.com/users/SethMichaelLarson/events{/privacy}","received_events_url":"https://api.github.com/users/SethMichaelLarson/received_events","type":"User","site_admin":false},"repo":{"id":71281542,"name":"urllib3","full_name":"SethMichaelLarson/urllib3","owner":{"login":"SethMichaelLarson","id":18519037,"avatar_url":"https://avatars.githubusercontent.com/u/18519037?v=3","gravatar_id":"","url":"https://api.github.com/users/SethMichaelLarson","html_url":"https://github.com/SethMichaelLarson","followers_url":"https://api.github.com/users/SethMichaelLarson/followers","following_url":"https://api.github.com/users/SethMichaelLarson/following{/other_user}","gists_url":"https://api.github.com/users/SethMichaelLarson/gists{/gist_id}","starred_url":"https://api.github.com/users/SethMichaelLarson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SethMichaelLarson/subscriptions","organizations_url":"https://api.github.com/users/SethMichaelLarson/orgs","repos_url":"https://api.github.com/users/SethMichaelLarson/repos","events_url":"https://api.github.com/users/SethMichaelLarson/events{/privacy}","received_events_url":"https://api.github.com/users/SethMichaelLarson/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/SethMichaelLarson/urllib3","description":"Python HTTP library with thread-safe connection pooling, file post support, sanity friendly, and more.","fork":true,"url":"https://api.github.com/repos/SethMichaelLarson/urllib3","forks_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/forks","keys_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/keys{/key_id}","collaborators_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/teams","hooks_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/hooks","issue_events_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/issues/events{/number}","events_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/events","assignees_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/assignees{/user}","branches_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/branches{/branch}","tags_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/tags","blobs_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/git/refs{/sha}","trees_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/git/trees{/sha}","statuses_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/statuses/{sha}","languages_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/languages","stargazers_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/stargazers","contributors_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/contributors","subscribers_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/subscribers","subscription_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/subscription","commits_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/commits{/sha}","git_commits_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/git/commits{/sha}","comments_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/comments{/number}","issue_comment_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/issues/comments{/number}","contents_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/contents/{+path}","compare_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/compare/{base}...{head}","merges_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/merges","archive_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/downloads","issues_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/issues{/number}","pulls_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/pulls{/number}","milestones_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/milestones{/number}","notifications_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/labels{/name}","releases_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/releases{/id}","deployments_url":"https://api.github.com/repos/SethMichaelLarson/urllib3/deployments","created_at":"2016-10-18T18:59:43Z","updated_at":"2016-10-18T18:59:44Z","pushed_at":"2016-10-27T18:35:54Z","git_url":"git://github.com/SethMichaelLarson/urllib3.git","ssh_url":"git@github.com:SethMichaelLarson/urllib3.git","clone_url":"https://github.com/SethMichaelLarson/urllib3.git","svn_url":"https://github.com/SethMichaelLarson/urllib3","homepage":"https://urllib3.readthedocs.io/","size":3046,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"shazow:master","ref":"master","sha":"056045a062478e3c3afc2402ec81680d53490876","user":{"login":"shazow","id":6292,"avatar_url":"https://avatars.githubusercontent.com/u/6292?v=3","gravatar_id":"","url":"https://api.github.com/users/shazow","html_url":"https://github.com/shazow","followers_url":"https://api.github.com/users/shazow/followers","following_url":"https://api.github.com/users/shazow/following{/other_user}","gists_url":"https://api.github.com/users/shazow/gists{/gist_id}","starred_url":"https://api.github.com/users/shazow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shazow/subscriptions","organizations_url":"https://api.github.com/users/shazow/orgs","repos_url":"https://api.github.com/users/shazow/repos","events_url":"https://api.github.com/users/shazow/events{/privacy}","received_events_url":"https://api.github.com/users/shazow/received_events","type":"User","site_admin":false},"repo":{"id":2410676,"name":"urllib3","full_name":"shazow/urllib3","owner":{"login":"shazow","id":6292,"avatar_url":"https://avatars.githubusercontent.com/u/6292?v=3","gravatar_id":"","url":"https://api.github.com/users/shazow","html_url":"https://github.com/shazow","followers_url":"https://api.github.com/users/shazow/followers","following_url":"https://api.github.com/users/shazow/following{/other_user}","gists_url":"https://api.github.com/users/shazow/gists{/gist_id}","starred_url":"https://api.github.com/users/shazow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shazow/subscriptions","organizations_url":"https://api.github.com/users/shazow/orgs","repos_url":"https://api.github.com/users/shazow/repos","events_url":"https://api.github.com/users/shazow/events{/privacy}","received_events_url":"https://api.github.com/users/shazow/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/shazow/urllib3","description":"Python HTTP library with thread-safe connection pooling, file post support, sanity friendly, and more.","fork":false,"url":"https://api.github.com/repos/shazow/urllib3","forks_url":"https://api.github.com/repos/shazow/urllib3/forks","keys_url":"https://api.github.com/repos/shazow/urllib3/keys{/key_id}","collaborators_url":"https://api.github.com/repos/shazow/urllib3/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/shazow/urllib3/teams","hooks_url":"https://api.github.com/repos/shazow/urllib3/hooks","issue_events_url":"https://api.github.com/repos/shazow/urllib3/issues/events{/number}","events_url":"https://api.github.com/repos/shazow/urllib3/events","assignees_url":"https://api.github.com/repos/shazow/urllib3/assignees{/user}","branches_url":"https://api.github.com/repos/shazow/urllib3/branches{/branch}","tags_url":"https://api.github.com/repos/shazow/urllib3/tags","blobs_url":"https://api.github.com/repos/shazow/urllib3/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/shazow/urllib3/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/shazow/urllib3/git/refs{/sha}","trees_url":"https://api.github.com/repos/shazow/urllib3/git/trees{/sha}","statuses_url":"https://api.github.com/repos/shazow/urllib3/statuses/{sha}","languages_url":"https://api.github.com/repos/shazow/urllib3/languages","stargazers_url":"https://api.github.com/repos/shazow/urllib3/stargazers","contributors_url":"https://api.github.com/repos/shazow/urllib3/contributors","subscribers_url":"https://api.github.com/repos/shazow/urllib3/subscribers","subscription_url":"https://api.github.com/repos/shazow/urllib3/subscription","commits_url":"https://api.github.com/repos/shazow/urllib3/commits{/sha}","git_commits_url":"https://api.github.com/repos/shazow/urllib3/git/commits{/sha}","comments_url":"https://api.github.com/repos/shazow/urllib3/comments{/number}","issue_comment_url":"https://api.github.com/repos/shazow/urllib3/issues/comments{/number}","contents_url":"https://api.github.com/repos/shazow/urllib3/contents/{+path}","compare_url":"https://api.github.com/repos/shazow/urllib3/compare/{base}...{head}","merges_url":"https://api.github.com/repos/shazow/urllib3/merges","archive_url":"https://api.github.com/repos/shazow/urllib3/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/shazow/urllib3/downloads","issues_url":"https://api.github.com/repos/shazow/urllib3/issues{/number}","pulls_url":"https://api.github.com/repos/shazow/urllib3/pulls{/number}","milestones_url":"https://api.github.com/repos/shazow/urllib3/milestones{/number}","notifications_url":"https://api.github.com/repos/shazow/urllib3/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/shazow/urllib3/labels{/name}","releases_url":"https://api.github.com/repos/shazow/urllib3/releases{/id}","deployments_url":"https://api.github.com/repos/shazow/urllib3/deployments","created_at":"2011-09-18T18:08:28Z","updated_at":"2016-10-28T09:17:51Z","pushed_at":"2016-10-28T07:06:49Z","git_url":"git://github.com/shazow/urllib3.git","ssh_url":"git@github.com:shazow/urllib3.git","clone_url":"https://github.com/shazow/urllib3.git","svn_url":"https://github.com/shazow/urllib3","homepage":"https://urllib3.readthedocs.io/","size":5145,"stargazers_count":1148,"watchers_count":1148,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":387,"mirror_url":null,"open_issues_count":123,"forks":387,"open_issues":123,"watchers":1148,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/shazow/urllib3/pulls/1001"},"html":{"href":"https://github.com/shazow/urllib3/pull/1001"},"issue":{"href":"https://api.github.com/repos/shazow/urllib3/issues/1001"},"comments":{"href":"https://api.github.com/repos/shazow/urllib3/issues/1001/comments"},"review_comments":{"href":"https://api.github.com/repos/shazow/urllib3/pulls/1001/comments"},"review_comment":{"href":"https://api.github.com/repos/shazow/urllib3/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/shazow/urllib3/pulls/1001/commits"},"statuses":{"href":"https://api.github.com/repos/shazow/urllib3/statuses/3b3582e2341478d1cbd3b48a36c172bc87c3b0bb"}}}}
{"actor":{"display_login":"Lukasa"}}