Repository: tornadoweb/tornado

Issue:
Prepare for python 3.8 on windows
Python 3.8 asyncio is going to make the "proactor" event loop the default, instead of the current "selector" event loop. This is a problem for Tornado because the proactor event loop doesn't support the unix-style `add_reader` APIs that Tornado uses. 

Anyone using Tornado 5+ on windows with python 3.8 will need to configure asyncio to use the selector event loop; we'll have to document this. We should also try to detect the use of a proactor event loop and give a clear error message (the current NotImplementError message can be seen in [this SO post](https://stackoverflow.com/questions/54805213/jupyter-notebook-cannot-start-with-python-3-8-in-windows-10)). 

I don't think it would be appropriate for Tornado itself to configure the selector event loop automatically, since it has drawbacks (less scalability than the proactor loop, the user may want to use another event loop entirely like `uvloop`). Applications *using* Tornado (like jupyter notebook) may wish to do so, though. Maybe there should even be some more magical way to choose the selector loop (an environment variable? a special package to install?) to avoid the need to build this in to each application, although i don't really like the idea of that much magic.


Discussion:
so the ugly patch is to do this on Tornado asyncio.py

````
import asyncio
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())  # python-3.8.0a4
````

what should be the external procedure (per client packages, like for example Jupyter packages) ?
I think you can do that at the beginning of your `main()` (or `if __name__ == '__main__':` etc), it does not need to be patched into a tornado module. Just somewhere before any event loop is started.
https://twitter.com/VictorStinner/status/1133323227298697216?s=09

""""
asyncio on Windows will be a much better experience in Python 3.8: IOCP event loop by default (subprocess support), CTRL+C and UDP support. https://t.co/MNpctRWma4 (outdated doc, UDP support is merged). Well done Andrew Svetlov @andrew_svetlov and others who made this possible!""

Resolving patch:
diff --git a/tornado/__init__.py b/tornado/__init__.py
--- a/tornado/__init__.py
+++ b/tornado/__init__.py
@@ -22,5 +22,5 @@
 # is zero for an official release, positive for a development branch,
 # or negative for a release candidate or beta (after the base version
 # number has been incremented)
-version = "6.0.2"
-version_info = (6, 0, 2, 0)
+version = "6.0.3"
+version_info = (6, 0, 3, 0)
diff --git a/tornado/gen.py b/tornado/gen.py
--- a/tornado/gen.py
+++ b/tornado/gen.py
@@ -557,8 +557,9 @@ def with_timeout(
     an absolute time relative to `.IOLoop.time`)
 
     If the wrapped `.Future` fails after it has timed out, the exception
-    will be logged unless it is of a type contained in ``quiet_exceptions``
-    (which may be an exception type or a sequence of types).
+    will be logged unless it is either of a type contained in
+    ``quiet_exceptions`` (which may be an exception type or a sequence of
+    types), or an ``asyncio.CancelledError``.
 
     The wrapped `.Future` is not canceled when the timeout expires,
     permitting it to be reused. `asyncio.wait_for` is similar to this
@@ -573,6 +574,9 @@ def with_timeout(
     .. versionchanged:: 4.4
        Added support for yieldable objects other than `.Future`.
 
+    .. versionchanged:: 6.0.3
+       ``asyncio.CancelledError`` is now always considered "quiet".
+
     """
     # It's tempting to optimize this by cancelling the input future on timeout
     # instead of creating a new one, but A) we can't know if we are the only
@@ -587,6 +591,8 @@ def with_timeout(
     def error_callback(future: Future) -> None:
         try:
             future.result()
+        except asyncio.CancelledError:
+            pass
         except Exception as e:
             if not isinstance(e, quiet_exceptions):
                 app_log.error(
diff --git a/tornado/iostream.py b/tornado/iostream.py
--- a/tornado/iostream.py
+++ b/tornado/iostream.py
@@ -794,6 +794,7 @@ def _handle_read(self) -> None:
             self._read_from_buffer(pos)
 
     def _start_read(self) -> Future:
+        self._check_closed()  # Before reading, check that stream is not closed.
         assert self._read_future is None, "Already reading"
         self._read_future = Future()
         return self._read_future


Test patch:
diff --git a/tornado/test/runtests.py b/tornado/test/runtests.py
--- a/tornado/test/runtests.py
+++ b/tornado/test/runtests.py
@@ -150,6 +150,16 @@ def main():
         warnings.filterwarnings(
             "ignore", category=ResourceWarning, module=r"asyncio\..*"
         )
+    # This deprecation warning is introduced in Python 3.8 and is
+    # triggered by pycurl. Unforunately, because it is raised in the C
+    # layer it can't be filtered by module and we must match the
+    # message text instead (Tornado's C module uses PY_SSIZE_T_CLEAN
+    # so it's not at risk of running into this issue).
+    warnings.filterwarnings(
+        "ignore",
+        category=DeprecationWarning,
+        message="PY_SSIZE_T_CLEAN will be required",
+    )
 
     logging.getLogger("tornado.access").setLevel(logging.CRITICAL)