Skip to content

Commit 471d3b1

Browse files
committed
Implement async generator based Voila get handler
1 parent c50271c commit 471d3b1

File tree

2 files changed

+39
-41
lines changed

2 files changed

+39
-41
lines changed

voila/handler.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#############################################################################
99

1010

11+
import asyncio
1112
import os
1213
from typing import Dict
1314

@@ -33,8 +34,7 @@ def initialize(self, **kwargs):
3334
# we want to avoid starting multiple kernels due to template mistakes
3435
self.kernel_started = False
3536

36-
@tornado.web.authenticated
37-
async def get(self, path=None):
37+
async def get_generator(self, path=None):
3838
# if the handler got a notebook_path argument, always serve that
3939
notebook_path = self.notebook_path or path
4040

@@ -101,22 +101,18 @@ async def get(self, path=None):
101101
QueryStringSocketHandler.send_updates({'kernel_id': kernel_id, 'payload': self.request.query})
102102
# Send rendered cell to frontend
103103
if len(rendered_cache) > 0:
104-
self.write(''.join(rendered_cache))
105-
self.flush()
104+
yield ''.join(rendered_cache)
106105

107106
# Wait for current running cell finish and send this cell to
108107
# frontend.
109108
rendered, rendering = await render_task
110109
if len(rendered) > len(rendered_cache):
111110
html_snippet = ''.join(rendered[len(rendered_cache):])
112-
self.write(html_snippet)
113-
self.flush()
111+
yield html_snippet
114112

115113
# Continue render cell from generator.
116114
async for html_snippet, _ in rendering:
117-
self.write(html_snippet)
118-
self.flush()
119-
self.flush()
115+
yield html_snippet
120116

121117
else:
122118
# All kernels are used or pre-heated kernel is disabled, start a normal kernel.
@@ -139,8 +135,7 @@ def time_out():
139135
can be used in a template to give feedback to a user
140136
"""
141137

142-
self.write('<script>voila_heartbeat()</script>\n')
143-
self.flush()
138+
return '<script>voila_heartbeat()</script>\n'
144139

145140
kernel_env[ENV_VARIABLE.VOILA_PREHEAT] = 'False'
146141
kernel_env[ENV_VARIABLE.VOILA_BASE_URL] = self.base_url
@@ -154,13 +149,34 @@ def time_out():
154149
)
155150
)
156151
kernel_future = self.kernel_manager.get_kernel(kernel_id)
157-
async for html_snippet, _ in gen.generate_content_generator(
158-
kernel_id, kernel_future, time_out
159-
):
160-
self.write(html_snippet)
161-
self.flush()
162-
# we may not want to consider not flusing after each snippet, but add an explicit flush function to the jinja context
163-
# yield # give control back to tornado's IO loop, so it can handle static files or other requests
152+
queue = asyncio.Queue()
153+
154+
async def put_html():
155+
async for html_snippet, _ in gen.generate_content_generator(kernel_id, kernel_future):
156+
await queue.put(html_snippet)
157+
158+
await queue.put(None)
159+
160+
asyncio.ensure_future(put_html())
161+
162+
# If not done within the timeout, we send a heartbeat
163+
# this is fundamentally to avoid browser/proxy read-timeouts, but
164+
# can be used in a template to give feedback to a user
165+
while True:
166+
try:
167+
html_snippet = await asyncio.wait_for(queue.get(), self.voila_configuration.http_keep_alive_timeout)
168+
except asyncio.TimeoutError:
169+
yield time_out()
170+
else:
171+
if html_snippet is None:
172+
break
173+
yield html_snippet
174+
175+
@tornado.web.authenticated
176+
async def get(self, path=None):
177+
gen = self.get_generator(path=path)
178+
async for html in gen:
179+
self.write(html)
164180
self.flush()
165181

166182
def redirect_to_file(self, path):

voila/notebook_renderer.py

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
#############################################################################
99

1010

11-
import asyncio
1211
import os
1312
import sys
1413
import traceback
15-
from typing import Callable, Generator, Tuple, Union, List
14+
from typing import Generator, Tuple, Union, List
1615

1716
import nbformat
1817
import tornado.web
@@ -150,13 +149,12 @@ def generate_content_generator(
150149
self,
151150
kernel_id: Union[str, None] = None,
152151
kernel_future=None,
153-
timeout_callback: Union[Callable, None] = None,
154152
) -> Generator:
155153
async def inner_kernel_start(nb):
156154
return await self._jinja_kernel_start(nb, kernel_id, kernel_future)
157155

158156
def inner_cell_generator(nb, kernel_id):
159-
return self._jinja_cell_generator(nb, kernel_id, timeout_callback)
157+
return self._jinja_cell_generator(nb, kernel_id)
160158

161159
# These functions allow the start of a kernel and execution of the
162160
# notebook after (parts of) the template has been rendered and send
@@ -248,32 +246,16 @@ async def _jinja_notebook_execute(self, nb, kernel_id):
248246

249247
await self._cleanup_resources()
250248

251-
async def _jinja_cell_generator(self, nb, kernel_id, timeout_callback):
249+
async def _jinja_cell_generator(self, nb, kernel_id):
252250
"""Generator that will execute a single notebook cell at a time"""
253251
nb, _ = ClearOutputPreprocessor().preprocess(
254252
nb, {'metadata': {'path': self.cwd}}
255253
)
256254
for cell_idx, input_cell in enumerate(nb.cells):
257255
try:
258-
task = asyncio.ensure_future(
259-
self.executor.execute_cell(
260-
input_cell, None, cell_idx, store_history=False
261-
)
256+
output_cell = await self.executor.execute_cell(
257+
input_cell, None, cell_idx, store_history=False
262258
)
263-
while True:
264-
_, pending = await asyncio.wait(
265-
{task}, timeout=self.voila_configuration.http_keep_alive_timeout
266-
)
267-
if pending:
268-
# If not done within the timeout, we send a heartbeat
269-
# this is fundamentally to avoid browser/proxy read-timeouts, but
270-
# can be used in a template to give feedback to a user
271-
if timeout_callback is not None:
272-
timeout_callback()
273-
274-
continue
275-
output_cell = await task
276-
break
277259
except TimeoutError:
278260
output_cell = input_cell
279261
break

0 commit comments

Comments
 (0)