| OLD | NEW |
| 1 # Copyright (C) 2010 Google Inc. All rights reserved. | 1 # Copyright (C) 2010 Google Inc. All rights reserved. |
| 2 # | 2 # |
| 3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
| 4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
| 5 # met: | 5 # met: |
| 6 # | 6 # |
| 7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
| 8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
| 9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
| 10 # copyright notice, this list of conditions and the following disclaimer | 10 # copyright notice, this list of conditions and the following disclaimer |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 28 | 28 |
| 29 """Package that implements the ServerProcess wrapper class""" | 29 """Package that implements the ServerProcess wrapper class""" |
| 30 | 30 |
| 31 import errno | 31 import errno |
| 32 import logging | 32 import logging |
| 33 import re |
| 33 import signal | 34 import signal |
| 34 import sys | 35 import sys |
| 35 import time | 36 import time |
| 36 | 37 |
| 37 # Note that although win32 python does provide an implementation of | 38 # Note that although win32 python does provide an implementation of |
| 38 # the win32 select API, it only works on sockets, and not on the named pipes | 39 # the win32 select API, it only works on sockets, and not on the named pipes |
| 39 # used by subprocess, so we have to use the native APIs directly. | 40 # used by subprocess, so we have to use the native APIs directly. |
| 40 _quote_cmd = None | 41 _quote_cmd = None |
| 41 | 42 |
| 42 if sys.platform == 'win32': | 43 if sys.platform == 'win32': |
| 43 import msvcrt | 44 import msvcrt |
| 44 import win32pipe | 45 import win32pipe |
| 45 import win32file | 46 import win32file |
| 46 import subprocess | 47 import subprocess |
| 47 _quote_cmd = subprocess.list2cmdline | 48 _quote_cmd = subprocess.list2cmdline |
| 48 else: | 49 else: |
| 49 import fcntl | 50 import fcntl |
| 50 import os | 51 import os |
| 51 import pipes | 52 import pipes |
| 52 import select | 53 import select |
| 53 _quote_cmd = lambda cmdline: ' '.join(pipes.quote(arg) for arg in cmdline) | 54 _quote_cmd = lambda cmdline: ' '.join(pipes.quote(arg) for arg in cmdline) |
| 54 | 55 |
| 55 from webkitpy.common.system.executive import ScriptError | 56 from webkitpy.common.system.executive import ScriptError |
| 56 | 57 |
| 57 | 58 |
| 58 _log = logging.getLogger(__name__) | 59 _log = logging.getLogger(__name__) |
| 59 | 60 |
| 60 | 61 |
| 62 _trailing_spaces_re = re.compile('(.*[^ ])?( +)$') |
| 63 |
| 64 |
| 65 def quote_data(data): |
| 66 txt = repr(data).replace('\\n', '\\n\n')[1:-1] |
| 67 lines = [] |
| 68 for l in txt.splitlines(): |
| 69 m = _trailing_spaces_re.match(l) |
| 70 if m: |
| 71 l = m.group(1) + m.group(2).replace(' ', '\x20') |
| 72 lines.append(l) |
| 73 return lines |
| 74 |
| 61 class ServerProcess(object): | 75 class ServerProcess(object): |
| 62 """This class provides a wrapper around a subprocess that | 76 """This class provides a wrapper around a subprocess that |
| 63 implements a simple request/response usage model. The primary benefit | 77 implements a simple request/response usage model. The primary benefit |
| 64 is that reading responses takes a deadline, so that we don't ever block | 78 is that reading responses takes a deadline, so that we don't ever block |
| 65 indefinitely. The class also handles transparently restarting processes | 79 indefinitely. The class also handles transparently restarting processes |
| 66 as necessary to keep issuing commands.""" | 80 as necessary to keep issuing commands.""" |
| 67 | 81 |
| 68 def __init__(self, port_obj, name, cmd, env=None, universal_newlines=False,
treat_no_data_as_crash=False, | 82 def __init__(self, port_obj, name, cmd, env=None, universal_newlines=False,
treat_no_data_as_crash=False, |
| 69 logging=False): | 83 logging=False): |
| 70 self._port = port_obj | 84 self._port = port_obj |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 114 def _start(self): | 128 def _start(self): |
| 115 if self._proc: | 129 if self._proc: |
| 116 raise ValueError("%s already running" % self._name) | 130 raise ValueError("%s already running" % self._name) |
| 117 self._reset() | 131 self._reset() |
| 118 # close_fds is a workaround for http://bugs.python.org/issue2320 | 132 # close_fds is a workaround for http://bugs.python.org/issue2320 |
| 119 close_fds = not self._host.platform.is_win() | 133 close_fds = not self._host.platform.is_win() |
| 120 if self._logging: | 134 if self._logging: |
| 121 env_str = '' | 135 env_str = '' |
| 122 if self._env: | 136 if self._env: |
| 123 env_str += '\n'.join("%s=%s" % (k, v) for k, v in self._env.item
s()) + '\n' | 137 env_str += '\n'.join("%s=%s" % (k, v) for k, v in self._env.item
s()) + '\n' |
| 124 _log.info('CMD: """\n%s%s\n"""', env_str, _quote_cmd(self._cmd)) | 138 _log.info('CMD: \n%s%s\n', env_str, _quote_cmd(self._cmd)) |
| 125 self._proc = self._host.executive.popen(self._cmd, stdin=self._host.exec
utive.PIPE, | 139 self._proc = self._host.executive.popen(self._cmd, stdin=self._host.exec
utive.PIPE, |
| 126 stdout=self._host.executive.PIPE, | 140 stdout=self._host.executive.PIPE, |
| 127 stderr=self._host.executive.PIPE, | 141 stderr=self._host.executive.PIPE, |
| 128 close_fds=close_fds, | 142 close_fds=close_fds, |
| 129 env=self._env, | 143 env=self._env, |
| 130 universal_newlines=self._universal_newlines) | 144 universal_newlines=self._universal_newlines) |
| 131 self._pid = self._proc.pid | 145 self._pid = self._proc.pid |
| 132 fd = self._proc.stdout.fileno() | 146 fd = self._proc.stdout.fileno() |
| 133 if not self._use_win32_apis: | 147 if not self._use_win32_apis: |
| 134 fl = fcntl.fcntl(fd, fcntl.F_GETFL) | 148 fl = fcntl.fcntl(fd, fcntl.F_GETFL) |
| (...skipping 20 matching lines...) Expand all Loading... |
| 155 if self._proc: | 169 if self._proc: |
| 156 return self._proc.poll() | 170 return self._proc.poll() |
| 157 return None | 171 return None |
| 158 | 172 |
| 159 def write(self, bytes): | 173 def write(self, bytes): |
| 160 """Write a request to the subprocess. The subprocess is (re-)start()'ed | 174 """Write a request to the subprocess. The subprocess is (re-)start()'ed |
| 161 if is not already running.""" | 175 if is not already running.""" |
| 162 if not self._proc: | 176 if not self._proc: |
| 163 self._start() | 177 self._start() |
| 164 try: | 178 try: |
| 165 if self._logging: | 179 self._log_data(' IN', bytes) |
| 166 _log.info(' IN: """%s"""', bytes) | |
| 167 self._proc.stdin.write(bytes) | 180 self._proc.stdin.write(bytes) |
| 168 except IOError, e: | 181 except IOError, e: |
| 169 self.stop(0.0) | 182 self.stop(0.0) |
| 170 # stop() calls _reset(), so we have to set crashed to True after cal
ling stop(). | 183 # stop() calls _reset(), so we have to set crashed to True after cal
ling stop(). |
| 171 self._crashed = True | 184 self._crashed = True |
| 172 | 185 |
| 173 def _pop_stdout_line_if_ready(self): | 186 def _pop_stdout_line_if_ready(self): |
| 174 index_after_newline = self._output.find('\n') + 1 | 187 index_after_newline = self._output.find('\n') + 1 |
| 175 if index_after_newline > 0: | 188 if index_after_newline > 0: |
| 176 return self._pop_output_bytes(index_after_newline) | 189 return self._pop_output_bytes(index_after_newline) |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 return None | 230 return None |
| 218 | 231 |
| 219 return self._read(deadline, retrieve_bytes_from_stdout_buffer) | 232 return self._read(deadline, retrieve_bytes_from_stdout_buffer) |
| 220 | 233 |
| 221 def _log(self, message): | 234 def _log(self, message): |
| 222 # This is a bit of a hack, but we first log a blank line to avoid | 235 # This is a bit of a hack, but we first log a blank line to avoid |
| 223 # messing up the master process's output. | 236 # messing up the master process's output. |
| 224 _log.info('') | 237 _log.info('') |
| 225 _log.info(message) | 238 _log.info(message) |
| 226 | 239 |
| 240 def _log_data(self, prefix, data): |
| 241 if self._logging and data and len(data): |
| 242 for line in quote_data(data): |
| 243 _log.info('%s: %s', prefix, line) |
| 244 |
| 227 def _handle_timeout(self): | 245 def _handle_timeout(self): |
| 228 self.timed_out = True | 246 self.timed_out = True |
| 229 self._port.sample_process(self._name, self._proc.pid) | 247 self._port.sample_process(self._name, self._proc.pid) |
| 230 | 248 |
| 231 def _split_string_after_index(self, string, index): | 249 def _split_string_after_index(self, string, index): |
| 232 return string[:index], string[index:] | 250 return string[:index], string[index:] |
| 233 | 251 |
| 234 def _pop_output_bytes(self, bytes_count): | 252 def _pop_output_bytes(self, bytes_count): |
| 235 output, self._output = self._split_string_after_index(self._output, byte
s_count) | 253 output, self._output = self._split_string_after_index(self._output, byte
s_count) |
| 236 return output | 254 return output |
| (...skipping 23 matching lines...) Expand all Loading... |
| 260 try: | 278 try: |
| 261 # Note that we may get no data during read() even though | 279 # Note that we may get no data during read() even though |
| 262 # select says we got something; see the select() man page | 280 # select says we got something; see the select() man page |
| 263 # on linux. I don't know if this happens on Mac OS and | 281 # on linux. I don't know if this happens on Mac OS and |
| 264 # other Unixen as well, but we don't bother special-casing | 282 # other Unixen as well, but we don't bother special-casing |
| 265 # Linux because it's relatively harmless either way. | 283 # Linux because it's relatively harmless either way. |
| 266 if out_fd in read_fds: | 284 if out_fd in read_fds: |
| 267 data = self._proc.stdout.read() | 285 data = self._proc.stdout.read() |
| 268 if not data and not stopping and (self._treat_no_data_as_crash o
r self._proc.poll()): | 286 if not data and not stopping and (self._treat_no_data_as_crash o
r self._proc.poll()): |
| 269 self._crashed = True | 287 self._crashed = True |
| 270 if self._logging and len(data): | 288 self._log_data('OUT', data) |
| 271 _log.info('OUT: """%s"""', data) | |
| 272 self._output += data | 289 self._output += data |
| 273 | 290 |
| 274 if err_fd in read_fds: | 291 if err_fd in read_fds: |
| 275 data = self._proc.stderr.read() | 292 data = self._proc.stderr.read() |
| 276 if not data and not stopping and (self._treat_no_data_as_crash o
r self._proc.poll()): | 293 if not data and not stopping and (self._treat_no_data_as_crash o
r self._proc.poll()): |
| 277 self._crashed = True | 294 self._crashed = True |
| 278 if self._logging and len(data): | 295 self._log_data('ERR', data) |
| 279 _log.info('ERR: """%s"""', data) | |
| 280 self._error += data | 296 self._error += data |
| 281 except IOError, e: | 297 except IOError, e: |
| 282 # We can ignore the IOErrors because we will detect if the subporces
s crashed | 298 # We can ignore the IOErrors because we will detect if the subporces
s crashed |
| 283 # the next time through the loop in _read() | 299 # the next time through the loop in _read() |
| 284 pass | 300 pass |
| 285 | 301 |
| 286 def _wait_for_data_and_update_buffers_using_win32_apis(self, deadline): | 302 def _wait_for_data_and_update_buffers_using_win32_apis(self, deadline): |
| 287 # See http://code.activestate.com/recipes/440554-module-to-allow-asynchr
onous-subprocess-use-on-win/ | 303 # See http://code.activestate.com/recipes/440554-module-to-allow-asynchr
onous-subprocess-use-on-win/ |
| 288 # and http://docs.activestate.com/activepython/2.6/pywin32/modules.html | 304 # and http://docs.activestate.com/activepython/2.6/pywin32/modules.html |
| 289 # for documentation on all of these win32-specific modules. | 305 # for documentation on all of these win32-specific modules. |
| 290 now = time.time() | 306 now = time.time() |
| 291 out_fh = msvcrt.get_osfhandle(self._proc.stdout.fileno()) | 307 out_fh = msvcrt.get_osfhandle(self._proc.stdout.fileno()) |
| 292 err_fh = msvcrt.get_osfhandle(self._proc.stderr.fileno()) | 308 err_fh = msvcrt.get_osfhandle(self._proc.stderr.fileno()) |
| 293 while (self._proc.poll() is None) and (now < deadline): | 309 while (self._proc.poll() is None) and (now < deadline): |
| 294 output = self._non_blocking_read_win32(out_fh) | 310 output = self._non_blocking_read_win32(out_fh) |
| 311 self._log_data('OUT', output) |
| 295 error = self._non_blocking_read_win32(err_fh) | 312 error = self._non_blocking_read_win32(err_fh) |
| 313 self._log_data('ERR', error) |
| 296 if output or error: | 314 if output or error: |
| 297 if output: | 315 if output: |
| 298 self._output += output | 316 self._output += output |
| 299 if error: | 317 if error: |
| 300 self._error += error | 318 self._error += error |
| 301 return | 319 return |
| 302 time.sleep(0.01) | 320 time.sleep(0.01) |
| 303 now = time.time() | 321 now = time.time() |
| 304 return | 322 return |
| 305 | 323 |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 345 if not self._proc: | 363 if not self._proc: |
| 346 self._start() | 364 self._start() |
| 347 | 365 |
| 348 def stop(self, timeout_secs=3.0): | 366 def stop(self, timeout_secs=3.0): |
| 349 if not self._proc: | 367 if not self._proc: |
| 350 return (None, None) | 368 return (None, None) |
| 351 | 369 |
| 352 now = time.time() | 370 now = time.time() |
| 353 if self._proc.stdin: | 371 if self._proc.stdin: |
| 354 if self._logging: | 372 if self._logging: |
| 355 _log.info(' IN: """^D"""') | 373 _log.info(' IN: ^D') |
| 356 self._proc.stdin.close() | 374 self._proc.stdin.close() |
| 357 self._proc.stdin = None | 375 self._proc.stdin = None |
| 358 killed = False | 376 killed = False |
| 359 if timeout_secs: | 377 if timeout_secs: |
| 360 deadline = now + timeout_secs | 378 deadline = now + timeout_secs |
| 361 while self._proc.poll() is None and time.time() < deadline: | 379 while self._proc.poll() is None and time.time() < deadline: |
| 362 time.sleep(0.01) | 380 time.sleep(0.01) |
| 363 if self._proc.poll() is None: | 381 if self._proc.poll() is None: |
| 364 _log.warning('stopping %s(pid %d) timed out, killing it' % (self
._name, self._proc.pid)) | 382 _log.warning('stopping %s(pid %d) timed out, killing it' % (self
._name, self._proc.pid)) |
| 365 | 383 |
| (...skipping 21 matching lines...) Expand all Loading... |
| 387 self._proc.wait() | 405 self._proc.wait() |
| 388 | 406 |
| 389 def replace_outputs(self, stdout, stderr): | 407 def replace_outputs(self, stdout, stderr): |
| 390 assert self._proc | 408 assert self._proc |
| 391 if stdout: | 409 if stdout: |
| 392 self._proc.stdout.close() | 410 self._proc.stdout.close() |
| 393 self._proc.stdout = stdout | 411 self._proc.stdout = stdout |
| 394 if stderr: | 412 if stderr: |
| 395 self._proc.stderr.close() | 413 self._proc.stderr.close() |
| 396 self._proc.stderr = stderr | 414 self._proc.stderr = stderr |
| OLD | NEW |