2d783e721d3cac498cd63dedfcc64e95e9513eb6
[WebKit-https.git] / Tools / Scripts / webkitpy / port / server_process.py
1 # Copyright (C) 2017 Apple Inc. All rights reserved.
2 # Copyright (C) 2010 Google Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the Google name nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 """Package that implements the ServerProcess wrapper class"""
31
32 import errno
33 import logging
34 import signal
35 import sys
36 import time
37
38 # Note that although win32 python does provide an implementation of
39 # the win32 select API, it only works on sockets, and not on the named pipes
40 # used by subprocess, so we have to use the native APIs directly.
41 if sys.platform.startswith('win'):
42     import msvcrt
43     import win32pipe
44     import win32file
45 else:
46     import fcntl
47     import os
48     import select
49
50 from webkitpy.common.system.executive import ScriptError
51
52
53 _log = logging.getLogger(__name__)
54
55
56 class ServerProcess(object):
57     """This class provides a wrapper around a subprocess that
58     implements a simple request/response usage model. The primary benefit
59     is that reading responses takes a deadline, so that we don't ever block
60     indefinitely. The class also handles transparently restarting processes
61     as necessary to keep issuing commands."""
62
63     def __init__(self, port_obj, name, cmd, env=None, universal_newlines=False, treat_no_data_as_crash=False, target_host=None):
64         self._port = port_obj
65         self._name = name  # Should be the command name (e.g. DumpRenderTree, ImageDiff)
66         self._cmd = cmd
67         self._env = env
68         # Set if the process outputs non-standard newlines like '\r\n' or '\r'.
69         # Don't set if there will be binary data or the data must be ASCII encoded.
70         self._universal_newlines = universal_newlines
71         self._treat_no_data_as_crash = treat_no_data_as_crash
72         self._target_host = target_host or port_obj.host
73         self._pid = None
74         self._reset()
75
76         # See comment in imports for why we need the win32 APIs and can't just use select.
77         # FIXME: there should be a way to get win32 vs. cygwin from platforminfo.
78         self._use_win32_apis = sys.platform.startswith('win')
79
80     def pid(self):
81         return self._pid
82
83     def _reset(self):
84         if getattr(self, '_proc', None):
85             if self._proc.stdin:
86                 self._proc.stdin.close()
87                 self._proc.stdin = None
88             if self._proc.stdout:
89                 self._proc.stdout.close()
90                 self._proc.stdout = None
91             if self._proc.stderr:
92                 self._proc.stderr.close()
93                 self._proc.stderr = None
94
95         self._proc = None
96         self._output = str()  # bytesarray() once we require Python 2.6
97         self._error = str()  # bytesarray() once we require Python 2.6
98         self._crashed = False
99         self.timed_out = False
100
101     def process_name(self):
102         return self._name
103
104     @staticmethod
105     def _set_file_nonblocking(file):
106         flags = fcntl.fcntl(file.fileno(), fcntl.F_GETFL)
107         fcntl.fcntl(file.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
108
109     def _start(self):
110         if self._proc:
111             raise ValueError("%s already running" % self._name)
112         self._reset()
113         self._proc = self._target_host.executive.popen(self._cmd, stdin=self._target_host.executive.PIPE,
114             stdout=self._target_host.executive.PIPE,
115             stderr=self._target_host.executive.PIPE,
116             close_fds=self._should_close_fds(),
117             env=self._env,
118             universal_newlines=self._universal_newlines)
119         self._pid = self._proc.pid
120         if not self._use_win32_apis:
121             self._set_file_nonblocking(self._proc.stdout)
122             self._set_file_nonblocking(self._proc.stderr)
123
124     def _should_close_fds(self):
125         # We need to pass close_fds=True to work around Python bug #2320
126         # (otherwise we can hang when we kill DumpRenderTree when we are running
127         # multiple threads). See http://bugs.python.org/issue2320 .
128         # In Python 2.7.10, close_fds is also supported on Windows.
129         # However, "you cannot set close_fds to true and also redirect the standard
130         # handles by setting stdin, stdout or stderr.".
131         platform = self._port.host.platform
132         if platform.is_win() and not platform.is_cygwin():
133             return False
134         else:
135             return True
136
137     def _handle_possible_interrupt(self):
138         """This routine checks to see if the process crashed or exited
139         because of a keyboard interrupt and raises KeyboardInterrupt
140         accordingly."""
141         # FIXME: Linux and Mac set the returncode to -signal.SIGINT if a
142         # subprocess is killed with a ctrl^C.  Previous comments in this
143         # routine said that supposedly Windows returns 0xc000001d, but that's not what
144         # -1073741510 evaluates to. Figure out what the right value is
145         # for win32 and cygwin here ...
146         if self._proc.returncode in (-1073741510, -signal.SIGINT):
147             raise KeyboardInterrupt
148
149     def poll(self):
150         """Check to see if the underlying process is running; returns None
151         if it still is (wrapper around subprocess.poll)."""
152         if self._proc:
153             return self._proc.poll()
154         return None
155
156     def write(self, bytes, ignore_crash=False):
157         """Write a request to the subprocess. The subprocess is (re-)start()'ed
158         if is not already running."""
159         if not self._proc:
160             self._start()
161         try:
162             self._proc.stdin.write(bytes)
163         except IOError, e:
164             self.stop(0.0)
165             # stop() calls _reset(), so we have to set crashed to True after calling stop()
166             # unless we already know that this is a timeout.
167             if not ignore_crash:
168                 _log.debug('This test marked as a crash because of a broken pipe when writing to stdin of the server process.')
169                 self._crashed = True
170
171     def _pop_stdout_line_if_ready(self):
172         index_after_newline = self._output.find('\n') + 1
173         if index_after_newline > 0:
174             return self._pop_output_bytes(index_after_newline)
175         return None
176
177     def _pop_stderr_line_if_ready(self):
178         index_after_newline = self._error.find('\n') + 1
179         if index_after_newline > 0:
180             return self._pop_error_bytes(index_after_newline)
181         return None
182
183     def pop_all_buffered_stderr(self):
184         return self._pop_error_bytes(len(self._error))
185
186     def read_stdout_line(self, deadline):
187         return self._read(deadline, self._pop_stdout_line_if_ready)
188
189     def read_stderr_line(self, deadline):
190         return self._read(deadline, self._pop_stderr_line_if_ready)
191
192     def read_either_stdout_or_stderr_line(self, deadline):
193         def retrieve_bytes_from_buffers():
194             stdout_line = self._pop_stdout_line_if_ready()
195             if stdout_line:
196                 return stdout_line, None
197             stderr_line = self._pop_stderr_line_if_ready()
198             if stderr_line:
199                 return None, stderr_line
200             return None  # Instructs the caller to keep waiting.
201
202         return_value = self._read(deadline, retrieve_bytes_from_buffers)
203         # FIXME: This is a bit of a hack around the fact that _read normally only returns one value, but this caller wants it to return two.
204         if return_value is None:
205             return None, None
206         return return_value
207
208     def read_stdout(self, deadline, size):
209         if size <= 0:
210             raise ValueError('ServerProcess.read() called with a non-positive size: %d ' % size)
211
212         def retrieve_bytes_from_stdout_buffer():
213             if len(self._output) >= size:
214                 return self._pop_output_bytes(size)
215             return None
216
217         return self._read(deadline, retrieve_bytes_from_stdout_buffer)
218
219     def _log(self, message):
220         # This is a bit of a hack, but we first log a blank line to avoid
221         # messing up the master process's output.
222         _log.info('')
223         _log.info(message)
224
225     def _handle_timeout(self):
226         self.timed_out = True
227         if self._port.get_option("sample_on_timeout"):
228             self._port.sample_process(self._name, self._proc.pid, self._target_host)
229
230     def _split_string_after_index(self, string, index):
231         return string[:index], string[index:]
232
233     def _pop_output_bytes(self, bytes_count):
234         output, self._output = self._split_string_after_index(self._output, bytes_count)
235         return output
236
237     def _pop_error_bytes(self, bytes_count):
238         output, self._error = self._split_string_after_index(self._error, bytes_count)
239         return output
240
241     def _wait_for_data_and_update_buffers_using_select(self, deadline, stopping=False):
242         if self._proc.stdout.closed or self._proc.stderr.closed:
243             # If the process crashed and is using FIFOs, like Chromium Android, the
244             # stdout and stderr pipes will be closed.
245             return
246
247         out_fd = self._proc.stdout.fileno()
248         err_fd = self._proc.stderr.fileno()
249         select_fds = (out_fd, err_fd)
250         try:
251             read_fds, _, _ = select.select(select_fds, [], select_fds, max(deadline - time.time(), 0))
252         except select.error, e:
253             # We can ignore EINVAL since it's likely the process just crashed and we'll
254             # figure that out the next time through the loop in _read().
255             # We also ignore EINTR as we can resume reading the next time
256             # through the loop in _read().
257             if e.args[0] in [errno.EINVAL, errno.EINTR]:
258                 return
259             raise
260
261         try:
262             # Note that we may get no data during read() even though
263             # select says we got something; see the select() man page
264             # on linux. I don't know if this happens on Mac OS and
265             # other Unixen as well, but we don't bother special-casing
266             # Linux because it's relatively harmless either way.
267             if out_fd in read_fds:
268                 data = self._proc.stdout.read()
269                 if not data and not stopping and (self._treat_no_data_as_crash or self._proc.poll()):
270                     _log.debug('This test marked as a crash because of no data while reading stdout for the server process.')
271                     self._crashed = True
272                 self._output += data
273
274             if err_fd in read_fds:
275                 data = self._proc.stderr.read()
276                 if not data and not stopping and (self._treat_no_data_as_crash or self._proc.poll()):
277                     _log.debug('This test marked as a crash because of no data while reading stdout for the server process.')
278                     self._crashed = True
279                 self._error += data
280         except IOError, e:
281             # We can ignore the IOErrors because we will detect if the subporcess crashed
282             # the next time through the loop in _read()
283             pass
284
285     def _wait_for_data_and_update_buffers_using_win32_apis(self, deadline):
286         # See http://code.activestate.com/recipes/440554-module-to-allow-asynchronous-subprocess-use-on-win/
287         # and http://docs.activestate.com/activepython/2.6/pywin32/modules.html
288         # for documentation on all of these win32-specific modules.
289         now = time.time()
290         out_fh = msvcrt.get_osfhandle(self._proc.stdout.fileno())
291         err_fh = msvcrt.get_osfhandle(self._proc.stderr.fileno())
292         while (self._proc.poll() is None) and (now < deadline):
293             output = self._non_blocking_read_win32(out_fh)
294             error = self._non_blocking_read_win32(err_fh)
295             if output or error:
296                 if output:
297                     self._output += output
298                 if error:
299                     self._error += error
300                 return
301             time.sleep(0.01)
302             now = time.time()
303         return
304
305     def _non_blocking_read_win32(self, handle):
306         try:
307             _, avail, _ = win32pipe.PeekNamedPipe(handle, 0)
308             if avail > 0:
309                 _, buf = win32file.ReadFile(handle, avail, None)
310                 return buf
311         except Exception, e:
312             if e[0] not in (109, errno.ESHUTDOWN):  # 109 == win32 ERROR_BROKEN_PIPE
313                 raise
314         return None
315
316     def has_crashed(self):
317         if not self._crashed and self.poll():
318             _log.debug('This test marked as a crash because of failure to poll the server process.')
319             self._crashed = True
320             self._handle_possible_interrupt()
321         return self._crashed
322
323     # This read function is a bit oddly-designed, as it polls both stdout and stderr, yet
324     # only reads/returns from one of them (buffering both in local self._output/self._error).
325     # It might be cleaner to pass in the file descriptor to poll instead.
326     def _read(self, deadline, fetch_bytes_from_buffers_callback):
327         while True:
328             # Polling does not need to occur before bytes are fetched from the buffer.
329             if self._crashed:
330                 return None
331
332             if time.time() > deadline:
333                 self._handle_timeout()
334                 return None
335
336             bytes = fetch_bytes_from_buffers_callback()
337             if bytes is not None:
338                 return bytes
339
340             if self.has_crashed():
341                 return None
342
343             if self._use_win32_apis:
344                 self._wait_for_data_and_update_buffers_using_win32_apis(deadline)
345             else:
346                 self._wait_for_data_and_update_buffers_using_select(deadline)
347
348     def start(self):
349         if not self._proc:
350             self._start()
351
352     def stop(self, timeout_secs=3.0):
353         if not self._proc:
354             return (None, None)
355
356         # Only bother to check for leaks or stderr if the process is still running.
357         if self.poll() is None:
358             self._port.check_for_leaks(self.process_name(), self.pid())
359
360         if self._proc.stdin:
361             self._proc.stdin.close()
362             self._proc.stdin = None
363
364         return self._wait_for_stop(timeout_secs)
365
366     def _wait_for_stop(self, timeout_secs=3.0):
367         now = time.time()
368         killed = False
369         if timeout_secs:
370             deadline = now + timeout_secs
371             while self._proc and self._proc.poll() is None and time.time() < deadline:
372                 time.sleep(0.01)
373             if self._proc and self._proc.poll() is None:
374                 _log.warning('stopping %s(pid %d) timed out, killing it' % (self._name, self._proc.pid))
375
376         if self._proc and self._proc.poll() is None:
377             self._kill()
378             killed = True
379             _log.debug('killed pid %d' % self._proc.pid)
380
381         # read any remaining data on the pipes and return it.
382         if self._proc and not killed:
383             if self._use_win32_apis:
384                 self._wait_for_data_and_update_buffers_using_win32_apis(now)
385             else:
386                 self._wait_for_data_and_update_buffers_using_select(now, stopping=True)
387         out, err = self._output, self._error
388         self._reset()
389         return (out, err)
390
391     def kill(self):
392         self.stop(0.0)
393
394     def _kill(self):
395         self._target_host.executive.kill_process(self._proc.pid)
396         if self._proc.poll() is None:
397             self._proc.wait()
398
399     def replace_outputs(self, stdout, stderr):
400         assert self._proc
401         if stdout:
402             self._proc.stdout.close()
403             self._proc.stdout = stdout
404         if stderr:
405             self._proc.stderr.close()
406             self._proc.stderr = stderr