Skip to content

Commit

Permalink
Allow bokeh server to be started on a Thread
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Mar 1, 2019
1 parent 2aedd25 commit 9da77ab
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 16 deletions.
25 changes: 25 additions & 0 deletions panel/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import inspect
import numbers
import hashlib
import threading

from collections import defaultdict, MutableSequence, MutableMapping, OrderedDict
from datetime import datetime
Expand Down Expand Up @@ -213,6 +214,30 @@ def __call__(self, pname):
return pname


class StoppableThread(threading.Thread):
"""Thread class with a stop() method."""

def __init__(self, io_loop=None, timeout=1000, **kwargs):
from tornado import ioloop
super(StoppableThread, self).__init__(**kwargs)
self._stop_event = threading.Event()
self.io_loop = io_loop
self._cb = ioloop.PeriodicCallback(self._check_stopped, timeout)
self._cb.start()

def _check_stopped(self):
if self.stopped:
self._cb.stop()
self.io_loop.stop()

def stop(self):
self._stop_event.set()

@property
def stopped(self):
return self._stop_event.is_set()


################################
# Display and update utilities #
################################
Expand Down
49 changes: 33 additions & 16 deletions panel/viewable.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,24 @@ def app(self, notebook_url="localhost:8888"):
"""
show(self._modify_doc, notebook_url=notebook_url)

def show(self, port=0, websocket_origin=None):
def _start_server(self, app, loop, port, websocket_origin):
if websocket_origin and not isinstance(websocket_origin, list):
websocket_origin = [websocket_origin]
opts = dict(allow_websocket_origin=websocket_origin) if websocket_origin else {}
loop.make_current()
opts['io_loop'] = loop
server = Server({'/': app}, port=port, **opts)
def show_callback():
server.show('/')
server.io_loop.add_callback(show_callback)
server.start()
try:
loop.start()
except RuntimeError:
pass
return server

def show(self, port=0, websocket_origin=None, threaded=False):
"""
Starts a bokeh server and displays the Viewable in a new tab
Expand All @@ -340,27 +357,31 @@ def show(self, port=0, websocket_origin=None):
an external web site.
If None, "localhost" is used.
threaded: boolean (optiona)
Whether to launch the Server on a separate thread, allowing
interactive use.
Returns
-------
server: bokeh Server instance
server: bokeh.server.Server or threading.Thread
Returns the bokeh server instance or the thread the server
was launched on (if threaded=True)
"""
def modify_doc(doc):
return self.server_doc(doc)
handler = FunctionHandler(modify_doc)
app = Application(handler)

from tornado.ioloop import IOLoop
loop = IOLoop.current()
if websocket_origin and not isinstance(websocket_origin, list):
websocket_origin = [websocket_origin]
opts = dict(allow_websocket_origin=websocket_origin) if websocket_origin else {}
opts['io_loop'] = loop
server = Server({'/': app}, port=port, **opts)
def show_callback():
server.show('/')
server.io_loop.add_callback(show_callback)
server.start()
if threaded:
from .util import StoppableThread
loop = IOLoop()
server = StoppableThread(target=self._start_server, io_loop=loop,
args=(app, loop, port, websocket_origin))
server.start()
else:
loop = IOLoop.current()
server = self._start_server(app, loop, port, websocket_origin)

def sig_exit(*args, **kwargs):
loop.add_callback_from_signal(do_stop)
Expand All @@ -369,10 +390,6 @@ def do_stop(*args, **kwargs):
loop.stop()

signal.signal(signal.SIGINT, sig_exit)
try:
loop.start()
except RuntimeError:
pass
return server


Expand Down

0 comments on commit 9da77ab

Please sign in to comment.