I'm loading templates from a coroutine in Tornado using render_string()
and it came to my mind that render_string()
is not a coroutine. So we're accessing the disk form a coroutine, but we're not yielding any futures. I wonder how does this work and if it blocks the application or not.
It's true, the first time a given template is read from the disk, it blocks Tornado's event loop:
class Loader(BaseLoader):
def _create_template(self, name):
path = os.path.join(self.root, name)
with open(path, "rb") as f:
template = Template(f.read(), name=name, loader=self)
return template
This initial load is probably fast, since the template is likely only a few kilobytes and already loaded into the machine's in-memory filesystem cache. Subsequent accesses of the same template by the same Tornado process are cached within Tornado itself:
class BaseLoader(object):
def load(self, name, parent_path=None):
"""Loads a template."""
name = self.resolve_path(name, parent_path=parent_path)
with self.lock:
if name not in self.templates:
self.templates[name] = self._create_template(name)
return self.templates[name]
So it doesn't seem worthwhile for Tornado to defer the filesystem access to a thread.
Often in Python async frameworks you'll see that not all I/O is performed asynchronously - quick and predictable blocking operations like accessing a file or a MySQL query might not block the loop long enough to worry about. What's critical is that long or unpredictable operations, like calling a remote HTTP service, are scheduled on the event loop.