Repository: tornadoweb/tornado

Issue:
Pattern for deprecated IOLoop constructor
I see in 6.2 beta that the IOLoop constructor is deprecated, and that migration docs are forthcoming. I wanted to report how we've been using one of the deprecated patterns as an example of something that might need migration.

We have a pattern of using tornado to handle some background IO in threads, that looks like:

```python
from tornado.ioloop import IOLoop

class Background(Thread):
    def __init__(self):
        self.io_loop = IOLoop(make_current=False)

    def run(self):
        self.io_loop.start()

    def stop(self):
        self.io_loop.add_callback(self.io_loop.stop)
```

This pattern of creating the loop and talking to it before it starts is super useful. But the code as-is has started failing with the 6.2 beta (I'm not sure if the breakage is intentional or not), and I see that calling `IOLoop()` constructor directly is deprecated, which I understand given its relationship to the deprecated `asyncio.get_event_loop()`.

Now, I've found that the following code works, if I explicitly choose the `AsyncIOLoop` implementation:

```python
from tornado.platform.asyncio import AsyncIOLoop

class Background(Thread):
    def __init__(self):
        self.io_loop = AsyncIOLoop()

    def run(self):
        self.io_loop.start()

    def stop(self):
        self.io_loop.add_callback(self.io_loop.stop)
```

This makes sense to me, because AsyncIOLoop explicitly calls `asyncio.new_event_loop` and doesn't rely on any globals like get/set_event_loop. Is this a reasonable approach? Or is creating/accessing _any_ tornado IOLoop while the asyncio loop is not running going to be unsupported, or at least discouraged?

Explicitly creating / having a handle on the event loop is easy enough with asyncio, but if we can't get a handle on the tornado IOLoop object until asyncio is running in the background thread, that's going to be a pain.


Discussion:
Thanks for the feedback and for testing the new beta. It's a bug that the behavior of the current code has changed - the only intentional change is to start emitting deprecation warnings (when those are enabled) in some places. I'll take a look and see why it's breaking.

That's an interesting observation about AsyncIOLoop vs IOLoop. I forgot that class still exists with slightly different semantics from the default. It should be emitting a deprecation warning if make_current=True, but other than that I think we could consider supporting that pattern. Maybe we could even undeprecate the IOLoop constructor with make_current=False and give it the semantics of AsyncIOLoop. 
Thanks! That makes sense.

Keeping everything working with the 3.10 deprecations is pretty tough, so I don't mind needing updates to do the 'make a loop, configure it, then start it' pattern. I'd just like to do whatever you think is the official way to do that. Certainly, "wrap it all in one big asyncio.run" is one option, but more of a pain to adapt to.

btw, I was wrong about the above code working without `make_current=False`, but `AsyncIOLoop(make_current=False)` is working: https://github.com/ipython/ipykernel/pull/956
After chasing my tail for longer than it should have taken, `IOLoop(make_current=False)` actually works fine and emits no deprecation warnings. So I think this pattern can be addressed solely by updating the _documentation_ that `IOLoop(make_current=False)` is not going to be deprecated.

Sorry if you wasted time on this, too!
Great. Looks like the `.. deprecated:: 6.2` block in the docs is accurate but the main body of the docs left out this possibility. Based on the commit message I think I had doubts about whether anything but test frameworks would use this option, but you've shown that there is a need so I'll update that paragraph. 

Resolving patch:
diff --git a/tornado/ioloop.py b/tornado/ioloop.py
--- a/tornado/ioloop.py
+++ b/tornado/ioloop.py
@@ -119,10 +119,12 @@ async def main():
     .. testoutput::
        :hide:
 
-    Do not attempt to construct an `IOLoop` directly; this is deprecated since
-    Tornado 6.2. Instead, initialize the `asyncio` event loop and use
-    `IOLoop.current()` to access an `IOLoop` wrapper around the current event
-    loop.
+    Most applications should not attempt to construct an `IOLoop` directly,
+    and instead initialize the `asyncio` event loop and use `IOLoop.current()`.
+    In some cases, such as in test frameworks when initializing an `IOLoop`
+    to be run in a secondary thread, it may be appropriate to construct
+    an `IOLoop` with ``IOLoop(make_current=False)``. Constructing an `IOLoop`
+    without the ``make_current=False`` argument is deprecated since Tornado 6.2.
 
     In general, an `IOLoop` cannot survive a fork or be shared across processes
     in any way. When multiple processes are being used, each process should


Test patch: