Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(583)

Unified Diff: recipe_engine/step_runner.py

Issue 1959563002: Use subprocess42 in recipe_engine to avoid threading madness. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/recipes-py@master
Patch Set: Don't need a lock because we're single threaded now! Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | recipe_engine/third_party/subprocess42.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: recipe_engine/step_runner.py
diff --git a/recipe_engine/step_runner.py b/recipe_engine/step_runner.py
index c4d166f9abc283ddd36ece58a27ecb81f8756d06..c3b5a23245daa09a04f22930207df2376feda96d 100644
--- a/recipe_engine/step_runner.py
+++ b/recipe_engine/step_runner.py
@@ -9,10 +9,8 @@ import datetime
import json
import os
import re
-import subprocess
import sys
import tempfile
-import threading
import traceback
from . import recipe_test_api
@@ -20,6 +18,45 @@ from . import stream
from . import types
from . import util
+from .third_party import subprocess42
+
+if sys.platform.startswith('win'):
M-A Ruel 2016/05/06 14:12:51 if sys.platform == 'win32':
iannucci 2016/05/06 17:52:01 Done.
+ # Windows has a bad habit of opening a dialog when a console program
+ # crashes, rather than just letting it crash. Therefore, when a
+ # program crashes on Windows, we don't find out until the build step
+ # times out. This code prevents the dialog from appearing, so that we
+ # find out immediately and don't waste time waiting for a user to
+ # close the dialog.
+ import ctypes
+ # SetErrorMode(SEM_NOGPFAULTERRORBOX). For more information, see:
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx
+ ctypes.windll.kernel32.SetErrorMode(0x0002)
M-A Ruel 2016/05/06 14:12:51 s/0x0002/2/ ? :) That's a good idea! I hadn't tho
iannucci 2016/05/06 17:52:01 I was matching the text in the msdn docs :) Looki
M-A Ruel 2016/05/06 17:57:51 Good question. I'm not fond of changing the global
+
M-A Ruel 2016/05/06 14:12:51 you don't use 2 lines between file level symbols?
iannucci 2016/05/06 17:52:01 lol, done
+class _streamingLinebuf(object):
+ def __init__(self):
+ self.buffedlines = []
+ self.extra = None
+
+ def ingest(self, data):
+ lines = data.splitlines()
+ if self.extra:
+ lines[0] = self.extra + lines[0]
+ self.extra = None
+ if not data.endswith("\n"):
+ self.extra = lines[-1]
+ lines = lines[:-1]
+ self.buffedlines += lines
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ if len(self.buffedlines) > 0:
+ ret = self.buffedlines[0]
+ self.buffedlines = self.buffedlines[1:]
+ return ret
+ raise StopIteration()
+
class StepRunner(object):
"""A StepRunner is the interface to actually running steps.
@@ -152,9 +189,9 @@ class SubprocessStepRunner(StepRunner):
'stdin': None,
}
for key in handles:
- if key in step_dict:
- handles[key] = open(step_dict[key],
- 'rb' if key == 'stdin' else 'wb')
+ fileName = step_dict.get(key)
+ if fileName:
+ handles[key] = open(fileName, 'rb' if key == 'stdin' else 'wb')
# The subprocess will inherit and close these handles.
retcode = self._run_cmd(
cmd=cmd, handles=handles, env=step_env, cwd=step_dict.get('cwd'))
@@ -191,7 +228,7 @@ class SubprocessStepRunner(StepRunner):
'--output-result-json=%s' % f.name, recipe]
cmd.extend(['%s=%s' % (k,repr(v)) for k, v in properties.iteritems()])
M-A Ruel 2016/05/06 14:12:51 [OT] don't you want to sort the resulting list jus
iannucci 2016/05/06 17:52:01 I mean... probably, but this code is going to be r
- retcode = subprocess.call(cmd)
+ retcode = subprocess42.call(cmd)
result = json.load(f)
if retcode != 0:
raise recipe_api.StepFailure(
@@ -251,39 +288,22 @@ class SubprocessStepRunner(StepRunner):
# If we are given StreamEngine.Streams, map them to PIPE for subprocess.
# We will manually forward them to their corresponding stream.
for key in ('stdout', 'stderr'):
- if (key in handles and
- isinstance(handles[key], stream.StreamEngine.Stream)):
- fhandles[key] = subprocess.PIPE
+ handle = handles.get(key)
+ if isinstance(handle, stream.StreamEngine.Stream):
+ fhandles[key] = subprocess42.PIPE
else:
- fhandles[key] = handles[key]
+ fhandles[key] = handle
# stdin must be a real handle, if it exists
fhandles['stdin'] = handles.get('stdin')
- if sys.platform.startswith('win'):
- # Windows has a bad habit of opening a dialog when a console program
- # crashes, rather than just letting it crash. Therefore, when a
- # program crashes on Windows, we don't find out until the build step
- # times out. This code prevents the dialog from appearing, so that we
- # find out immediately and don't waste time waiting for a user to
- # close the dialog.
- import ctypes
- # SetErrorMode(SEM_NOGPFAULTERRORBOX). For more information, see:
- # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx
- ctypes.windll.kernel32.SetErrorMode(0x0002)
- # CREATE_NO_WINDOW. For more information, see:
- # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
- creationflags = 0x8000000
- else:
- creationflags = 0
-
with _modify_lookup_path(env.get('PATH')):
- proc = subprocess.Popen(
+ proc = subprocess42.Popen(
cmd,
env=env,
cwd=cwd,
universal_newlines=True,
- creationflags=creationflags,
+ detached=True,
**fhandles)
# Safe to close file handles now that subprocess has inherited them.
@@ -291,35 +311,27 @@ class SubprocessStepRunner(StepRunner):
if isinstance(handle, file):
handle.close()
- outlock = threading.Lock()
- def make_pipe_thread(inhandle, outstream):
- def body():
- while True:
- line = inhandle.readline()
- if not line:
- break
- line = line[:-1] # Strip newline for write_line's expectations
- # (universal_newlines is on, so it's only \n)
- outlock.acquire()
- try:
- outstream.write_line(line)
- finally:
- outlock.release()
- t = threading.Thread(target=body, args=())
- t.daemon = True
- return t
-
- threads = []
+ outstreams = {}
+ linebufs = {}
for key in ('stdout', 'stderr'):
- if (key in handles and
- isinstance(handles[key], stream.StreamEngine.Stream)):
- threads.append(make_pipe_thread(getattr(proc, key), handles[key]))
-
- for th in threads:
- th.start()
- proc.wait()
- for th in threads:
- th.join()
+ handle = handles.get(key)
+ if isinstance(handle, stream.StreamEngine.Stream):
+ outstreams[key] = handle
+ linebufs[key] = _streamingLinebuf()
+
+ if len(linebufs) > 0:
M-A Ruel 2016/05/06 14:12:52 if linebufs:
iannucci 2016/05/06 17:52:01 Done.
+ for pipe, data in proc.yield_any(timeout=1):
+ if pipe is None:
+ continue
+ buf = linebufs.get(pipe)
+ if not buf:
+ continue
+ buf.ingest(data)
+ for line in buf:
+ outstreams[pipe].write_line(line)
+ else:
+ proc.wait()
+
return proc.returncode
def _trigger_builds(self, step, trigger_specs):
« no previous file with comments | « no previous file | recipe_engine/third_party/subprocess42.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698