Repository: bokeh/bokeh

Issue:
Importing bokeh.models creates zombie process
The following was brought to my attention: 

Importing `bokeh.models` seems to create a zombie process.  Here is a minimal failing example:

```python
import bokeh.models, psutil, time
time.sleep(3)
print(psutil.Process().children())
print(psutil.Process().children()[0].status())
```

prints

```
[<psutil.Process(pid=4851, name='node') at 140524007092176>]
zombie
```

This may be caused by https://github.com/bokeh/bokeh/blob/master/bokeh/util/compiler.py#L116 

I mentioned this to @bryevdv and he suggested that I ping and assign @mattpap 


Discussion:

Resolving patch:
diff --git a/bokeh/util/compiler.py b/bokeh/util/compiler.py
--- a/bokeh/util/compiler.py
+++ b/bokeh/util/compiler.py
@@ -113,22 +113,32 @@ def _detect_nodejs():
 
     for nodejs_path in nodejs_paths:
         try:
-            Popen([nodejs_path, "--version"], stdout=PIPE, stderr=PIPE)
+            proc = Popen([nodejs_path, "--version"], stdout=PIPE, stderr=PIPE)
+            proc.wait()
         except OSError:
             pass
         else:
             return nodejs_path
     else:
-        return None
+        raise RuntimeError('node.js is needed to allow compilation of custom models ' +
+                           '("conda install -c bokeh nodejs" or follow https://nodejs.org/en/download/)')
 
-_nodejs = _detect_nodejs()
-_npmjs = None if _nodejs is None else join(dirname(_nodejs), "npm")
+_nodejs = None
+_npmjs = None
 
-def _run(app, argv, input=None):
+def _nodejs_path():
+    global _nodejs
     if _nodejs is None:
-        raise RuntimeError('node.js is needed to allow compilation of custom models ' +
-                           '("conda install -c bokeh nodejs" or follow https://nodejs.org/en/download/)')
+        _nodejs = _detect_nodejs()
+    return _nodejs
 
+def _npmjs_path():
+    global _npmjs
+    if _npmjs is None:
+        _npmjs = join(dirname(_nodejs_path()), "npm")
+    return _npmjs
+
+def _run(app, argv, input=None):
     proc = Popen([app] + argv, stdout=PIPE, stderr=PIPE, stdin=PIPE)
     (stdout, errout) = proc.communicate(input=None if input is None else json.dumps(input).encode())
 
@@ -138,10 +148,10 @@ def _run(app, argv, input=None):
         return stdout.decode('utf-8')
 
 def _run_nodejs(argv, input=None):
-    return _run(_nodejs, argv, input)
+    return _run(_nodejs_path(), argv, input)
 
 def _run_npmjs(argv, input=None):
-    return _run(_npmjs, argv, input)
+    return _run(_npmjs_path(), argv, input)
 
 def _version(run_app):
     try:


Test patch:
diff --git a/bokeh/util/tests/test_compiler.py b/bokeh/util/tests/test_compiler.py
--- a/bokeh/util/tests/test_compiler.py
+++ b/bokeh/util/tests/test_compiler.py
@@ -1,9 +1,11 @@
 from __future__ import absolute_import
 
-from bokeh.util.compiler import nodejs_compile
+from mock import Mock
+
+import bokeh.util.compiler as buc
 
 def test_nodejs_compile_coffeescript():
-    assert nodejs_compile("""(a, b) -> a + b""", "coffeescript", "some.coffee") == \
+    assert buc.nodejs_compile("""(a, b) -> a + b""", "coffeescript", "some.coffee") == \
         dict(code="""\
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
@@ -12,7 +14,7 @@ def test_nodejs_compile_coffeescript():
 });
 """, deps=[])
 
-    assert nodejs_compile("""some = require 'some/module'""", "coffeescript", "some.coffee") == \
+    assert buc.nodejs_compile("""some = require 'some/module'""", "coffeescript", "some.coffee") == \
         dict(code="""\
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
@@ -20,12 +22,12 @@ def test_nodejs_compile_coffeescript():
 some = require('some/module');
 """, deps=["some/module"])
 
-    assert nodejs_compile("""(a, b) -> a + b +""", "coffeescript", "some.coffee") == \
+    assert buc.nodejs_compile("""(a, b) -> a + b +""", "coffeescript", "some.coffee") == \
         dict(error=dict(
             message="unexpected end of input",
             text="some.coffee:unexpected end of input"))
 
-    assert nodejs_compile("""some = require some/module'""", "coffeescript", "some.coffee") == \
+    assert buc.nodejs_compile("""some = require some/module'""", "coffeescript", "some.coffee") == \
         dict(error=dict(
             line=1,
             column=27,
@@ -34,12 +36,12 @@ def test_nodejs_compile_coffeescript():
             extract="some = require some/module'",
             annotated="some.coffee:1:27:missing '\n  some = require some/module'\n                            ^"))
 
-    assert nodejs_compile("""(a, b) -> a + b +""", "coffeescript", "some.coffee") == \
+    assert buc.nodejs_compile("""(a, b) -> a + b +""", "coffeescript", "some.coffee") == \
         dict(error=dict(
             message="unexpected end of input",
             text="some.coffee:unexpected end of input"))
 
-    assert nodejs_compile("""some = require some/module'""", "coffeescript", "some.coffee") == \
+    assert buc.nodejs_compile("""some = require some/module'""", "coffeescript", "some.coffee") == \
         dict(error=dict(
             line=1,
             column=27,
@@ -49,7 +51,7 @@ def test_nodejs_compile_coffeescript():
             annotated="some.coffee:1:27:missing '\n  some = require some/module'\n                            ^"))
 
 def test_nodejs_compile_javascript():
-    assert nodejs_compile("""function f(a, b) { return a + b; };""", "javascript", "some.js") == \
+    assert buc.nodejs_compile("""function f(a, b) { return a + b; };""", "javascript", "some.js") == \
         dict(code="""\
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
@@ -57,14 +59,14 @@ def test_nodejs_compile_javascript():
 ;
 """, deps=[])
 
-    assert nodejs_compile("""var some = require('some/module');""", "javascript", "some.js") == \
+    assert buc.nodejs_compile("""var some = require('some/module');""", "javascript", "some.js") == \
         dict(code="""\
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
 var some = require('some/module');
 """, deps=["some/module"])
 
-    assert nodejs_compile("""function f(a, b) { eturn a + b; };""", "javascript", "some.js") == \
+    assert buc.nodejs_compile("""function f(a, b) { eturn a + b; };""", "javascript", "some.js") == \
         dict(error=dict(
             line=1,
             column=26,
@@ -72,10 +74,10 @@ def test_nodejs_compile_javascript():
             text="some.js:1:26:';' expected."))
 
 def test_nodejs_compile_less():
-    assert nodejs_compile(""".bk-some-style { color: mix(#ff0000, #0000ff, 50%); }""", "less", "some.less") == \
+    assert buc.nodejs_compile(""".bk-some-style { color: mix(#ff0000, #0000ff, 50%); }""", "less", "some.less") == \
         dict(code=""".bk-some-style{color:#800080}""")
 
-    assert nodejs_compile(""".bk-some-style color: green; }""", "less", "some.less") == \
+    assert buc.nodejs_compile(""".bk-some-style color: green; }""", "less", "some.less") == \
         dict(error=dict(
             line=1,
             column=21,
@@ -83,3 +85,11 @@ def test_nodejs_compile_less():
             text="some.less:1:21:Unrecognised input",
             extract=".bk-some-style color: green; }",
             annotated="some.less:1:21:Unrecognised input\n  .bk-some-style color: green; }"))
+
+def test__detect_nodejs_calls_wait():
+    m = Mock()
+    old_Popen = buc.Popen
+    buc.Popen = lambda *args, **kw: m
+    buc._detect_nodejs()
+    assert m.wait.called
+    buc.Popen = old_Popen