Repository: tornadoweb/tornado
Pull request #2052 (2017-05-22 05:04:52)

all: Remove deprecated io_loop arguments
IOLoop.current() is now used in all cases; it is no longer possible to
pass IOLoops around and use them directly. This occasionally requires
awkward workarounds with IOLoop.run_sync, but mainly in test code
(and the synchronous HTTPClient).


Patch:
diff --git a/docs/httpclient.rst b/docs/httpclient.rst
index 53a0a881..78231618 100644
--- a/docs/httpclient.rst
+++ b/docs/httpclient.rst
@@ -47,7 +47,7 @@ Implementations
 
 .. module:: tornado.curl_httpclient
 
-.. class:: CurlAsyncHTTPClient(io_loop, max_clients=10, defaults=None)
+.. class:: CurlAsyncHTTPClient(max_clients=10, defaults=None)
 
    ``libcurl``-based HTTP client.
 
diff --git a/tornado/autoreload.py b/tornado/autoreload.py
index 60571efe..24deb79d 100644
--- a/tornado/autoreload.py
+++ b/tornado/autoreload.py
@@ -111,13 +111,13 @@ _reload_attempted = False
 _io_loops = weakref.WeakKeyDictionary()  # type: ignore
 
 
-def start(io_loop=None, check_time=500):
+def start(check_time=500):
     """Begins watching source files for changes.
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
     """
-    io_loop = io_loop or ioloop.IOLoop.current()
+    io_loop = ioloop.IOLoop.current()
     if io_loop in _io_loops:
         return
     _io_loops[io_loop] = True
@@ -125,7 +125,7 @@ def start(io_loop=None, check_time=500):
         gen_log.warning("tornado.autoreload started more than once in the same process")
     modify_times = {}
     callback = functools.partial(_reload_on_update, modify_times)
-    scheduler = ioloop.PeriodicCallback(callback, check_time, io_loop=io_loop)
+    scheduler = ioloop.PeriodicCallback(callback, check_time)
     scheduler.start()
 
 
diff --git a/tornado/concurrent.py b/tornado/concurrent.py
index 667e6b17..b1bcc56e 100644
--- a/tornado/concurrent.py
+++ b/tornado/concurrent.py
@@ -378,9 +378,9 @@ def run_on_executor(*args, **kwargs):
     The decorated method may be called with a ``callback`` keyword
     argument and returns a future.
 
-    The `.IOLoop` and executor to be used are determined by the ``io_loop``
-    and ``executor`` attributes of ``self``. To use different attributes,
-    pass keyword arguments to the decorator::
+    The executor to be used is determined by the ``executor``
+    attributes of ``self``. To use a different attribute name, pass a
+    keyword argument to the decorator::
 
         @run_on_executor(executor='_thread_pool')
         def foo(self):
@@ -388,17 +388,20 @@ def run_on_executor(*args, **kwargs):
 
     .. versionchanged:: 4.2
        Added keyword arguments to use alternative attributes.
+
+    .. versionchanged:: 5.0
+       Always uses the current IOLoop instead of ``self.io_loop``.
     """
     def run_on_executor_decorator(fn):
         executor = kwargs.get("executor", "executor")
-        io_loop = kwargs.get("io_loop", "io_loop")
 
         @functools.wraps(fn)
         def wrapper(self, *args, **kwargs):
             callback = kwargs.pop("callback", None)
             future = getattr(self, executor).submit(fn, self, *args, **kwargs)
             if callback:
-                getattr(self, io_loop).add_future(
+                from tornado.ioloop import IOLoop
+                IOLoop.current().add_future(
                     future, lambda future: callback(future.result()))
             return future
         return wrapper
diff --git a/tornado/curl_httpclient.py b/tornado/curl_httpclient.py
index 554710ee..77c9537c 100644
--- a/tornado/curl_httpclient.py
+++ b/tornado/curl_httpclient.py
@@ -37,8 +37,8 @@ curl_log = logging.getLogger('tornado.curl_httpclient')
 
 
 class CurlAsyncHTTPClient(AsyncHTTPClient):
-    def initialize(self, io_loop, max_clients=10, defaults=None):
-        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)
+    def initialize(self, max_clients=10, defaults=None):
+        super(CurlAsyncHTTPClient, self).initialize(defaults=defaults)
         self._multi = pycurl.CurlMulti()
         self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)
         self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)
@@ -53,7 +53,7 @@ class CurlAsyncHTTPClient(AsyncHTTPClient):
         # SOCKETFUNCTION.  Mitigate the effects of such bugs by
         # forcing a periodic scan of all active requests.
         self._force_timeout_callback = ioloop.PeriodicCallback(
-            self._handle_force_timeout, 1000, io_loop=io_loop)
+            self._handle_force_timeout, 1000)
         self._force_timeout_callback.start()
 
         # Work around a bug in libcurl 7.29.0: Some fields in the curl
diff --git a/tornado/gen.py b/tornado/gen.py
index 99f91066..2e9e9a6a 100644
--- a/tornado/gen.py
+++ b/tornado/gen.py
@@ -624,14 +624,14 @@ def Task(func, *args, **kwargs):
 
 
 class YieldFuture(YieldPoint):
-    def __init__(self, future, io_loop=None):
+    def __init__(self, future):
         """Adapts a `.Future` to the `YieldPoint` interface.
 
-        .. versionchanged:: 4.1
-           The ``io_loop`` argument is deprecated.
+        .. versionchanged:: 5.0
+           The ``io_loop`` argument (deprecated since version 4.1) has been removed.
         """
         self.future = future
-        self.io_loop = io_loop or IOLoop.current()
+        self.io_loop = IOLoop.current()
 
     def start(self, runner):
         if not self.future.done():
@@ -868,7 +868,7 @@ def maybe_future(x):
         return fut
 
 
-def with_timeout(timeout, future, io_loop=None, quiet_exceptions=()):
+def with_timeout(timeout, future, quiet_exceptions=()):
     """Wraps a `.Future` (or other yieldable object) in a timeout.
 
     Raises `TimeoutError` if the input future does not complete before
@@ -902,8 +902,7 @@ def with_timeout(timeout, future, io_loop=None, quiet_exceptions=()):
     future = convert_yielded(future)
     result = Future()
     chain_future(future, result)
-    if io_loop is None:
-        io_loop = IOLoop.current()
+    io_loop = IOLoop.current()
 
     def error_callback(future):
         try:
diff --git a/tornado/http1connection.py b/tornado/http1connection.py
index 53744ece..c6d3e336 100644
--- a/tornado/http1connection.py
+++ b/tornado/http1connection.py
@@ -164,7 +164,6 @@ class HTTP1Connection(httputil.HTTPConnection):
                     header_data = yield gen.with_timeout(
                         self.stream.io_loop.time() + self.params.header_timeout,
                         header_future,
-                        io_loop=self.stream.io_loop,
                         quiet_exceptions=iostream.StreamClosedError)
                 except gen.TimeoutError:
                     self.close()
@@ -224,7 +223,7 @@ class HTTP1Connection(httputil.HTTPConnection):
                         try:
                             yield gen.with_timeout(
                                 self.stream.io_loop.time() + self._body_timeout,
-                                body_future, self.stream.io_loop,
+                                body_future,
                                 quiet_exceptions=iostream.StreamClosedError)
                         except gen.TimeoutError:
                             gen_log.info("Timeout reading body from %s",
diff --git a/tornado/httpclient.py b/tornado/httpclient.py
index 8436ece4..05c62081 100644
--- a/tornado/httpclient.py
+++ b/tornado/httpclient.py
@@ -46,7 +46,7 @@ import weakref
 
 from tornado.concurrent import TracebackFuture
 from tornado.escape import utf8, native_str
-from tornado import httputil, stack_context
+from tornado import gen, httputil, stack_context
 from tornado.ioloop import IOLoop
 from tornado.util import Configurable
 
@@ -75,7 +75,10 @@ class HTTPClient(object):
         self._io_loop = IOLoop(make_current=False)
         if async_client_class is None:
             async_client_class = AsyncHTTPClient
-        self._async_client = async_client_class(self._io_loop, **kwargs)
+        # Create the client while our IOLoop is "current", without
+        # clobbering the thread's real current IOLoop (if any).
+        self._async_client = self._io_loop.run_sync(
+            gen.coroutine(lambda: async_client_class(**kwargs)))
         self._closed = False
 
     def __del__(self):
@@ -120,12 +123,12 @@ class AsyncHTTPClient(Configurable):
     The constructor for this class is magic in several respects: It
     actually creates an instance of an implementation-specific
     subclass, and instances are reused as a kind of pseudo-singleton
-    (one per `.IOLoop`).  The keyword argument ``force_instance=True``
-    can be used to suppress this singleton behavior.  Unless
-    ``force_instance=True`` is used, no arguments other than
-    ``io_loop`` should be passed to the `AsyncHTTPClient` constructor.
-    The implementation subclass as well as arguments to its
-    constructor can be set with the static method `configure()`
+    (one per `.IOLoop`). The keyword argument ``force_instance=True``
+    can be used to suppress this singleton behavior. Unless
+    ``force_instance=True`` is used, no arguments should be passed to
+    the `AsyncHTTPClient` constructor. The implementation subclass as
+    well as arguments to its constructor can be set with the static
+    method `configure()`
 
     All `AsyncHTTPClient` implementations support a ``defaults``
     keyword argument, which can be used to set default values for
@@ -137,8 +140,9 @@ class AsyncHTTPClient(Configurable):
         client = AsyncHTTPClient(force_instance=True,
             defaults=dict(user_agent="MyUserAgent"))
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
+
     """
     @classmethod
     def configurable_base(cls):
@@ -156,16 +160,15 @@ class AsyncHTTPClient(Configurable):
             setattr(cls, attr_name, weakref.WeakKeyDictionary())
         return getattr(cls, attr_name)
 
-    def __new__(cls, io_loop=None, force_instance=False, **kwargs):
-        io_loop = io_loop or IOLoop.current()
+    def __new__(cls, force_instance=False, **kwargs):
+        io_loop = IOLoop.current()
         if force_instance:
             instance_cache = None
         else:
             instance_cache = cls._async_clients()
         if instance_cache is not None and io_loop in instance_cache:
             return instance_cache[io_loop]
-        instance = super(AsyncHTTPClient, cls).__new__(cls, io_loop=io_loop,
-                                                       **kwargs)
+        instance = super(AsyncHTTPClient, cls).__new__(cls, **kwargs)
         # Make sure the instance knows which cache to remove itself from.
         # It can't simply call _async_clients() because we may be in
         # __new__(AsyncHTTPClient) but instance.__class__ may be
@@ -175,8 +178,8 @@ class AsyncHTTPClient(Configurable):
             instance_cache[instance.io_loop] = instance
         return instance
 
-    def initialize(self, io_loop, defaults=None):
-        self.io_loop = io_loop
+    def initialize(self, defaults=None):
+        self.io_loop = IOLoop.current()
         self.defaults = dict(HTTPRequest._DEFAULTS)
         if defaults is not None:
             self.defaults.update(defaults)
diff --git a/tornado/httpserver.py b/tornado/httpserver.py
index d757be18..7a7d08cd 100644
--- a/tornado/httpserver.py
+++ b/tornado/httpserver.py
@@ -134,6 +134,9 @@ class HTTPServer(TCPServer, Configurable,
 
     .. versionchanged:: 4.5
        Added the ``trusted_downstream`` argument.
+
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument has been removed.
     """
     def __init__(self, *args, **kwargs):
         # Ignore args to __init__; real initialization belongs in
@@ -143,7 +146,7 @@ class HTTPServer(TCPServer, Configurable,
         # completely)
         pass
 
-    def initialize(self, request_callback, no_keep_alive=False, io_loop=None,
+    def initialize(self, request_callback, no_keep_alive=False,
                    xheaders=False, ssl_options=None, protocol=None,
                    decompress_request=False,
                    chunk_size=None, max_header_size=None,
@@ -162,7 +165,7 @@ class HTTPServer(TCPServer, Configurable,
             max_body_size=max_body_size,
             body_timeout=body_timeout,
             no_keep_alive=no_keep_alive)
-        TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options,
+        TCPServer.__init__(self, ssl_options=ssl_options,
                            max_buffer_size=max_buffer_size,
                            read_chunk_size=chunk_size)
         self._connections = set()
diff --git a/tornado/ioloop.py b/tornado/ioloop.py
index a1288e77..5997cef6 100644
--- a/tornado/ioloop.py
+++ b/tornado/ioloop.py
@@ -1000,15 +1000,15 @@ class PeriodicCallback(object):
 
     `start` must be called after the `PeriodicCallback` is created.
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
     """
-    def __init__(self, callback, callback_time, io_loop=None):
+    def __init__(self, callback, callback_time):
         self.callback = callback
         if callback_time <= 0:
             raise ValueError("Periodic callback must have a positive callback_time")
         self.callback_time = callback_time
-        self.io_loop = io_loop or IOLoop.current()
+        self.io_loop = IOLoop.current()
         self._running = False
         self._timeout = None
 
diff --git a/tornado/iostream.py b/tornado/iostream.py
index a1619c49..94249028 100644
--- a/tornado/iostream.py
+++ b/tornado/iostream.py
@@ -135,12 +135,10 @@ class BaseIOStream(object):
     Subclasses must implement `fileno`, `close_fd`, `write_to_fd`,
     `read_from_fd`, and optionally `get_fd_error`.
     """
-    def __init__(self, io_loop=None, max_buffer_size=None,
+    def __init__(self, max_buffer_size=None,
                  read_chunk_size=None, max_write_buffer_size=None):
         """`BaseIOStream` constructor.
 
-        :arg io_loop: The `.IOLoop` to use; defaults to `.IOLoop.current`.
-                      Deprecated since Tornado 4.1.
         :arg max_buffer_size: Maximum amount of incoming data to buffer;
             defaults to 100MB.
         :arg read_chunk_size: Amount of data to read at one time from the
@@ -151,8 +149,11 @@ class BaseIOStream(object):
         .. versionchanged:: 4.0
            Add the ``max_write_buffer_size`` parameter.  Changed default
            ``read_chunk_size`` to 64KB.
+        .. versionchanged:: 5.0
+           The ``io_loop`` argument (deprecated since version 4.1) has been
+           removed.
         """
-        self.io_loop = io_loop or ioloop.IOLoop.current()
+        self.io_loop = ioloop.IOLoop.current()
         self.max_buffer_size = max_buffer_size or 104857600
         # A chunk size that is too close to max_buffer_size can cause
         # spurious failures.
@@ -1186,8 +1187,7 @@ class IOStream(BaseIOStream):
         self._close_callback = None
 
         future = TracebackFuture()
-        ssl_stream = SSLIOStream(socket, ssl_options=ssl_options,
-                                 io_loop=self.io_loop)
+        ssl_stream = SSLIOStream(socket, ssl_options=ssl_options)
         # Wrap the original close callback so we can fail our Future as well.
         # If we had an "unwrap" counterpart to this method we would need
         # to restore the original callback after our Future resolves
diff --git a/tornado/netutil.py b/tornado/netutil.py
index c34c8c8b..97f84f13 100644
--- a/tornado/netutil.py
+++ b/tornado/netutil.py
@@ -232,7 +232,7 @@ if hasattr(socket, 'AF_UNIX'):
         return sock
 
 
-def add_accept_handler(sock, callback, io_loop=None):
+def add_accept_handler(sock, callback):
     """Adds an `.IOLoop` event handler to accept new connections on ``sock``.
 
     When a connection is accepted, ``callback(connection, address)`` will
@@ -241,12 +241,9 @@ def add_accept_handler(sock, callback, io_loop=None):
     is different from the ``callback(fd, events)`` signature used for
     `.IOLoop` handlers.
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
     """
-    if io_loop is None:
-        io_loop = IOLoop.current()
-
     def accept_handler(fd, events):
         # More connections may come in while we're handling callbacks;
         # to prevent starvation of other tasks we must limit the number
@@ -274,7 +271,7 @@ def add_accept_handler(sock, callback, io_loop=None):
                     continue
                 raise
             callback(connection, address)
-    io_loop.add_handler(sock, accept_handler, IOLoop.READ)
+    IOLoop.current().add_handler(sock, accept_handler, IOLoop.READ)
 
 
 def is_valid_ip(ip):
@@ -363,11 +360,11 @@ class ExecutorResolver(Resolver):
     ``close_resolver=False``; use this if you want to reuse the same
     executor elsewhere.
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
     """
-    def initialize(self, io_loop=None, executor=None, close_executor=True):
-        self.io_loop = io_loop or IOLoop.current()
+    def initialize(self, executor=None, close_executor=True):
+        self.io_loop = IOLoop.current()
         if executor is not None:
             self.executor = executor
             self.close_executor = close_executor
@@ -400,8 +397,8 @@ class BlockingResolver(ExecutorResolver):
     The `.IOLoop` will be blocked during the resolution, although the
     callback will not be run until the next `.IOLoop` iteration.
     """
-    def initialize(self, io_loop=None):
-        super(BlockingResolver, self).initialize(io_loop=io_loop)
+    def initialize(self):
+        super(BlockingResolver, self).initialize()
 
 
 class ThreadedResolver(ExecutorResolver):
@@ -423,10 +420,10 @@ class ThreadedResolver(ExecutorResolver):
     _threadpool = None  # type: ignore
     _threadpool_pid = None  # type: int
 
-    def initialize(self, io_loop=None, num_threads=10):
+    def initialize(self, num_threads=10):
         threadpool = ThreadedResolver._create_threadpool(num_threads)
         super(ThreadedResolver, self).initialize(
-            io_loop=io_loop, executor=threadpool, close_executor=False)
+            executor=threadpool, close_executor=False)
 
     @classmethod
     def _create_threadpool(cls, num_threads):
diff --git a/tornado/platform/caresresolver.py b/tornado/platform/caresresolver.py
index fd6e9d27..38247f7e 100644
--- a/tornado/platform/caresresolver.py
+++ b/tornado/platform/caresresolver.py
@@ -19,11 +19,11 @@ class CaresResolver(Resolver):
     the default for ``tornado.simple_httpclient``, but other libraries
     may default to ``AF_UNSPEC``.
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
     """
-    def initialize(self, io_loop=None):
-        self.io_loop = io_loop or IOLoop.current()
+    def initialize(self):
+        self.io_loop = IOLoop.current()
         self.channel = pycares.Channel(sock_state_cb=self._sock_state_cb)
         self.fds = {}
 
diff --git a/tornado/platform/twisted.py b/tornado/platform/twisted.py
index 0f9787e8..79608e5f 100644
--- a/tornado/platform/twisted.py
+++ b/tornado/platform/twisted.py
@@ -112,7 +112,7 @@ class TornadoReactor(PosixReactorBase):
     instead of ``reactor.run()``.
 
     It is also possible to create a non-global reactor by calling
-    ``tornado.platform.twisted.TornadoReactor(io_loop)``.  However, if
+    ``tornado.platform.twisted.TornadoReactor()``.  However, if
     the `.IOLoop` and reactor are to be short-lived (such as those used in
     unit tests), additional cleanup may be required.  Specifically, it is
     recommended to call::
@@ -122,13 +122,11 @@ class TornadoReactor(PosixReactorBase):
 
     before closing the `.IOLoop`.
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
     """
-    def __init__(self, io_loop=None):
-        if not io_loop:
-            io_loop = tornado.ioloop.IOLoop.current()
-        self._io_loop = io_loop
+    def __init__(self):
+        self._io_loop = tornado.ioloop.IOLoop.current()
         self._readers = {}  # map of reader objects to fd
         self._writers = {}  # map of writer objects to fd
         self._fds = {}  # a map of fd to a (reader, writer) tuple
@@ -319,7 +317,9 @@ class _TestReactor(TornadoReactor):
     """
     def __init__(self):
         # always use a new ioloop
-        super(_TestReactor, self).__init__(IOLoop())
+        IOLoop(make_current=True)
+        super(_TestReactor, self).__init__()
+        IOLoop.clear_current()
 
     def listenTCP(self, port, factory, backlog=50, interface=''):
         # default to localhost to avoid firewall prompts on the mac
@@ -335,7 +335,7 @@ class _TestReactor(TornadoReactor):
             port, protocol, interface=interface, maxPacketSize=maxPacketSize)
 
 
-def install(io_loop=None):
+def install():
     """Install this package as the default Twisted reactor.
 
     ``install()`` must be called very early in the startup process,
@@ -346,13 +346,11 @@ def install(io_loop=None):
     in multi-process mode, and an external process manager such as
     ``supervisord`` is recommended instead.
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
 
     """
-    if not io_loop:
-        io_loop = tornado.ioloop.IOLoop.current()
-    reactor = TornadoReactor(io_loop)
+    reactor = TornadoReactor()
     from twisted.internet.main import installReactor  # type: ignore
     installReactor(reactor)
     return reactor
@@ -526,14 +524,13 @@ class TwistedResolver(Resolver):
 
     Requires Twisted 12.1 or newer.
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
     """
-    def initialize(self, io_loop=None):
-        self.io_loop = io_loop or IOLoop.current()
+    def initialize(self):
         # partial copy of twisted.names.client.createResolver, which doesn't
         # allow for a reactor to be passed in.
-        self.reactor = tornado.platform.twisted.TornadoReactor(io_loop)
+        self.reactor = tornado.platform.twisted.TornadoReactor()
 
         host_resolver = twisted.names.hosts.Resolver('/etc/hosts')
         cache_resolver = twisted.names.cache.CacheResolver(reactor=self.reactor)
diff --git a/tornado/process.py b/tornado/process.py
index fae94f3c..594913c6 100644
--- a/tornado/process.py
+++ b/tornado/process.py
@@ -200,15 +200,14 @@ class Subprocess(object):
     * ``stdin``, ``stdout``, and ``stderr`` may have the value
       ``tornado.process.Subprocess.STREAM``, which will make the corresponding
       attribute of the resulting Subprocess a `.PipeIOStream`.
-    * A new keyword argument ``io_loop`` may be used to pass in an IOLoop.
 
     The ``Subprocess.STREAM`` option and the ``set_exit_callback`` and
     ``wait_for_exit`` methods do not work on Windows. There is
     therefore no reason to use this class instead of
     ``subprocess.Popen`` on that platform.
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
 
     """
     STREAM = object()
@@ -217,7 +216,7 @@ class Subprocess(object):
     _waiting = {}  # type: ignore
 
     def __init__(self, *args, **kwargs):
-        self.io_loop = kwargs.pop('io_loop', None) or ioloop.IOLoop.current()
+        self.io_loop = ioloop.IOLoop.current()
         # All FDs we create should be closed on error; those in to_close
         # should be closed in the parent process on success.
         pipe_fds = []
@@ -227,19 +226,19 @@ class Subprocess(object):
             kwargs['stdin'] = in_r
             pipe_fds.extend((in_r, in_w))
             to_close.append(in_r)
-            self.stdin = PipeIOStream(in_w, io_loop=self.io_loop)
+            self.stdin = PipeIOStream(in_w)
         if kwargs.get('stdout') is Subprocess.STREAM:
             out_r, out_w = _pipe_cloexec()
             kwargs['stdout'] = out_w
             pipe_fds.extend((out_r, out_w))
             to_close.append(out_w)
-            self.stdout = PipeIOStream(out_r, io_loop=self.io_loop)
+            self.stdout = PipeIOStream(out_r)
         if kwargs.get('stderr') is Subprocess.STREAM:
             err_r, err_w = _pipe_cloexec()
             kwargs['stderr'] = err_w
             pipe_fds.extend((err_r, err_w))
             to_close.append(err_w)
-            self.stderr = PipeIOStream(err_r, io_loop=self.io_loop)
+            self.stderr = PipeIOStream(err_r)
         try:
             self.proc = subprocess.Popen(*args, **kwargs)
         except:
@@ -270,7 +269,7 @@ class Subprocess(object):
         signal handler is causing a problem.
         """
         self._exit_callback = stack_context.wrap(callback)
-        Subprocess.initialize(self.io_loop)
+        Subprocess.initialize()
         Subprocess._waiting[self.pid] = self
         Subprocess._try_cleanup_process(self.pid)
 
@@ -302,7 +301,7 @@ class Subprocess(object):
         return future
 
     @classmethod
-    def initialize(cls, io_loop=None):
+    def initialize(cls):
         """Initializes the ``SIGCHLD`` handler.
 
         The signal handler is run on an `.IOLoop` to avoid locking issues.
@@ -310,13 +309,13 @@ class Subprocess(object):
         same one used by individual Subprocess objects (as long as the
         ``IOLoops`` are each running in separate threads).
 
-        .. versionchanged:: 4.1
-           The ``io_loop`` argument is deprecated.
+        .. versionchanged:: 5.0
+           The ``io_loop`` argument (deprecated since version 4.1) has been
+           removed.
         """
         if cls._initialized:
             return
-        if io_loop is None:
-            io_loop = ioloop.IOLoop.current()
+        io_loop = ioloop.IOLoop.current()
         cls._old_sigchld = signal.signal(
             signal.SIGCHLD,
             lambda sig, frame: io_loop.add_callback_from_signal(cls._cleanup))
diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py
index 8fb70707..f394689d 100644
--- a/tornado/simple_httpclient.py
+++ b/tornado/simple_httpclient.py
@@ -6,6 +6,7 @@ from tornado import gen
 from tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main, _RequestProxy
 from tornado import httputil
 from tornado.http1connection import HTTP1Connection, HTTP1ConnectionParameters
+from tornado.ioloop import IOLoop
 from tornado.iostream import StreamClosedError
 from tornado.netutil import Resolver, OverrideResolver, _client_ssl_defaults
 from tornado.log import gen_log
@@ -56,7 +57,7 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient):
     are not reused, and callers cannot select the network interface to be
     used.
     """
-    def initialize(self, io_loop, max_clients=10,
+    def initialize(self, max_clients=10,
                    hostname_mapping=None, max_buffer_size=104857600,
                    resolver=None, defaults=None, max_header_size=None,
                    max_body_size=None):
@@ -92,8 +93,7 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient):
         .. versionchanged:: 4.2
            Added the ``max_body_size`` argument.
         """
-        super(SimpleAsyncHTTPClient, self).initialize(io_loop,
-                                                      defaults=defaults)
+        super(SimpleAsyncHTTPClient, self).initialize(defaults=defaults)
         self.max_clients = max_clients
         self.queue = collections.deque()
         self.active = {}
@@ -107,12 +107,12 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient):
             self.resolver = resolver
             self.own_resolver = False
         else:
-            self.resolver = Resolver(io_loop=io_loop)
+            self.resolver = Resolver()
             self.own_resolver = True
         if hostname_mapping is not None:
             self.resolver = OverrideResolver(resolver=self.resolver,
                                              mapping=hostname_mapping)
-        self.tcp_client = TCPClient(resolver=self.resolver, io_loop=io_loop)
+        self.tcp_client = TCPClient(resolver=self.resolver)
 
     def close(self):
         super(SimpleAsyncHTTPClient, self).close()
@@ -153,7 +153,7 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient):
 
     def _handle_request(self, request, release_callback, final_callback):
         self._connection_class()(
-            self.io_loop, self, request, release_callback,
+            self, request, release_callback,
             final_callback, self.max_buffer_size, self.tcp_client,
             self.max_header_size, self.max_body_size)
 
@@ -190,11 +190,11 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient):
 class _HTTPConnection(httputil.HTTPMessageDelegate):
     _SUPPORTED_METHODS = set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
 
-    def __init__(self, io_loop, client, request, release_callback,
+    def __init__(self, client, request, release_callback,
                  final_callback, max_buffer_size, tcp_client,
                  max_header_size, max_body_size):
-        self.start_time = io_loop.time()
-        self.io_loop = io_loop
+        self.io_loop = IOLoop.current()
+        self.start_time = self.io_loop.time()
         self.client = client
         self.request = request
         self.release_callback = release_callback
diff --git a/tornado/tcpclient.py b/tornado/tcpclient.py
index 33074bd5..ec51a613 100644
--- a/tornado/tcpclient.py
+++ b/tornado/tcpclient.py
@@ -47,8 +47,8 @@ class _Connector(object):
     http://tools.ietf.org/html/rfc6555
 
     """
-    def __init__(self, addrinfo, io_loop, connect):
-        self.io_loop = io_loop
+    def __init__(self, addrinfo, connect):
+        self.io_loop = IOLoop.current()
         self.connect = connect
 
         self.future = Future()
@@ -137,16 +137,15 @@ class _Connector(object):
 class TCPClient(object):
     """A non-blocking TCP connection factory.
 
-    .. versionchanged:: 4.1
-       The ``io_loop`` argument is deprecated.
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
     """
-    def __init__(self, resolver=None, io_loop=None):
-        self.io_loop = io_loop or IOLoop.current()
+    def __init__(self, resolver=None):
         if resolver is not None:
             self.resolver = resolver
             self._own_resolver = False
         else:
-            self.resolver = Resolver(io_loop=io_loop)
+            self.resolver = Resolver()
             self._own_resolver = True
 
     def close(self):
@@ -175,7 +174,7 @@ class TCPClient(object):
         """
         addrinfo = yield self.resolver.resolve(host, port, af)
         connector = _Connector(
-            addrinfo, self.io_loop,
+            addrinfo,
             functools.partial(self._create_stream, max_buffer_size,
                               source_ip=source_ip, source_port=source_port)
         )
@@ -212,7 +211,6 @@ class TCPClient(object):
                 raise
         try:
             stream = IOStream(socket_obj,
-                              io_loop=self.io_loop,
                               max_buffer_size=max_buffer_size)
         except socket.error as e:
             fu = Future()
diff --git a/tornado/tcpserver.py b/tornado/tcpserver.py
index f47ec89a..b3cdaa2c 100644
--- a/tornado/tcpserver.py
+++ b/tornado/tcpserver.py
@@ -102,10 +102,13 @@ class TCPServer(object):
 
     .. versionadded:: 3.1
        The ``max_buffer_size`` argument.
+
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument has been removed.
     """
-    def __init__(self, io_loop=None, ssl_options=None, max_buffer_size=None,
+    def __init__(self, ssl_options=None, max_buffer_size=None,
                  read_chunk_size=None):
-        self.io_loop = io_loop
+        self.io_loop = IOLoop.current()
         self.ssl_options = ssl_options
         self._sockets = {}  # fd -> socket object
         self._pending_sockets = []
@@ -151,13 +154,9 @@ class TCPServer(object):
         method and `tornado.process.fork_processes` to provide greater
         control over the initialization of a multi-process server.
         """
-        if self.io_loop is None:
-            self.io_loop = IOLoop.current()
-
         for sock in sockets:
             self._sockets[sock.fileno()] = sock
-            add_accept_handler(sock, self._handle_connection,
-                               io_loop=self.io_loop)
+            add_accept_handler(sock, self._handle_connection)
 
     def add_socket(self, socket):
         """Singular version of `add_sockets`.  Takes a single socket object."""
@@ -284,11 +283,11 @@ class TCPServer(object):
                     raise
         try:
             if self.ssl_options is not None:
-                stream = SSLIOStream(connection, io_loop=self.io_loop,
+                stream = SSLIOStream(connection,
                                      max_buffer_size=self.max_buffer_size,
                                      read_chunk_size=self.read_chunk_size)
             else:
-                stream = IOStream(connection, io_loop=self.io_loop,
+                stream = IOStream(connection,
                                   max_buffer_size=self.max_buffer_size,
                                   read_chunk_size=self.read_chunk_size)
 
diff --git a/tornado/websocket.py b/tornado/websocket.py
index 7600910c..d5a7fa89 100644
--- a/tornado/websocket.py
+++ b/tornado/websocket.py
@@ -1040,7 +1040,7 @@ class WebSocketClientConnection(simple_httpclient._HTTPConnection):
     This class should not be instantiated directly; use the
     `websocket_connect` function instead.
     """
-    def __init__(self, io_loop, request, on_message_callback=None,
+    def __init__(self, request, on_message_callback=None,
                  compression_options=None, ping_interval=None, ping_timeout=None,
                  max_message_size=None):
         self.compression_options = compression_options
@@ -1073,9 +1073,9 @@ class WebSocketClientConnection(simple_httpclient._HTTPConnection):
             request.headers['Sec-WebSocket-Extensions'] = (
                 'permessage-deflate; client_max_window_bits')
 
-        self.tcp_client = TCPClient(io_loop=io_loop)
+        self.tcp_client = TCPClient()
         super(WebSocketClientConnection, self).__init__(
-            io_loop, None, request, lambda: None, self._on_http_response,
+            None, request, lambda: None, self._on_http_response,
             104857600, self.tcp_client, 65536, 104857600)
 
     def close(self, code=None, reason=None):
@@ -1179,7 +1179,7 @@ class WebSocketClientConnection(simple_httpclient._HTTPConnection):
                                    compression_options=self.compression_options)
 
 
-def websocket_connect(url, io_loop=None, callback=None, connect_timeout=None,
+def websocket_connect(url, callback=None, connect_timeout=None,
                       on_message_callback=None, compression_options=None,
                       ping_interval=None, ping_timeout=None,
                       max_message_size=None):
@@ -1210,14 +1210,14 @@ def websocket_connect(url, io_loop=None, callback=None, connect_timeout=None,
 
     .. versionchanged:: 4.1
        Added ``compression_options`` and ``on_message_callback``.
-       The ``io_loop`` argument is deprecated.
 
     .. versionchanged:: 4.5
        Added the ``ping_interval``, ``ping_timeout``, and ``max_message_size``
        arguments, which have the same meaning as in `WebSocketHandler`.
+
+    .. versionchanged:: 5.0
+       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
     """
-    if io_loop is None:
-        io_loop = IOLoop.current()
     if isinstance(url, httpclient.HTTPRequest):
         assert connect_timeout is None
         request = url
@@ -1228,12 +1228,12 @@ def websocket_connect(url, io_loop=None, callback=None, connect_timeout=None,
         request = httpclient.HTTPRequest(url, connect_timeout=connect_timeout)
     request = httpclient._RequestProxy(
         request, httpclient.HTTPRequest._DEFAULTS)
-    conn = WebSocketClientConnection(io_loop, request,
+    conn = WebSocketClientConnection(request,
                                      on_message_callback=on_message_callback,
                                      compression_options=compression_options,
                                      ping_interval=ping_interval,
                                      ping_timeout=ping_timeout,
                                      max_message_size=max_message_size)
     if callback is not None:
-        io_loop.add_future(conn.connect_future, callback)
+        IOLoop.current().add_future(conn.connect_future, callback)
     return conn.connect_future


Test patch:
diff --git a/docs/testing.rst b/docs/testing.rst
index 334d2587..c8219738 100644
--- a/docs/testing.rst
+++ b/docs/testing.rst
@@ -36,6 +36,4 @@
 
    .. autofunction:: bind_unused_port
 
-   .. autofunction:: get_unused_port
-
    .. autofunction:: get_async_test_timeout
diff --git a/tornado/test/concurrent_test.py b/tornado/test/concurrent_test.py
index 4d89f572..d1e266ff 100644
--- a/tornado/test/concurrent_test.py
+++ b/tornado/test/concurrent_test.py
@@ -25,6 +25,7 @@ import traceback
 from tornado.concurrent import Future, return_future, ReturnValueIgnoredError, run_on_executor
 from tornado.escape import utf8, to_unicode
 from tornado import gen
+from tornado.ioloop import IOLoop
 from tornado.iostream import IOStream
 from tornado.log import app_log
 from tornado import stack_context
@@ -217,9 +218,8 @@ class CapError(Exception):
 
 
 class BaseCapClient(object):
-    def __init__(self, port, io_loop):
+    def __init__(self, port):
         self.port = port
-        self.io_loop = io_loop
 
     def process_response(self, data):
         status, message = re.match('(.*)\t(.*)\n', to_unicode(data)).groups()
@@ -233,7 +233,7 @@ class ManualCapClient(BaseCapClient):
     def capitalize(self, request_data, callback=None):
         logging.info("capitalize")
         self.request_data = request_data
-        self.stream = IOStream(socket.socket(), io_loop=self.io_loop)
+        self.stream = IOStream(socket.socket())
         self.stream.connect(('127.0.0.1', self.port),
                             callback=self.handle_connect)
         self.future = Future()
@@ -261,7 +261,7 @@ class DecoratorCapClient(BaseCapClient):
     def capitalize(self, request_data, callback):
         logging.info("capitalize")
         self.request_data = request_data
-        self.stream = IOStream(socket.socket(), io_loop=self.io_loop)
+        self.stream = IOStream(socket.socket())
         self.stream.connect(('127.0.0.1', self.port),
                             callback=self.handle_connect)
         self.callback = callback
@@ -282,7 +282,7 @@ class GeneratorCapClient(BaseCapClient):
     @gen.engine
     def capitalize(self, request_data, callback):
         logging.info('capitalize')
-        stream = IOStream(socket.socket(), io_loop=self.io_loop)
+        stream = IOStream(socket.socket())
         logging.info('connecting')
         yield gen.Task(stream.connect, ('127.0.0.1', self.port))
         stream.write(utf8(request_data + '\n'))
@@ -296,10 +296,10 @@ class GeneratorCapClient(BaseCapClient):
 class ClientTestMixin(object):
     def setUp(self):
         super(ClientTestMixin, self).setUp()  # type: ignore
-        self.server = CapServer(io_loop=self.io_loop)
+        self.server = CapServer()
         sock, port = bind_unused_port()
         self.server.add_sockets([sock])
-        self.client = self.client_class(io_loop=self.io_loop, port=port)
+        self.client = self.client_class(port=port)
 
     def tearDown(self):
         self.server.stop()
@@ -362,74 +362,41 @@ class RunOnExecutorTest(AsyncTestCase):
     @gen_test
     def test_no_calling(self):
         class Object(object):
-            def __init__(self, io_loop):
-                self.io_loop = io_loop
+            def __init__(self):
                 self.executor = futures.thread.ThreadPoolExecutor(1)
 
             @run_on_executor
             def f(self):
                 return 42
 
-        o = Object(io_loop=self.io_loop)
+        o = Object()
         answer = yield o.f()
         self.assertEqual(answer, 42)
 
     @gen_test
     def test_call_with_no_args(self):
         class Object(object):
-            def __init__(self, io_loop):
-                self.io_loop = io_loop
+            def __init__(self):
                 self.executor = futures.thread.ThreadPoolExecutor(1)
 
             @run_on_executor()
             def f(self):
                 return 42
 
-        o = Object(io_loop=self.io_loop)
-        answer = yield o.f()
-        self.assertEqual(answer, 42)
-
-    @gen_test
-    def test_call_with_io_loop(self):
-        class Object(object):
-            def __init__(self, io_loop):
-                self._io_loop = io_loop
-                self.executor = futures.thread.ThreadPoolExecutor(1)
-
-            @run_on_executor(io_loop='_io_loop')
-            def f(self):
-                return 42
-
-        o = Object(io_loop=self.io_loop)
+        o = Object()
         answer = yield o.f()
         self.assertEqual(answer, 42)
 
     @gen_test
     def test_call_with_executor(self):
         class Object(object):
-            def __init__(self, io_loop):
-                self.io_loop = io_loop
+            def __init__(self):
                 self.__executor = futures.thread.ThreadPoolExecutor(1)
 
             @run_on_executor(executor='_Object__executor')
             def f(self):
                 return 42
 
-        o = Object(io_loop=self.io_loop)
-        answer = yield o.f()
-        self.assertEqual(answer, 42)
-
-    @gen_test
-    def test_call_with_both(self):
-        class Object(object):
-            def __init__(self, io_loop):
-                self._io_loop = io_loop
-                self.__executor = futures.thread.ThreadPoolExecutor(1)
-
-            @run_on_executor(io_loop='_io_loop', executor='_Object__executor')
-            def f(self):
-                return 42
-
-        o = Object(io_loop=self.io_loop)
+        o = Object()
         answer = yield o.f()
         self.assertEqual(answer, 42)
diff --git a/tornado/test/curl_httpclient_test.py b/tornado/test/curl_httpclient_test.py
index eb6f89d6..35d75011 100644
--- a/tornado/test/curl_httpclient_test.py
+++ b/tornado/test/curl_httpclient_test.py
@@ -24,8 +24,7 @@ if pycurl is not None:
 @unittest.skipIf(pycurl is None, "pycurl module not present")
 class CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):
     def get_http_client(self):
-        client = CurlAsyncHTTPClient(io_loop=self.io_loop,
-                                     defaults=dict(allow_ipv6=False))
+        client = CurlAsyncHTTPClient(defaults=dict(allow_ipv6=False))
         # make sure AsyncHTTPClient magic doesn't give us the wrong class
         self.assertTrue(isinstance(client, CurlAsyncHTTPClient))
         return client
@@ -94,7 +93,7 @@ class CurlHTTPClientTestCase(AsyncHTTPTestCase):
         ])
 
     def create_client(self, **kwargs):
-        return CurlAsyncHTTPClient(self.io_loop, force_instance=True,
+        return CurlAsyncHTTPClient(force_instance=True,
                                    defaults=dict(allow_ipv6=False),
                                    **kwargs)
 
diff --git a/tornado/test/gen_test.py b/tornado/test/gen_test.py
index fea4c644..8183c164 100644
--- a/tornado/test/gen_test.py
+++ b/tornado/test/gen_test.py
@@ -1096,8 +1096,7 @@ class GenTaskHandler(RequestHandler):
     @asynchronous
     @gen.engine
     def get(self):
-        io_loop = self.request.connection.stream.io_loop
-        client = AsyncHTTPClient(io_loop=io_loop)
+        client = AsyncHTTPClient()
         response = yield gen.Task(client.fetch, self.get_argument('url'))
         response.rethrow()
         self.finish(b"got response: " + response.body)
@@ -1250,7 +1249,7 @@ class WithTimeoutTest(AsyncTestCase):
         self.io_loop.add_timeout(datetime.timedelta(seconds=0.1),
                                  lambda: future.set_result('asdf'))
         result = yield gen.with_timeout(datetime.timedelta(seconds=3600),
-                                        future, io_loop=self.io_loop)
+                                        future)
         self.assertEqual(result, 'asdf')
 
     @gen_test
@@ -1261,14 +1260,14 @@ class WithTimeoutTest(AsyncTestCase):
             lambda: future.set_exception(ZeroDivisionError()))
         with self.assertRaises(ZeroDivisionError):
             yield gen.with_timeout(datetime.timedelta(seconds=3600),
-                                   future, io_loop=self.io_loop)
+                                   future)
 
     @gen_test
     def test_already_resolved(self):
         future = Future()
         future.set_result('asdf')
         result = yield gen.with_timeout(datetime.timedelta(seconds=3600),
-                                        future, io_loop=self.io_loop)
+                                        future)
         self.assertEqual(result, 'asdf')
 
     @unittest.skipIf(futures is None, 'futures module not present')
diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py
index 320454e4..1fa42b7e 100644
--- a/tornado/test/httpclient_test.py
+++ b/tornado/test/httpclient_test.py
@@ -210,10 +210,10 @@ Transfer-Encoding: chunked
             def accept_callback(conn, address):
                 # fake an HTTP server using chunked encoding where the final chunks
                 # and connection close all happen at once
-                stream = IOStream(conn, io_loop=self.io_loop)
+                stream = IOStream(conn)
                 stream.read_until(b"\r\n\r\n",
                                   functools.partial(write_response, stream))
-            netutil.add_accept_handler(sock, accept_callback, self.io_loop)
+            netutil.add_accept_handler(sock, accept_callback)
             self.http_client.fetch("http://127.0.0.1:%d/" % port, self.stop)
             resp = self.wait()
             resp.rethrow()
@@ -358,7 +358,7 @@ Transfer-Encoding: chunked
     def test_configure_defaults(self):
         defaults = dict(user_agent='TestDefaultUserAgent', allow_ipv6=False)
         # Construct a new instance of the configured client class
-        client = self.http_client.__class__(self.io_loop, force_instance=True,
+        client = self.http_client.__class__(force_instance=True,
                                             defaults=defaults)
         try:
             client.fetch(self.get_url('/user_agent'), callback=self.stop)
@@ -398,10 +398,10 @@ X-XSS-Protection: 1;
 """.replace(b"\n", b"\r\n"), callback=stream.close)
 
             def accept_callback(conn, address):
-                stream = IOStream(conn, io_loop=self.io_loop)
+                stream = IOStream(conn)
                 stream.read_until(b"\r\n\r\n",
                                   functools.partial(write_response, stream))
-            netutil.add_accept_handler(sock, accept_callback, self.io_loop)
+            netutil.add_accept_handler(sock, accept_callback)
             self.http_client.fetch("http://127.0.0.1:%d/" % port, self.stop)
             resp = self.wait()
             resp.rethrow()
@@ -594,10 +594,13 @@ class SyncHTTPClientTest(unittest.TestCase):
                 'AsyncIOMainLoop')
         self.server_ioloop = IOLoop()
 
-        sock, self.port = bind_unused_port()
-        app = Application([('/', HelloWorldHandler)])
-        self.server = HTTPServer(app, io_loop=self.server_ioloop)
-        self.server.add_socket(sock)
+        @gen.coroutine
+        def init_server():
+            sock, self.port = bind_unused_port()
+            app = Application([('/', HelloWorldHandler)])
+            self.server = HTTPServer(app)
+            self.server.add_socket(sock)
+        self.server_ioloop.run_sync(init_server)
 
         self.server_thread = threading.Thread(target=self.server_ioloop.start)
         self.server_thread.start()
diff --git a/tornado/test/httpserver_test.py b/tornado/test/httpserver_test.py
index 11cb7231..f5f91a9d 100644
--- a/tornado/test/httpserver_test.py
+++ b/tornado/test/httpserver_test.py
@@ -256,7 +256,7 @@ class HTTPConnectionTest(AsyncHTTPTestCase):
         # Run through a 100-continue interaction by hand:
         # When given Expect: 100-continue, we get a 100 response after the
         # headers, and then the real response after the body.
-        stream = IOStream(socket.socket(), io_loop=self.io_loop)
+        stream = IOStream(socket.socket())
         stream.connect(("127.0.0.1", self.get_http_port()), callback=self.stop)
         self.wait()
         stream.write(b"\r\n".join([b"POST /hello HTTP/1.1",
@@ -597,9 +597,9 @@ class UnixSocketTest(AsyncTestCase):
         self.sockfile = os.path.join(self.tmpdir, "test.sock")
         sock = netutil.bind_unix_socket(self.sockfile)
         app = Application([("/hello", HelloWorldRequestHandler)])
-        self.server = HTTPServer(app, io_loop=self.io_loop)
+        self.server = HTTPServer(app)
         self.server.add_socket(sock)
-        self.stream = IOStream(socket.socket(socket.AF_UNIX), io_loop=self.io_loop)
+        self.stream = IOStream(socket.socket(socket.AF_UNIX))
         self.stream.connect(self.sockfile, self.stop)
         self.wait()
 
@@ -681,7 +681,7 @@ class KeepAliveTest(AsyncHTTPTestCase):
 
     # The next few methods are a crude manual http client
     def connect(self):
-        self.stream = IOStream(socket.socket(), io_loop=self.io_loop)
+        self.stream = IOStream(socket.socket())
         self.stream.connect(('127.0.0.1', self.get_http_port()), self.stop)
         self.wait()
 
@@ -842,7 +842,7 @@ class StreamingChunkSizeTest(AsyncHTTPTestCase):
     def get_http_client(self):
         # body_producer doesn't work on curl_httpclient, so override the
         # configured AsyncHTTPClient implementation.
-        return SimpleAsyncHTTPClient(io_loop=self.io_loop)
+        return SimpleAsyncHTTPClient()
 
     def get_httpserver_options(self):
         return dict(chunk_size=self.CHUNK_SIZE, decompress_request=True)
@@ -1025,7 +1025,7 @@ class BodyLimitsTest(AsyncHTTPTestCase):
     def get_http_client(self):
         # body_producer doesn't work on curl_httpclient, so override the
         # configured AsyncHTTPClient implementation.
-        return SimpleAsyncHTTPClient(io_loop=self.io_loop)
+        return SimpleAsyncHTTPClient()
 
     def test_small_body(self):
         response = self.fetch('/buffered', method='PUT', body=b'a' * 4096)
diff --git a/tornado/test/iostream_test.py b/tornado/test/iostream_test.py
index 91bc7bf6..03016447 100644
--- a/tornado/test/iostream_test.py
+++ b/tornado/test/iostream_test.py
@@ -185,8 +185,7 @@ class TestIOStreamMixin(object):
         def connect_callback():
             streams[1] = client_stream
             self.stop()
-        netutil.add_accept_handler(listener, accept_callback,
-                                   io_loop=self.io_loop)
+        netutil.add_accept_handler(listener, accept_callback)
         client_stream = self._make_client_iostream(socket.socket(), **kwargs)
         client_stream.connect(('127.0.0.1', port),
                               callback=connect_callback)
@@ -227,7 +226,7 @@ class TestIOStreamMixin(object):
         # epoll IOLoop in this respect)
         cleanup_func, port = refusing_port()
         self.addCleanup(cleanup_func)
-        stream = IOStream(socket.socket(), self.io_loop)
+        stream = IOStream(socket.socket())
         self.connect_called = False
 
         def connect_callback():
@@ -255,7 +254,7 @@ class TestIOStreamMixin(object):
         # so we mock it instead. If IOStream changes to call a Resolver
         # before sock.connect, the mock target will need to change too.
         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
-        stream = IOStream(s, io_loop=self.io_loop)
+        stream = IOStream(s)
         stream.set_close_callback(self.stop)
         with mock.patch('socket.socket.connect',
                         side_effect=socket.gaierror(errno.EIO, 'boom')):
@@ -845,12 +844,12 @@ class TestIOStreamMixin(object):
 
 class TestIOStreamWebHTTP(TestIOStreamWebMixin, AsyncHTTPTestCase):
     def _make_client_iostream(self):
-        return IOStream(socket.socket(), io_loop=self.io_loop)
+        return IOStream(socket.socket())
 
 
 class TestIOStreamWebHTTPS(TestIOStreamWebMixin, AsyncHTTPSTestCase):
     def _make_client_iostream(self):
-        return SSLIOStream(socket.socket(), io_loop=self.io_loop,
+        return SSLIOStream(socket.socket(),
                            ssl_options=dict(cert_reqs=ssl.CERT_NONE))
 
 
@@ -868,10 +867,10 @@ class TestIOStreamSSL(TestIOStreamMixin, AsyncTestCase):
                                      server_side=True,
                                      do_handshake_on_connect=False,
                                      **_server_ssl_options())
-        return SSLIOStream(connection, io_loop=self.io_loop, **kwargs)
+        return SSLIOStream(connection, **kwargs)
 
     def _make_client_iostream(self, connection, **kwargs):
-        return SSLIOStream(connection, io_loop=self.io_loop,
+        return SSLIOStream(connection,
                            ssl_options=dict(cert_reqs=ssl.CERT_NONE),
                            **kwargs)
 
@@ -889,12 +888,11 @@ class TestIOStreamSSLContext(TestIOStreamMixin, AsyncTestCase):
         connection = ssl_wrap_socket(connection, context,
                                      server_side=True,
                                      do_handshake_on_connect=False)
-        return SSLIOStream(connection, io_loop=self.io_loop, **kwargs)
+        return SSLIOStream(connection, **kwargs)
 
     def _make_client_iostream(self, connection, **kwargs):
         context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
-        return SSLIOStream(connection, io_loop=self.io_loop,
-                           ssl_options=context, **kwargs)
+        return SSLIOStream(connection, ssl_options=context, **kwargs)
 
 
 class TestIOStreamStartTLS(AsyncTestCase):
@@ -1100,8 +1098,8 @@ class TestPipeIOStream(AsyncTestCase):
     def test_pipe_iostream(self):
         r, w = os.pipe()
 
-        rs = PipeIOStream(r, io_loop=self.io_loop)
-        ws = PipeIOStream(w, io_loop=self.io_loop)
+        rs = PipeIOStream(r)
+        ws = PipeIOStream(w)
 
         ws.write(b"hel")
         ws.write(b"lo world")
@@ -1125,8 +1123,8 @@ class TestPipeIOStream(AsyncTestCase):
     def test_pipe_iostream_big_write(self):
         r, w = os.pipe()
 
-        rs = PipeIOStream(r, io_loop=self.io_loop)
-        ws = PipeIOStream(w, io_loop=self.io_loop)
+        rs = PipeIOStream(r)
+        ws = PipeIOStream(w)
 
         NUM_BYTES = 1048576
 
diff --git a/tornado/test/netutil_test.py b/tornado/test/netutil_test.py
index 9564290a..be581764 100644
--- a/tornado/test/netutil_test.py
+++ b/tornado/test/netutil_test.py
@@ -78,7 +78,7 @@ def _failing_getaddrinfo(*args):
 class BlockingResolverTest(AsyncTestCase, _ResolverTestMixin):
     def setUp(self):
         super(BlockingResolverTest, self).setUp()
-        self.resolver = BlockingResolver(io_loop=self.io_loop)
+        self.resolver = BlockingResolver()
 
 
 # getaddrinfo-based tests need mocking to reliably generate errors;
@@ -87,7 +87,7 @@ class BlockingResolverTest(AsyncTestCase, _ResolverTestMixin):
 class BlockingResolverErrorTest(AsyncTestCase, _ResolverErrorTestMixin):
     def setUp(self):
         super(BlockingResolverErrorTest, self).setUp()
-        self.resolver = BlockingResolver(io_loop=self.io_loop)
+        self.resolver = BlockingResolver()
         self.real_getaddrinfo = socket.getaddrinfo
         socket.getaddrinfo = _failing_getaddrinfo
 
@@ -101,7 +101,7 @@ class BlockingResolverErrorTest(AsyncTestCase, _ResolverErrorTestMixin):
 class ThreadedResolverTest(AsyncTestCase, _ResolverTestMixin):
     def setUp(self):
         super(ThreadedResolverTest, self).setUp()
-        self.resolver = ThreadedResolver(io_loop=self.io_loop)
+        self.resolver = ThreadedResolver()
 
     def tearDown(self):
         self.resolver.close()
@@ -111,7 +111,7 @@ class ThreadedResolverTest(AsyncTestCase, _ResolverTestMixin):
 class ThreadedResolverErrorTest(AsyncTestCase, _ResolverErrorTestMixin):
     def setUp(self):
         super(ThreadedResolverErrorTest, self).setUp()
-        self.resolver = BlockingResolver(io_loop=self.io_loop)
+        self.resolver = BlockingResolver()
         self.real_getaddrinfo = socket.getaddrinfo
         socket.getaddrinfo = _failing_getaddrinfo
 
@@ -158,7 +158,7 @@ class ThreadedResolverImportTest(unittest.TestCase):
 class CaresResolverTest(AsyncTestCase, _ResolverTestMixin):
     def setUp(self):
         super(CaresResolverTest, self).setUp()
-        self.resolver = CaresResolver(io_loop=self.io_loop)
+        self.resolver = CaresResolver()
 
 
 # TwistedResolver produces consistent errors in our test cases so we
@@ -170,7 +170,7 @@ class TwistedResolverTest(AsyncTestCase, _ResolverTestMixin,
                           _ResolverErrorTestMixin):
     def setUp(self):
         super(TwistedResolverTest, self).setUp()
-        self.resolver = TwistedResolver(io_loop=self.io_loop)
+        self.resolver = TwistedResolver()
 
 
 class IsValidIPTest(unittest.TestCase):
diff --git a/tornado/test/process_test.py b/tornado/test/process_test.py
index 74c10abf..d8337f5d 100644
--- a/tornado/test/process_test.py
+++ b/tornado/test/process_test.py
@@ -147,8 +147,7 @@ class SubprocessTest(AsyncTestCase):
                                     "LayeredTwistedIOLoop")
         subproc = Subprocess([sys.executable, '-u', '-i'],
                              stdin=Subprocess.STREAM,
-                             stdout=Subprocess.STREAM, stderr=subprocess.STDOUT,
-                             io_loop=self.io_loop)
+                             stdout=Subprocess.STREAM, stderr=subprocess.STDOUT)
         self.addCleanup(lambda: (subproc.proc.terminate(), subproc.proc.wait()))
         subproc.stdout.read_until(b'>>> ', self.stop)
         self.wait()
@@ -168,8 +167,7 @@ class SubprocessTest(AsyncTestCase):
         # Close the parent's stdin handle and see that the child recognizes it.
         subproc = Subprocess([sys.executable, '-u', '-i'],
                              stdin=Subprocess.STREAM,
-                             stdout=Subprocess.STREAM, stderr=subprocess.STDOUT,
-                             io_loop=self.io_loop)
+                             stdout=Subprocess.STREAM, stderr=subprocess.STDOUT)
         self.addCleanup(lambda: (subproc.proc.terminate(), subproc.proc.wait()))
         subproc.stdout.read_until(b'>>> ', self.stop)
         self.wait()
@@ -184,8 +182,7 @@ class SubprocessTest(AsyncTestCase):
         skip_if_twisted()
         subproc = Subprocess([sys.executable, '-u', '-c',
                               r"import sys; sys.stderr.write('hello\n')"],
-                             stderr=Subprocess.STREAM,
-                             io_loop=self.io_loop)
+                             stderr=Subprocess.STREAM)
         self.addCleanup(lambda: (subproc.proc.terminate(), subproc.proc.wait()))
         subproc.stderr.read_until(b'\n', self.stop)
         data = self.wait()
@@ -194,10 +191,9 @@ class SubprocessTest(AsyncTestCase):
     def test_sigchild(self):
         # Twisted's SIGCHLD handler and Subprocess's conflict with each other.
         skip_if_twisted()
-        Subprocess.initialize(io_loop=self.io_loop)
+        Subprocess.initialize()
         self.addCleanup(Subprocess.uninitialize)
-        subproc = Subprocess([sys.executable, '-c', 'pass'],
-                             io_loop=self.io_loop)
+        subproc = Subprocess([sys.executable, '-c', 'pass'])
         subproc.set_exit_callback(self.stop)
         ret = self.wait()
         self.assertEqual(ret, 0)
@@ -215,12 +211,11 @@ class SubprocessTest(AsyncTestCase):
 
     def test_sigchild_signal(self):
         skip_if_twisted()
-        Subprocess.initialize(io_loop=self.io_loop)
+        Subprocess.initialize()
         self.addCleanup(Subprocess.uninitialize)
         subproc = Subprocess([sys.executable, '-c',
                               'import time; time.sleep(30)'],
-                             stdout=Subprocess.STREAM,
-                             io_loop=self.io_loop)
+                             stdout=Subprocess.STREAM)
         subproc.set_exit_callback(self.stop)
         os.kill(subproc.pid, signal.SIGTERM)
         try:
diff --git a/tornado/test/simple_httpclient_test.py b/tornado/test/simple_httpclient_test.py
index 02d57c5f..f7133143 100644
--- a/tornado/test/simple_httpclient_test.py
+++ b/tornado/test/simple_httpclient_test.py
@@ -29,8 +29,7 @@ from tornado.web import RequestHandler, Application, asynchronous, url, stream_r
 
 class SimpleHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):
     def get_http_client(self):
-        client = SimpleAsyncHTTPClient(io_loop=self.io_loop,
-                                       force_instance=True)
+        client = SimpleAsyncHTTPClient(force_instance=True)
         self.assertTrue(isinstance(client, SimpleAsyncHTTPClient))
         return client
 
@@ -150,16 +149,16 @@ class SimpleHTTPClientTestMixin(object):
 
     def test_singleton(self):
         # Class "constructor" reuses objects on the same IOLoop
-        self.assertTrue(SimpleAsyncHTTPClient(self.io_loop) is
-                        SimpleAsyncHTTPClient(self.io_loop))
+        self.assertTrue(SimpleAsyncHTTPClient() is
+                        SimpleAsyncHTTPClient())
         # unless force_instance is used
-        self.assertTrue(SimpleAsyncHTTPClient(self.io_loop) is not
-                        SimpleAsyncHTTPClient(self.io_loop,
-                                              force_instance=True))
+        self.assertTrue(SimpleAsyncHTTPClient() is not
+                        SimpleAsyncHTTPClient(force_instance=True))
         # different IOLoops use different objects
         with closing(IOLoop()) as io_loop2:
-            self.assertTrue(SimpleAsyncHTTPClient(self.io_loop) is not
-                            SimpleAsyncHTTPClient(io_loop2))
+            client1 = self.io_loop.run_sync(gen.coroutine(SimpleAsyncHTTPClient))
+            client2 = io_loop2.run_sync(gen.coroutine(SimpleAsyncHTTPClient))
+            self.assertTrue(client1 is not client2)
 
     def test_connection_limit(self):
         with closing(self.create_client(max_clients=2)) as client:
@@ -481,8 +480,7 @@ class SimpleHTTPClientTestCase(SimpleHTTPClientTestMixin, AsyncHTTPTestCase):
         self.http_client = self.create_client()
 
     def create_client(self, **kwargs):
-        return SimpleAsyncHTTPClient(self.io_loop, force_instance=True,
-                                     **kwargs)
+        return SimpleAsyncHTTPClient(force_instance=True, **kwargs)
 
 
 class SimpleHTTPSClientTestCase(SimpleHTTPClientTestMixin, AsyncHTTPSTestCase):
@@ -491,7 +489,7 @@ class SimpleHTTPSClientTestCase(SimpleHTTPClientTestMixin, AsyncHTTPSTestCase):
         self.http_client = self.create_client()
 
     def create_client(self, **kwargs):
-        return SimpleAsyncHTTPClient(self.io_loop, force_instance=True,
+        return SimpleAsyncHTTPClient(force_instance=True,
                                      defaults=dict(validate_cert=False),
                                      **kwargs)
 
@@ -544,24 +542,22 @@ class CreateAsyncHTTPClientTestCase(AsyncTestCase):
 
     def test_max_clients(self):
         AsyncHTTPClient.configure(SimpleAsyncHTTPClient)
-        with closing(AsyncHTTPClient(
-                self.io_loop, force_instance=True)) as client:
+        with closing(AsyncHTTPClient(force_instance=True)) as client:
             self.assertEqual(client.max_clients, 10)
         with closing(AsyncHTTPClient(
-                self.io_loop, max_clients=11, force_instance=True)) as client:
+                max_clients=11, force_instance=True)) as client:
             self.assertEqual(client.max_clients, 11)
 
         # Now configure max_clients statically and try overriding it
         # with each way max_clients can be passed
         AsyncHTTPClient.configure(SimpleAsyncHTTPClient, max_clients=12)
-        with closing(AsyncHTTPClient(
-                self.io_loop, force_instance=True)) as client:
+        with closing(AsyncHTTPClient(force_instance=True)) as client:
             self.assertEqual(client.max_clients, 12)
         with closing(AsyncHTTPClient(
-                self.io_loop, max_clients=13, force_instance=True)) as client:
+                max_clients=13, force_instance=True)) as client:
             self.assertEqual(client.max_clients, 13)
         with closing(AsyncHTTPClient(
-                self.io_loop, max_clients=14, force_instance=True)) as client:
+                max_clients=14, force_instance=True)) as client:
             self.assertEqual(client.max_clients, 14)
 
 
@@ -644,7 +640,6 @@ class HostnameMappingTestCase(AsyncHTTPTestCase):
     def setUp(self):
         super(HostnameMappingTestCase, self).setUp()
         self.http_client = SimpleAsyncHTTPClient(
-            self.io_loop,
             hostname_mapping={
                 'www.example.com': '127.0.0.1',
                 ('foo.example.com', 8000): ('127.0.0.1', self.get_http_port()),
@@ -676,7 +671,6 @@ class ResolveTimeoutTestCase(AsyncHTTPTestCase):
 
         super(ResolveTimeoutTestCase, self).setUp()
         self.http_client = SimpleAsyncHTTPClient(
-            self.io_loop,
             resolver=BadResolver())
 
     def get_app(self):
@@ -703,7 +697,7 @@ class MaxHeaderSizeTest(AsyncHTTPTestCase):
                             ('/large', LargeHeaders)])
 
     def get_http_client(self):
-        return SimpleAsyncHTTPClient(io_loop=self.io_loop, max_header_size=1024)
+        return SimpleAsyncHTTPClient(max_header_size=1024)
 
     def test_small_headers(self):
         response = self.fetch('/small')
@@ -730,7 +724,7 @@ class MaxBodySizeTest(AsyncHTTPTestCase):
                             ('/large', LargeBody)])
 
     def get_http_client(self):
-        return SimpleAsyncHTTPClient(io_loop=self.io_loop, max_body_size=1024 * 64)
+        return SimpleAsyncHTTPClient(max_body_size=1024 * 64)
 
     def test_small_body(self):
         response = self.fetch('/small')
@@ -754,7 +748,7 @@ class MaxBufferSizeTest(AsyncHTTPTestCase):
 
     def get_http_client(self):
         # 100KB body with 64KB buffer
-        return SimpleAsyncHTTPClient(io_loop=self.io_loop, max_body_size=1024 * 100, max_buffer_size=1024 * 64)
+        return SimpleAsyncHTTPClient(max_body_size=1024 * 100, max_buffer_size=1024 * 64)
 
     def test_large_body(self):
         response = self.fetch('/large')
diff --git a/tornado/test/stack_context_test.py b/tornado/test/stack_context_test.py
index 59d25474..fb9e0d7a 100644
--- a/tornado/test/stack_context_test.py
+++ b/tornado/test/stack_context_test.py
@@ -2,6 +2,7 @@
 from __future__ import absolute_import, division, print_function
 
 from tornado import gen
+from tornado.ioloop import IOLoop
 from tornado.log import app_log
 from tornado.stack_context import (StackContext, wrap, NullContext, StackContextInconsistentError,
                                    ExceptionStackContext, run_with_stack_context, _state)
@@ -14,22 +15,21 @@ import logging
 
 
 class TestRequestHandler(RequestHandler):
-    def __init__(self, app, request, io_loop):
+    def __init__(self, app, request):
         super(TestRequestHandler, self).__init__(app, request)
-        self.io_loop = io_loop
 
     @asynchronous
     def get(self):
         logging.debug('in get()')
         # call self.part2 without a self.async_callback wrapper.  Its
         # exception should still get thrown
-        self.io_loop.add_callback(self.part2)
+        IOLoop.current().add_callback(self.part2)
 
     def part2(self):
         logging.debug('in part2()')
         # Go through a third layer to make sure that contexts once restored
         # are again passed on to future callbacks
-        self.io_loop.add_callback(self.part3)
+        IOLoop.current().add_callback(self.part3)
 
     def part3(self):
         logging.debug('in part3()')
@@ -44,8 +44,7 @@ class TestRequestHandler(RequestHandler):
 
 class HTTPStackContextTest(AsyncHTTPTestCase):
     def get_app(self):
-        return Application([('/', TestRequestHandler,
-                             dict(io_loop=self.io_loop))])
+        return Application([('/', TestRequestHandler)])
 
     def test_stack_context(self):
         with ExpectLog(app_log, "Uncaught exception GET /"):
diff --git a/tornado/test/tcpclient_test.py b/tornado/test/tcpclient_test.py
index 76206e85..117f28de 100644
--- a/tornado/test/tcpclient_test.py
+++ b/tornado/test/tcpclient_test.py
@@ -218,7 +218,7 @@ class ConnectorTest(AsyncTestCase):
             future.set_exception(IOError())
 
     def start_connect(self, addrinfo):
-        conn = _Connector(addrinfo, self.io_loop, self.create_stream)
+        conn = _Connector(addrinfo, self.create_stream)
         # Give it a huge timeout; we'll trigger timeouts manually.
         future = conn.start(3600)
         return conn, future
diff --git a/tornado/test/twisted_test.py b/tornado/test/twisted_test.py
index 1604ce52..10afebb7 100644
--- a/tornado/test/twisted_test.py
+++ b/tornado/test/twisted_test.py
@@ -98,8 +98,9 @@ def restore_signal_handlers(saved):
 class ReactorTestCase(unittest.TestCase):
     def setUp(self):
         self._saved_signals = save_signal_handlers()
-        self._io_loop = IOLoop()
-        self._reactor = TornadoReactor(self._io_loop)
+        self._io_loop = IOLoop(make_current=True)
+        self._reactor = TornadoReactor()
+        IOLoop.clear_current()
 
     def tearDown(self):
         self._io_loop.close(all_fds=True)
@@ -361,7 +362,7 @@ class CompatibilityTests(unittest.TestCase):
         self.saved_signals = save_signal_handlers()
         self.io_loop = IOLoop()
         self.io_loop.make_current()
-        self.reactor = TornadoReactor(self.io_loop)
+        self.reactor = TornadoReactor()
 
     def tearDown(self):
         self.reactor.disconnectAll()
@@ -385,7 +386,7 @@ class CompatibilityTests(unittest.TestCase):
                 self.write("Hello from tornado!")
         app = Application([('/', HelloHandler)],
                           log_function=lambda x: None)
-        server = HTTPServer(app, io_loop=self.io_loop)
+        server = HTTPServer(app)
         sock, self.tornado_port = bind_unused_port()
         server.add_sockets([sock])
 
@@ -401,7 +402,7 @@ class CompatibilityTests(unittest.TestCase):
 
     def tornado_fetch(self, url, runner):
         responses = []
-        client = AsyncHTTPClient(self.io_loop)
+        client = AsyncHTTPClient()
 
         def callback(response):
             responses.append(response)
@@ -701,7 +702,7 @@ if have_twisted:
             # get the next-best IOLoop implementation, so use the lowest common
             # denominator.
             self.real_io_loop = SelectIOLoop(make_current=False)  # type: ignore
-            reactor = TornadoReactor(io_loop=self.real_io_loop)
+            reactor = self.real_io_loop.run_sync(gen.coroutine(TornadoReactor))
             super(LayeredTwistedIOLoop, self).initialize(reactor=reactor, **kwargs)
             self.add_callback(self.make_current)
 
diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py
index de26c42e..ffccc02d 100644
--- a/tornado/test/web_test.py
+++ b/tornado/test/web_test.py
@@ -379,7 +379,7 @@ class ConnectionCloseTest(WebTestCase):
     def test_connection_close(self):
         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
         s.connect(("127.0.0.1", self.get_http_port()))
-        self.stream = IOStream(s, io_loop=self.io_loop)
+        self.stream = IOStream(s)
         self.stream.write(b"GET / HTTP/1.0\r\n\r\n")
         self.wait()
 
@@ -1481,7 +1481,7 @@ class StatusReasonTest(SimpleHandlerTestCase):
 
     def get_http_client(self):
         # simple_httpclient only: curl doesn't expose the reason string
-        return SimpleAsyncHTTPClient(io_loop=self.io_loop)
+        return SimpleAsyncHTTPClient()
 
     def test_status(self):
         response = self.fetch("/?code=304")
@@ -1520,7 +1520,7 @@ class RaiseWithReasonTest(SimpleHandlerTestCase):
 
     def get_http_client(self):
         # simple_httpclient only: curl doesn't expose the reason string
-        return SimpleAsyncHTTPClient(io_loop=self.io_loop)
+        return SimpleAsyncHTTPClient()
 
     def test_raise_with_reason(self):
         response = self.fetch("/")
@@ -2115,7 +2115,7 @@ class StreamingRequestBodyTest(WebTestCase):
         # Use a raw connection so we can control the sending of data.
         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
         s.connect(("127.0.0.1", self.get_http_port()))
-        stream = IOStream(s, io_loop=self.io_loop)
+        stream = IOStream(s)
         stream.write(b"GET " + url + b" HTTP/1.1\r\n")
         if connection_close:
             stream.write(b"Connection: close\r\n")
@@ -2211,7 +2211,7 @@ class BaseStreamingRequestFlowControlTest(object):
 
     def get_http_client(self):
         # simple_httpclient only: curl doesn't support body_producer.
-        return SimpleAsyncHTTPClient(io_loop=self.io_loop)
+        return SimpleAsyncHTTPClient()
 
     # Test all the slightly different code paths for fixed, chunked, etc bodies.
     def test_flow_control_fixed_body(self):
diff --git a/tornado/test/websocket_test.py b/tornado/test/websocket_test.py
index e0b5573d..5a2a6577 100644
--- a/tornado/test/websocket_test.py
+++ b/tornado/test/websocket_test.py
@@ -211,7 +211,7 @@ class WebSocketTest(WebSocketBaseTestCase):
     def test_websocket_callbacks(self):
         websocket_connect(
             'ws://127.0.0.1:%d/echo' % self.get_http_port(),
-            io_loop=self.io_loop, callback=self.stop)
+            callback=self.stop)
         ws = self.wait().result()
         ws.write_message('hello')
         ws.read_message(self.stop)
@@ -273,7 +273,6 @@ class WebSocketTest(WebSocketBaseTestCase):
             with ExpectLog(gen_log, ".*"):
                 yield websocket_connect(
                     'ws://127.0.0.1:%d/' % port,
-                    io_loop=self.io_loop,
                     connect_timeout=3600)
 
     @gen_test
@@ -372,8 +371,7 @@ class WebSocketTest(WebSocketBaseTestCase):
         url = 'ws://127.0.0.1:%d/echo' % port
         headers = {'Origin': 'http://127.0.0.1:%d' % port}
 
-        ws = yield websocket_connect(HTTPRequest(url, headers=headers),
-                                     io_loop=self.io_loop)
+        ws = yield websocket_connect(HTTPRequest(url, headers=headers))
         ws.write_message('hello')
         response = yield ws.read_message()
         self.assertEqual(response, 'hello')
@@ -386,8 +384,7 @@ class WebSocketTest(WebSocketBaseTestCase):
         url = 'ws://127.0.0.1:%d/echo' % port
         headers = {'Origin': 'http://127.0.0.1:%d/something' % port}
 
-        ws = yield websocket_connect(HTTPRequest(url, headers=headers),
-                                     io_loop=self.io_loop)
+        ws = yield websocket_connect(HTTPRequest(url, headers=headers))
         ws.write_message('hello')
         response = yield ws.read_message()
         self.assertEqual(response, 'hello')
@@ -401,8 +398,7 @@ class WebSocketTest(WebSocketBaseTestCase):
         headers = {'Origin': '127.0.0.1:%d' % port}
 
         with self.assertRaises(HTTPError) as cm:
-            yield websocket_connect(HTTPRequest(url, headers=headers),
-                                    io_loop=self.io_loop)
+            yield websocket_connect(HTTPRequest(url, headers=headers))
         self.assertEqual(cm.exception.code, 403)
 
     @gen_test
@@ -415,8 +411,7 @@ class WebSocketTest(WebSocketBaseTestCase):
         headers = {'Origin': 'http://somewhereelse.com'}
 
         with self.assertRaises(HTTPError) as cm:
-            yield websocket_connect(HTTPRequest(url, headers=headers),
-                                    io_loop=self.io_loop)
+            yield websocket_connect(HTTPRequest(url, headers=headers))
 
         self.assertEqual(cm.exception.code, 403)
 
@@ -430,8 +425,7 @@ class WebSocketTest(WebSocketBaseTestCase):
         headers = {'Origin': 'http://subtenant.localhost'}
 
         with self.assertRaises(HTTPError) as cm:
-            yield websocket_connect(HTTPRequest(url, headers=headers),
-                                    io_loop=self.io_loop)
+            yield websocket_connect(HTTPRequest(url, headers=headers))
 
         self.assertEqual(cm.exception.code, 403)
 
diff --git a/tornado/testing.py b/tornado/testing.py
index 3c30c9ee..9979617d 100644
--- a/tornado/testing.py
+++ b/tornado/testing.py
@@ -73,24 +73,6 @@ else:
     except ImportError:
         import unittest  # type: ignore
 
-_next_port = 10000
-
-
-def get_unused_port():
-    """Returns a (hopefully) unused port number.
-
-    This function does not guarantee that the port it returns is available,
-    only that a series of get_unused_port calls in a single process return
-    distinct ports.
-
-    .. deprecated::
-       Use bind_unused_port instead, which is guaranteed to find an unused port.
-    """
-    global _next_port
-    port = _next_port
-    _next_port = _next_port + 1
-    return port
-
 
 def bind_unused_port(reuse_port=False):
     """Binds a server socket to an available port on localhost.
@@ -166,8 +148,7 @@ class AsyncTestCase(unittest.TestCase):
     callbacks should call ``self.stop()`` to signal completion.
 
     By default, a new `.IOLoop` is constructed for each test and is available
-    as ``self.io_loop``.  This `.IOLoop` should be used in the construction of
-    HTTP clients/servers, etc.  If the code being tested requires a
+    as ``self.io_loop``.  If the code being tested requires a
     global `.IOLoop`, subclasses should override `get_new_ioloop` to return it.
 
     The `.IOLoop`'s ``start`` and ``stop`` methods should not be
@@ -182,7 +163,7 @@ class AsyncTestCase(unittest.TestCase):
         class MyTestCase(AsyncTestCase):
             @tornado.testing.gen_test
             def test_http_fetch(self):
-                client = AsyncHTTPClient(self.io_loop)
+                client = AsyncHTTPClient()
                 response = yield client.fetch("http://www.tornadoweb.org")
                 # Test contents of response
                 self.assertIn("FriendFeed", response.body)
@@ -190,7 +171,7 @@ class AsyncTestCase(unittest.TestCase):
         # This test uses argument passing between self.stop and self.wait.
         class MyTestCase2(AsyncTestCase):
             def test_http_fetch(self):
-                client = AsyncHTTPClient(self.io_loop)
+                client = AsyncHTTPClient()
                 client.fetch("http://www.tornadoweb.org/", self.stop)
                 response = self.wait()
                 # Test contents of response
@@ -199,7 +180,7 @@ class AsyncTestCase(unittest.TestCase):
         # This test uses an explicit callback-based style.
         class MyTestCase3(AsyncTestCase):
             def test_http_fetch(self):
-                client = AsyncHTTPClient(self.io_loop)
+                client = AsyncHTTPClient()
                 client.fetch("http://www.tornadoweb.org/", self.handle_fetch)
                 self.wait()
 
@@ -382,11 +363,10 @@ class AsyncHTTPTestCase(AsyncTestCase):
         self.http_server.add_sockets([sock])
 
     def get_http_client(self):
-        return AsyncHTTPClient(io_loop=self.io_loop)
+        return AsyncHTTPClient()
 
     def get_http_server(self):
-        return HTTPServer(self._app, io_loop=self.io_loop,
-                          **self.get_httpserver_options())
+        return HTTPServer(self._app, **self.get_httpserver_options())
 
     def get_app(self):
         """Should be overridden by subclasses to return a
@@ -448,7 +428,7 @@ class AsyncHTTPSTestCase(AsyncHTTPTestCase):
     Interface is generally the same as `AsyncHTTPTestCase`.
     """
     def get_http_client(self):
-        return AsyncHTTPClient(io_loop=self.io_loop, force_instance=True,
+        return AsyncHTTPClient(force_instance=True,
                                defaults=dict(validate_cert=False))
 
     def get_httpserver_options(self):