3
3
import threading
4
4
import errno
5
5
import logging
6
- from typing import Any , Dict , List , Mapping , Text , TypeVar , Union
6
+ import select
7
+ import os
7
8
9
+ import cStringIO
10
+ from cStringIO import StringIO
11
+ from typing import Any , Dict , List , Mapping , Text , TypeVar , Union
12
+ from pkg_resources import resource_stream
8
13
9
14
class JavascriptException (Exception ):
10
15
pass
11
16
12
17
_logger = logging .getLogger ("cwltool" )
13
18
14
- JSON = Union [Dict [Any ,Any ], List [Any ], Text , int , long , float , bool , None ]
19
+ JSON = Union [Dict [Text ,Any ], List [Any ], Text , int , long , float , bool , None ]
20
+
21
+ localdata = threading .local ()
15
22
16
23
have_node_slim = False
17
24
18
- def execjs (js , jslib , timeout = None ): # type: (Union[Mapping, Text], Any, int) -> JSON
25
+ def new_js_proc ():
26
+ # type: () -> subprocess.Popen
27
+
28
+ res = resource_stream (__name__ , 'cwlNodeEngine.js' )
29
+ nodecode = res .read ()
30
+
19
31
nodejs = None
20
32
trynodes = ("nodejs" , "node" )
21
33
for n in trynodes :
22
34
try :
23
- nodejs = subprocess .Popen ([n ], stdin = subprocess .PIPE , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
35
+ nodejs = subprocess .Popen ([n , "--eval" , nodecode ], stdin = subprocess .PIPE , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
24
36
break
25
37
except OSError as e :
26
38
if e .errno == errno .ENOENT :
@@ -39,7 +51,7 @@ def execjs(js, jslib, timeout=None): # type: (Union[Mapping, Text], Any, int) -
39
51
nodejs = subprocess .Popen (["docker" , "run" ,
40
52
"--attach=STDIN" , "--attach=STDOUT" , "--attach=STDERR" ,
41
53
"--sig-proxy=true" , "--interactive" ,
42
- "--rm" , nodeimg ],
54
+ "--rm" , nodeimg , "node" , "--eval" , nodecode ],
43
55
stdin = subprocess .PIPE , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
44
56
except OSError as e :
45
57
if e .errno == errno .ENOENT :
@@ -55,15 +67,24 @@ def execjs(js, jslib, timeout=None): # type: (Union[Mapping, Text], Any, int) -
55
67
"expressions, but couldn't find it. Tried %s, docker run "
56
68
"node:slim" % u", " .join (trynodes ))
57
69
70
+ return nodejs
71
+
72
+
73
+ def execjs (js , jslib , timeout = None ): # type: (Union[Mapping, Text], Any, int) -> JSON
74
+
75
+ if not hasattr (localdata , "proc" ) or localdata .proc .poll () is not None :
76
+ localdata .proc = new_js_proc ()
77
+
78
+ nodejs = localdata .proc
79
+
58
80
fn = u"\" use strict\" ;\n %s\n (function()%s)()" % (jslib , js if isinstance (js , basestring ) and len (js ) > 1 and js [0 ] == '{' else ("{return (%s);}" % js ))
59
- script = u"console.log(JSON.stringify(require(\" vm\" ).runInNewContext(%s, {})));\n " % json .dumps (fn )
60
81
61
82
killed = []
62
83
63
84
def term ():
64
85
try :
65
- nodejs .kill ()
66
86
killed .append (True )
87
+ nodejs .kill ()
67
88
except OSError :
68
89
pass
69
90
@@ -73,17 +94,44 @@ def term():
73
94
tm = threading .Timer (timeout , term )
74
95
tm .start ()
75
96
76
- stdoutdata , stderrdata = nodejs .communicate (script )
97
+ stdin_buf = StringIO (json .dumps (fn )+ "\n " )
98
+ stdout_buf = StringIO ()
99
+ stderr_buf = StringIO ()
100
+
101
+ completed = [] # type: List[Union[cStringIO.InputType, cStringIO.OutputType]]
102
+ while len (completed ) < 3 :
103
+ rready , wready , _ = select .select ([nodejs .stdout , nodejs .stderr ], [nodejs .stdin ], [])
104
+ if nodejs .stdin in wready :
105
+ b = stdin_buf .read (select .PIPE_BUF )
106
+ if b :
107
+ os .write (nodejs .stdin .fileno (), b )
108
+ elif stdin_buf not in completed :
109
+ completed .append (stdin_buf )
110
+ for pipes in ((nodejs .stdout , stdout_buf ), (nodejs .stderr , stderr_buf )):
111
+ if pipes [0 ] in rready :
112
+ b = os .read (pipes [0 ].fileno (), select .PIPE_BUF )
113
+ if b :
114
+ pipes [1 ].write (b )
115
+ elif pipes [1 ] not in completed :
116
+ completed .append (pipes [1 ])
117
+ if stdout_buf .getvalue ().endswith ("\n " ):
118
+ for buf in (stdout_buf , stderr_buf ):
119
+ if buf not in completed :
120
+ completed .append (buf )
77
121
tm .cancel ()
78
122
123
+ stdin_buf .close ()
124
+ stdoutdata = stdout_buf .getvalue ()
125
+ stderrdata = stderr_buf .getvalue ()
126
+
79
127
def fn_linenum (): # type: () -> Text
80
128
return u"\n " .join (u"%04i %s" % (i + 1 , b ) for i , b in enumerate (fn .split ("\n " )))
81
129
82
- if killed :
83
- raise JavascriptException ( u"Long-running script killed after %s seconds. \n script was: \n %s \n " % ( timeout , fn_linenum ()))
84
-
85
- if nodejs . returncode != 0 :
86
- raise JavascriptException (u"Returncode was: %s\n script was:\n %s\n stdout was: '%s'\n stderr was: '%s'\n " % (nodejs .returncode , fn_linenum (), stdoutdata , stderrdata ))
130
+ if nodejs . poll () not in ( None , 0 ) :
131
+ if killed :
132
+ raise JavascriptException ( u"Long-running script killed after %s seconds. \n script was: \n %s \n " % ( timeout , fn_linenum ()))
133
+ else :
134
+ raise JavascriptException (u"Returncode was: %s\n script was:\n %s\n stdout was: '%s'\n stderr was: '%s'\n " % (nodejs .returncode , fn_linenum (), stdoutdata , stderrdata ))
87
135
else :
88
136
try :
89
137
return json .loads (stdoutdata )
0 commit comments