From 679fde69f22648c943a72b5c5fb2629b4b33ae07 Mon Sep 17 00:00:00 2001
From: Yury Selivanov <yury@edgedb.com>
Date: Fri, 25 Oct 2019 10:29:47 -0400
Subject: [PATCH 1/6] Bump to 0.14.0rc1

---
 uvloop/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/uvloop/__init__.py b/uvloop/__init__.py
index 81ec85ab..df67eb14 100644
--- a/uvloop/__init__.py
+++ b/uvloop/__init__.py
@@ -7,7 +7,7 @@
 from .loop import Loop as __BaseLoop  # NOQA
 
 
-__version__ = '0.14.0.dev0'
+__version__ = '0.14.0rc1'
 __all__ = ('new_event_loop', 'install', 'EventLoopPolicy')
 
 

From d76d982717fb648c1bd75fc6ba8a98180bc8d97c Mon Sep 17 00:00:00 2001
From: Yury Selivanov <yury@edgedb.com>
Date: Mon, 28 Oct 2019 18:07:27 -0400
Subject: [PATCH 2/6] Fix KeyboardInterrupt handling logic.

When uvloop is run in the main thread we *always* want to set up a
self-pipe and a signal wakeup FD.  That's the only way how libuv
can be notified that a ^C happened and break away from selecting
on sockets.

asyncio does not need to do that, as the 'selectors' module it uses
is already aware of the way Python implements ^C handling.

This translates to a slightly different behavior between asyncio &
uvloop:

1. uvloop needs to always call signal.set_wakeup_fd() when run in the
  main thread;

2. asyncio only needs to call signal.set_wakeup_fd() when a user
  registers a signal handler.

(2) means that if the user had not set up any signals, the signal
wakeup FD stays the same between different asyncio runs.  This commit
fixes uvloop signal implementation to make sure that uvloop behaves
the same way as asyncio in regards to signal wakeup FD between the
loop runs.  It also ensures that uvloop always have a proper
self-pipe set up so that ^C is always supported when it is run in
the main thread.

Issue #295.
---
 tests/test_dealloc.py |   2 +-
 tests/test_regr1.py   |  57 +++++++++---------
 tests/test_signals.py |  39 ++++++++++++
 uvloop/loop.pxd       |   8 ++-
 uvloop/loop.pyx       | 135 +++++++++++++++++++++++-------------------
 5 files changed, 146 insertions(+), 95 deletions(-)

diff --git a/tests/test_dealloc.py b/tests/test_dealloc.py
index 3ae436e8..0b9b2f6e 100644
--- a/tests/test_dealloc.py
+++ b/tests/test_dealloc.py
@@ -36,7 +36,7 @@ async def foo():
     return 42
 
 def main():
-    asyncio.set_event_loop(uvloop.new_event_loop())
+    uvloop.install()
     loop = asyncio.get_event_loop()
     loop.set_debug(True)
     loop.run_until_complete(foo())
diff --git a/tests/test_regr1.py b/tests/test_regr1.py
index 8c8d5572..c502457e 100644
--- a/tests/test_regr1.py
+++ b/tests/test_regr1.py
@@ -74,36 +74,33 @@ def on_alarm(self, sig, fr):
             raise FailedTestError
 
     def run_test(self):
-        try:
-            for i in range(10):
-                for threaded in [True, False]:
-                    if threaded:
-                        qin, qout = queue.Queue(), queue.Queue()
-                        threading.Thread(
-                            target=run_server,
-                            args=(qin, qout),
-                            daemon=True).start()
-                    else:
-                        qin = multiprocessing.Queue()
-                        qout = multiprocessing.Queue()
-                        multiprocessing.Process(
-                            target=run_server,
-                            args=(qin, qout),
-                            daemon=True).start()
-
-                    addr = qout.get()
-                    loop = self.new_loop()
-                    asyncio.set_event_loop(loop)
-                    loop.create_task(
-                        loop.create_connection(
-                            lambda: EchoClientProtocol(loop),
-                            host=addr[0], port=addr[1]))
-                    loop.run_forever()
-                    loop.close()
-                    qin.put('stop')
-                    qout.get()
-        finally:
-            loop.close()
+        for i in range(10):
+            for threaded in [True, False]:
+                if threaded:
+                    qin, qout = queue.Queue(), queue.Queue()
+                    threading.Thread(
+                        target=run_server,
+                        args=(qin, qout),
+                        daemon=True).start()
+                else:
+                    qin = multiprocessing.Queue()
+                    qout = multiprocessing.Queue()
+                    multiprocessing.Process(
+                        target=run_server,
+                        args=(qin, qout),
+                        daemon=True).start()
+
+                addr = qout.get()
+                loop = self.new_loop()
+                asyncio.set_event_loop(loop)
+                loop.create_task(
+                    loop.create_connection(
+                        lambda: EchoClientProtocol(loop),
+                        host=addr[0], port=addr[1]))
+                loop.run_forever()
+                loop.close()
+                qin.put('stop')
+                qout.get()
 
     @unittest.skipIf(
         multiprocessing.get_start_method(False) == 'spawn',
diff --git a/tests/test_signals.py b/tests/test_signals.py
index 96879cb8..e51f5691 100644
--- a/tests/test_signals.py
+++ b/tests/test_signals.py
@@ -117,6 +117,45 @@ async def worker():
 loop = """ + self.NEW_LOOP + """
 asyncio.set_event_loop(loop)
 loop.create_task(worker())
+try:
+    loop.run_forever()
+finally:
+    srv.close()
+    loop.run_until_complete(srv.wait_closed())
+    loop.close()
+"""
+
+            proc = await asyncio.create_subprocess_exec(
+                sys.executable, b'-W', b'ignore', b'-c', PROG,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE)
+
+            await proc.stdout.readline()
+            time.sleep(DELAY)
+            proc.send_signal(signal.SIGINT)
+            out, err = await proc.communicate()
+            self.assertIn(b'KeyboardInterrupt', err)
+
+        self.loop.run_until_complete(runner())
+
+    @tb.silence_long_exec_warning()
+    def test_signals_sigint_uvcode_two_loop_runs(self):
+        async def runner():
+            PROG = R"""\
+import asyncio
+import uvloop
+
+srv = None
+
+async def worker():
+    global srv
+    cb = lambda *args: None
+    srv = await asyncio.start_server(cb, '127.0.0.1', 0)
+
+loop = """ + self.NEW_LOOP + """
+asyncio.set_event_loop(loop)
+loop.run_until_complete(worker())
+print('READY', flush=True)
 try:
     loop.run_forever()
 finally:
diff --git a/uvloop/loop.pxd b/uvloop/loop.pxd
index 765e48ef..f36d1e2f 100644
--- a/uvloop/loop.pxd
+++ b/uvloop/loop.pxd
@@ -65,6 +65,7 @@ cdef class Loop:
         object _ssock
         object _csock
         bint _listening_signals
+        int _old_signal_wakeup_id
 
         set _timers
         dict _polls
@@ -149,6 +150,8 @@ cdef class Loop:
 
     cdef void _handle_exception(self, object ex)
 
+    cdef inline _is_main_thread(self)
+
     cdef inline _new_future(self)
     cdef inline _check_signal(self, sig)
     cdef inline _check_closed(self)
@@ -186,10 +189,9 @@ cdef class Loop:
 
     cdef _sock_set_reuseport(self, int fd)
 
-    cdef _setup_signals(self)
+    cdef _setup_or_resume_signals(self)
     cdef _shutdown_signals(self)
-    cdef _recv_signals_start(self)
-    cdef _recv_signals_stop(self)
+    cdef _pause_signals(self)
 
     cdef _handle_signal(self, sig)
     cdef _read_from_self(self)
diff --git a/uvloop/loop.pyx b/uvloop/loop.pyx
index fe79165c..c865fedf 100644
--- a/uvloop/loop.pyx
+++ b/uvloop/loop.pyx
@@ -167,6 +167,7 @@ cdef class Loop:
         self._ssock = self._csock = None
         self._signal_handlers = {}
         self._listening_signals = False
+        self._old_signal_wakeup_id = -1
 
         self._coroutine_debug_set = False
 
@@ -183,6 +184,9 @@ cdef class Loop:
 
         self._servers = set()
 
+    cdef inline _is_main_thread(self):
+        return MAIN_THREAD_ID == PyThread_get_thread_ident()
+
     def __init__(self):
         self.set_debug((not sys_ignore_environment
                         and bool(os_environ.get('PYTHONASYNCIODEBUG'))))
@@ -241,34 +245,40 @@ cdef class Loop:
 
         self._debug_exception_handler_cnt = 0
 
-    cdef _setup_signals(self):
-        cdef int old_wakeup_fd
+    cdef _setup_or_resume_signals(self):
+        if not self._is_main_thread():
+            return
 
         if self._listening_signals:
-            return
+            raise RuntimeError('signals handling has been already setup')
+
+        if self._ssock is not None:
+            raise RuntimeError('self-pipe exists before loop run')
+
+        # Create a self-pipe and call set_signal_wakeup_fd() with one
+        # of its ends.  This is needed so that libuv knows that it needs
+        # to wakeup on ^C (no matter if the SIGINT handler is still the
+        # standard Python's one or or user set their own.)
 
         self._ssock, self._csock = socket_socketpair()
-        self._ssock.setblocking(False)
-        self._csock.setblocking(False)
         try:
-            old_wakeup_fd = _set_signal_wakeup_fd(self._csock.fileno())
-        except (OSError, ValueError):
-            # Not the main thread
+            self._ssock.setblocking(False)
+            self._csock.setblocking(False)
+
+            fileno = self._csock.fileno()
+
+            self._old_signal_wakeup_id = _set_signal_wakeup_fd(fileno)
+        except Exception:
+            # Out of all statements in the try block, only the
+            # "_set_signal_wakeup_fd()" call can fail, but it shouldn't,
+            # as we ensure that the current thread is the main thread.
+            # Still, if something goes horribly wrong we want to clean up
+            # the socket pair.
             self._ssock.close()
             self._csock.close()
-            self._ssock = self._csock = None
-            return
-
-        self._listening_signals = True
-        return old_wakeup_fd
-
-    cdef _recv_signals_start(self):
-        cdef object old_wakeup_fd = None
-        if self._ssock is None:
-            old_wakeup_fd = self._setup_signals()
-            if self._ssock is None:
-                # Not the main thread.
-                return
+            self._ssock = None
+            self._csock = None
+            raise
 
         self._add_reader(
             self._ssock,
@@ -277,30 +287,24 @@ cdef class Loop:
                 "Loop._read_from_self",
                 <method_t>self._read_from_self,
                 self))
-        return old_wakeup_fd
 
-    cdef _recv_signals_stop(self):
-        if self._ssock is None:
-            return
+        self._listening_signals = True
 
-        self._remove_reader(self._ssock)
+    cdef _pause_signals(self):
+        if not self._is_main_thread():
+            if self._listening_signals:
+                raise RuntimeError(
+                    'cannot pause signals handling; no longer running in '
+                    'the main thread')
+            else:
+                return
 
-    cdef _shutdown_signals(self):
         if not self._listening_signals:
-            return
+            raise RuntimeError('signals handling has not been setup')
 
-        for sig in list(self._signal_handlers):
-            self.remove_signal_handler(sig)
-
-        if not self._listening_signals:
-            # `remove_signal_handler` will call `_shutdown_signals` when
-            # removing last signal handler.
-            return
+        self._listening_signals = False
 
-        try:
-            signal_set_wakeup_fd(-1)
-        except (ValueError, OSError) as exc:
-            aio_logger.info('set_wakeup_fd(-1) failed: %s', exc)
+        _set_signal_wakeup_fd(self._old_signal_wakeup_id)
 
         self._remove_reader(self._ssock)
         self._ssock.close()
@@ -308,7 +312,24 @@ cdef class Loop:
         self._ssock = None
         self._csock = None
 
-        self._listening_signals = False
+    cdef _shutdown_signals(self):
+        if not self._is_main_thread():
+            if self._signal_handlers:
+                aio_logger.warning(
+                    'cannot cleanup signal handlers: closing the event loop '
+                    'in a non-main OS thread')
+            return
+
+        if self._listening_signals:
+            raise RuntimeError(
+                'cannot shutdown signals handling as it has not been paused')
+
+        if self._ssock:
+            raise RuntimeError(
+                'self-pipe was not cleaned up after loop was run')
+
+        for sig in list(self._signal_handlers):
+            self.remove_signal_handler(sig)
 
     def __sighandler(self, signum, frame):
         self._signals.add(signum)
@@ -451,7 +472,6 @@ cdef class Loop:
 
     cdef _run(self, uv.uv_run_mode mode):
         cdef int err
-        cdef object old_wakeup_fd
 
         if self._closed == 1:
             raise RuntimeError('unable to start the loop; it was closed')
@@ -474,7 +494,7 @@ cdef class Loop:
         self.handler_check__exec_writes.start()
         self.handler_idle.start()
 
-        old_wakeup_fd = self._recv_signals_start()
+        self._setup_or_resume_signals()
 
         if aio_set_running_loop is not None:
             aio_set_running_loop(self)
@@ -484,13 +504,11 @@ cdef class Loop:
             if aio_set_running_loop is not None:
                 aio_set_running_loop(None)
 
-            self._recv_signals_stop()
-            if old_wakeup_fd is not None:
-                signal_set_wakeup_fd(old_wakeup_fd)
-
             self.handler_check__exec_writes.stop()
             self.handler_idle.stop()
 
+            self._pause_signals()
+
             self._thread_is_main = 0
             self._thread_id = 0
             self._running = 0
@@ -2794,10 +2812,10 @@ cdef class Loop:
         cdef:
             Handle h
 
-        if not self._listening_signals:
-            self._setup_signals()
-            if not self._listening_signals:
-                raise ValueError('set_wakeup_fd only works in main thread')
+        if not self._is_main_thread():
+            raise ValueError(
+                'add_signal_handler() can only be called from '
+                'the main thread')
 
         if (aio_iscoroutine(callback)
                 or aio_iscoroutinefunction(callback)):
@@ -2829,14 +2847,6 @@ cdef class Loop:
 
         self._check_signal(sig)
         self._check_closed()
-        try:
-            # set_wakeup_fd() raises ValueError if this is not the
-            # main thread.  By calling it early we ensure that an
-            # event loop running in another thread cannot add a signal
-            # handler.
-            _set_signal_wakeup_fd(self._csock.fileno())
-        except (ValueError, OSError) as exc:
-            raise RuntimeError(str(exc))
 
         h = new_Handle(self, callback, args or None, None)
         self._signal_handlers[sig] = h
@@ -2866,6 +2876,12 @@ cdef class Loop:
 
         Return True if a signal handler was removed, False if not.
         """
+
+        if not self._is_main_thread():
+            raise ValueError(
+                'remove_signal_handler() can only be called from '
+                'the main thread')
+
         self._check_signal(sig)
 
         if not self._listening_signals:
@@ -2889,9 +2905,6 @@ cdef class Loop:
             else:
                 raise
 
-        if not self._signal_handlers:
-            self._shutdown_signals()
-
         return True
 
     @cython.iterable_coroutine

From e6fd63774e41d56ee14c8a5259e0db466fec7c0c Mon Sep 17 00:00:00 2001
From: Fantix King <fantix.king@gmail.com>
Date: Fri, 25 Oct 2019 17:28:27 -0500
Subject: [PATCH 3/6] fix missing data on EOF in flushing

* when EOF is received and data is still pending in incoming buffer,
  the data will be lost before this fix
* also removed sleep from a recent-written test
---
 tests/test_tcp.py   | 70 +++++++++++++++++++--------------------------
 uvloop/sslproto.pxd |  1 +
 uvloop/sslproto.pyx | 47 +++++++++++-------------------
 3 files changed, 47 insertions(+), 71 deletions(-)

diff --git a/tests/test_tcp.py b/tests/test_tcp.py
index 8ea6e592..6ab170af 100644
--- a/tests/test_tcp.py
+++ b/tests/test_tcp.py
@@ -2606,35 +2606,6 @@ def server(sock):
             self.assertEqual(len(data), CHUNK * SIZE)
             sock.close()
 
-        def openssl_server(sock):
-            conn = openssl_ssl.Connection(sslctx_openssl, sock)
-            conn.set_accept_state()
-
-            while True:
-                try:
-                    data = conn.recv(16384)
-                    self.assertEqual(data, b'ping')
-                    break
-                except openssl_ssl.WantReadError:
-                    pass
-
-            # use renegotiation to queue data in peer _write_backlog
-            conn.renegotiate()
-            conn.send(b'pong')
-
-            data_size = 0
-            while True:
-                try:
-                    chunk = conn.recv(16384)
-                    if not chunk:
-                        break
-                    data_size += len(chunk)
-                except openssl_ssl.WantReadError:
-                    pass
-                except openssl_ssl.ZeroReturnError:
-                    break
-            self.assertEqual(data_size, CHUNK * SIZE)
-
         def run(meth):
             def wrapper(sock):
                 try:
@@ -2652,12 +2623,18 @@ async def client(addr):
                 *addr,
                 ssl=client_sslctx,
                 server_hostname='')
+            sslprotocol = writer.get_extra_info('uvloop.sslproto')
             writer.write(b'ping')
             data = await reader.readexactly(4)
             self.assertEqual(data, b'pong')
+
+            sslprotocol.pause_writing()
             for _ in range(SIZE):
                 writer.write(b'x' * CHUNK)
+
             writer.close()
+            sslprotocol.resume_writing()
+
             await self.wait_closed(writer)
             try:
                 data = await reader.read()
@@ -2669,9 +2646,6 @@ async def client(addr):
         with self.tcp_server(run(server)) as srv:
             self.loop.run_until_complete(client(srv.addr))
 
-        with self.tcp_server(run(openssl_server)) as srv:
-            self.loop.run_until_complete(client(srv.addr))
-
     def test_remote_shutdown_receives_trailing_data(self):
         if self.implementation == 'asyncio':
             raise unittest.SkipTest()
@@ -2892,7 +2866,13 @@ async def client(addr, ctx):
         self.assertIsNone(ctx())
 
     def test_shutdown_timeout_handler_not_set(self):
+        if self.implementation == 'asyncio':
+            # asyncio cannot receive EOF after resume_reading()
+            raise unittest.SkipTest()
+
         loop = self.loop
+        eof = asyncio.Event()
+        extra = None
 
         def server(sock):
             sslctx = self._create_server_ssl_context(self.ONLYCERT,
@@ -2900,12 +2880,12 @@ def server(sock):
             sock = sslctx.wrap_socket(sock, server_side=True)
             sock.send(b'hello')
             assert sock.recv(1024) == b'world'
-            time.sleep(0.1)
-            sock.send(b'extra bytes' * 1)
+            sock.send(b'extra bytes')
             # sending EOF here
             sock.shutdown(socket.SHUT_WR)
+            loop.call_soon_threadsafe(eof.set)
             # make sure we have enough time to reproduce the issue
-            time.sleep(0.1)
+            assert sock.recv(1024) == b''
             sock.close()
 
         class Protocol(asyncio.Protocol):
@@ -2917,20 +2897,28 @@ def connection_made(self, transport):
                 self.transport = transport
 
             def data_received(self, data):
-                self.transport.write(b'world')
-                # pause reading would make incoming data stay in the sslobj
-                self.transport.pause_reading()
-                # resume for AIO to pass
-                loop.call_later(0.2, self.transport.resume_reading)
+                if data == b'hello':
+                    self.transport.write(b'world')
+                    # pause reading would make incoming data stay in the sslobj
+                    self.transport.pause_reading()
+                else:
+                    nonlocal extra
+                    extra = data
 
             def connection_lost(self, exc):
-                self.fut.set_result(None)
+                if exc is None:
+                    self.fut.set_result(None)
+                else:
+                    self.fut.set_exception(exc)
 
         async def client(addr):
             ctx = self._create_client_ssl_context()
             tr, pr = await loop.create_connection(Protocol, *addr, ssl=ctx)
+            await eof.wait()
+            tr.resume_reading()
             await pr.fut
             tr.close()
+            assert extra == b'extra bytes'
 
         with self.tcp_server(server) as srv:
             loop.run_until_complete(client(srv.addr))
diff --git a/uvloop/sslproto.pxd b/uvloop/sslproto.pxd
index c29af7ba..bc94bfd5 100644
--- a/uvloop/sslproto.pxd
+++ b/uvloop/sslproto.pxd
@@ -65,6 +65,7 @@ cdef class SSLProtocol:
 
         bint _ssl_writing_paused
         bint _app_reading_paused
+        bint _eof_received
 
         size_t _incoming_high_water
         size_t _incoming_low_water
diff --git a/uvloop/sslproto.pyx b/uvloop/sslproto.pyx
index c5b9c3a1..1a52e71e 100644
--- a/uvloop/sslproto.pyx
+++ b/uvloop/sslproto.pyx
@@ -278,6 +278,7 @@ cdef class SSLProtocol:
         self._incoming_high_water = 0
         self._incoming_low_water = 0
         self._set_read_buffer_limits()
+        self._eof_received = False
 
         self._app_writing_paused = False
         self._outgoing_high_water = 0
@@ -391,6 +392,7 @@ cdef class SSLProtocol:
         will close itself.  If it returns a true value, closing the
         transport is up to the protocol.
         """
+        self._eof_received = True
         try:
             if self._loop.get_debug():
                 aio_logger.debug("%r received EOF", self)
@@ -400,9 +402,10 @@ cdef class SSLProtocol:
 
             elif self._state == WRAPPED:
                 self._set_state(FLUSHING)
-                self._do_write()
-                self._set_state(SHUTDOWN)
-                self._do_shutdown()
+                if self._app_reading_paused:
+                    return True
+                else:
+                    self._do_flush()
 
             elif self._state == FLUSHING:
                 self._do_write()
@@ -412,11 +415,14 @@ cdef class SSLProtocol:
             elif self._state == SHUTDOWN:
                 self._do_shutdown()
 
-        finally:
+        except Exception:
             self._transport.close()
+            raise
 
     cdef _get_extra_info(self, name, default=None):
-        if name in self._extra:
+        if name == 'uvloop.sslproto':
+            return self
+        elif name in self._extra:
             return self._extra[name]
         elif self._transport is not None:
             return self._transport.get_extra_info(name, default)
@@ -555,33 +561,14 @@ cdef class SSLProtocol:
                 aio_TimeoutError('SSL shutdown timed out'))
 
     cdef _do_flush(self):
-        if self._write_backlog:
-            try:
-                while True:
-                    # data is discarded when FLUSHING
-                    chunk_size = len(self._sslobj_read(SSL_READ_MAX_SIZE))
-                    if not chunk_size:
-                        # close_notify
-                        break
-            except ssl_SSLAgainErrors as exc:
-                pass
-            except ssl_SSLError as exc:
-                self._on_shutdown_complete(exc)
-                return
-
-            try:
-                self._do_write()
-            except Exception as exc:
-                self._on_shutdown_complete(exc)
-                return
-
-        if not self._write_backlog:
-            self._set_state(SHUTDOWN)
-            self._do_shutdown()
+        self._do_read()
+        self._set_state(SHUTDOWN)
+        self._do_shutdown()
 
     cdef _do_shutdown(self):
         try:
-            self._sslobj.unwrap()
+            if not self._eof_received:
+                self._sslobj.unwrap()
         except ssl_SSLAgainErrors as exc:
             self._process_outgoing()
         except ssl_SSLError as exc:
@@ -655,7 +642,7 @@ cdef class SSLProtocol:
     # Incoming flow
 
     cdef _do_read(self):
-        if self._state != WRAPPED:
+        if self._state != WRAPPED and self._state != FLUSHING:
             return
         try:
             if not self._app_reading_paused:

From 6fd6264145f403f05d1d5ed1758bb6b4e4741c71 Mon Sep 17 00:00:00 2001
From: Fantix King <fantix.king@gmail.com>
Date: Mon, 28 Oct 2019 17:54:52 -0500
Subject: [PATCH 4/6] run `test_shutdown_timeout_handler_not_set` in aio

---
 tests/test_tcp.py | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/tests/test_tcp.py b/tests/test_tcp.py
index 6ab170af..83a61a2f 100644
--- a/tests/test_tcp.py
+++ b/tests/test_tcp.py
@@ -2866,10 +2866,6 @@ async def client(addr, ctx):
         self.assertIsNone(ctx())
 
     def test_shutdown_timeout_handler_not_set(self):
-        if self.implementation == 'asyncio':
-            # asyncio cannot receive EOF after resume_reading()
-            raise unittest.SkipTest()
-
         loop = self.loop
         eof = asyncio.Event()
         extra = None

From 9b4bd2ea4ab7aa8db8dd3aebff4b8c36a8f1dd98 Mon Sep 17 00:00:00 2001
From: Yury Selivanov <yury@edgedb.com>
Date: Mon, 28 Oct 2019 23:26:26 -0400
Subject: [PATCH 5/6] Bump the version to 0.14.0rc2

---
 uvloop/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/uvloop/__init__.py b/uvloop/__init__.py
index df67eb14..ffa45c9c 100644
--- a/uvloop/__init__.py
+++ b/uvloop/__init__.py
@@ -7,7 +7,7 @@
 from .loop import Loop as __BaseLoop  # NOQA
 
 
-__version__ = '0.14.0rc1'
+__version__ = '0.14.0rc2'
 __all__ = ('new_event_loop', 'install', 'EventLoopPolicy')
 
 

From 0e72f817ebb99cf432f72e9d7478e482c6ca0f3e Mon Sep 17 00:00:00 2001
From: Yury Selivanov <yury@edgedb.com>
Date: Tue, 5 Nov 2019 09:30:10 -0500
Subject: [PATCH 6/6] Bump the version to 0.14.0

---
 uvloop/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/uvloop/__init__.py b/uvloop/__init__.py
index ffa45c9c..06b87591 100644
--- a/uvloop/__init__.py
+++ b/uvloop/__init__.py
@@ -7,7 +7,7 @@
 from .loop import Loop as __BaseLoop  # NOQA
 
 
-__version__ = '0.14.0rc2'
+__version__ = '0.14.0'
 __all__ = ('new_event_loop', 'install', 'EventLoopPolicy')