Repository: bokeh/bokeh

Issue:
--num-procs broken

Some of the recent Bokeh server refactoring is not compatible with `--num-procs`:

```
../../bin/bokeh serve  --num-procs=4 --show gapminder                                         (master)
2016-12-09 08:46:05,896 Starting Bokeh server version 0.12.4dev7-55-g9210f28
ERROR:Cannot run in multiple processes: IOLoop instance has already been initialized. You cannot call IOLoop.instance() before calling start_processes()
```


Discussion:

Resolving patch:
diff --git a/bokeh/server/server.py b/bokeh/server/server.py
--- a/bokeh/server/server.py
+++ b/bokeh/server/server.py
@@ -109,10 +109,6 @@ def __init__(self, applications, io_loop=None, **kwargs):
         self._started = False
         self._stopped = False
 
-        if io_loop is None:
-            io_loop = IOLoop.current()
-        self._loop = io_loop
-
         port = kwargs.get('port', DEFAULT_SERVER_PORT)
         self._address = kwargs.get('address') or None
 
@@ -124,14 +120,12 @@ def __init__(self, applications, io_loop=None, **kwargs):
 
         sockets, self._port = _bind_sockets(self._address, port)
         try:
-            tornado_kwargs['io_loop'] = io_loop
             tornado_kwargs['hosts'] = _create_hosts_whitelist(kwargs.get('host'), self._port)
             tornado_kwargs['extra_websocket_origins'] = _create_hosts_whitelist(kwargs.get('allow_websocket_origin'), self._port)
             tornado_kwargs['use_index'] = kwargs.get('use_index', True)
             tornado_kwargs['redirect_root'] = kwargs.get('redirect_root', True)
 
             self._tornado = BokehTornado(self._applications, self.prefix, **tornado_kwargs)
-            self._tornado.initialize(**tornado_kwargs)
             self._http = HTTPServer(self._tornado, xheaders=kwargs.get('use_xheaders', False))
             self._http.start(self._num_procs)
             self._http.add_sockets(sockets)
@@ -141,6 +135,13 @@ def __init__(self, applications, io_loop=None, **kwargs):
                 s.close()
             raise
 
+        # Can only instantiate the IO loop after HTTPServer.start() was
+        # called because of `num_procs`, see issue #5524
+        if io_loop is None:
+            io_loop = IOLoop.current()
+        self._loop = io_loop
+        self._tornado.initialize(io_loop=io_loop, **tornado_kwargs)
+
     @property
     def port(self):
         '''The actual port number the server is listening on for HTTP
diff --git a/bokeh/server/tornado.py b/bokeh/server/tornado.py
--- a/bokeh/server/tornado.py
+++ b/bokeh/server/tornado.py
@@ -146,7 +146,6 @@ class BokehTornado(TornadoApplication):
 
     def __init__(self, applications, prefix, hosts,
                  extra_websocket_origins,
-                 io_loop,
                  extra_patterns=None,
                  secret_key=settings.secret_key_bytes(),
                  sign_sessions=settings.sign_sessions(),


Test patch:
diff --git a/bokeh/command/subcommands/tests/test_serve.py b/bokeh/command/subcommands/tests/test_serve.py
--- a/bokeh/command/subcommands/tests/test_serve.py
+++ b/bokeh/command/subcommands/tests/test_serve.py
@@ -1,6 +1,7 @@
 from __future__ import absolute_import, print_function
 
 import argparse
+import contextlib
 import re
 import socket
 import subprocess
@@ -162,6 +163,22 @@ def test_args():
     )
 
 
+@contextlib.contextmanager
+def run_bokeh_serve(args):
+    cmd = [sys.executable, "-m", "bokeh", "serve"] + args
+    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    try:
+        yield p
+    except Exception:
+        p.terminate()
+        p.wait()
+        print("---- An error occurred, subprocess stdout follows ----")
+        print(p.stdout.read().decode())
+        raise
+    else:
+        p.terminate()
+        p.wait()
+
 def check_error(args):
     cmd = [sys.executable, "-m", "bokeh", "serve"] + args
     try:
@@ -191,9 +208,7 @@ def test_port_not_available():
         sock.close()
 
 def test_actual_port_printed_out():
-    cmd = [sys.executable, "-m", "bokeh", "serve", "--port", "0"]
-    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-    try:
+    with run_bokeh_serve(["--port", "0"]) as p:
         pat = re.compile(r'Starting Bokeh server on port (\d+) with applications at paths')
         while True:
             line = p.stdout.readline()
@@ -207,6 +222,3 @@ def test_actual_port_printed_out():
         assert port > 0
         r = requests.get("http://localhost:%d/" % (port,))
         assert r.status_code == 200
-    finally:
-        p.terminate()
-        p.wait()