iOS 12 Simulator Release WK2 frequently timing out while running layout tests
[WebKit-https.git] / Tools / Scripts / webkitpy / port / server_process.py
1 # Copyright (C) 2017-2019 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
68         # Windows does not allow unicode values in the environment
69         if env and self._port.host.platform.is_native_win():
70             self._env = {key: env[key].encode('utf-8') for key in env}
71         else:
72             self._env = env
73
74         # Set if the process outputs non-standard newlines like '\r\n' or '\r'.
75         # Don't set if there will be binary data or the data must be ASCII encoded.
76         self._universal_newlines = universal_newlines
77         self._treat_no_data_as_crash = treat_no_data_as_crash
78         self._target_host = target_host or port_obj.host
79         self._pid = None
80         self._child_processes = {}
81         self._reset()
82
83         # See comment in imports for why we need the win32 APIs and can't just use select.
84         # FIXME: there should be a way to get win32 vs. cygwin from platforminfo.
85         self._use_win32_apis = sys.platform.startswith('win')
86
87     def child_processes(self):
88         return self._child_processes
89
90     def set_child_processes(self, child_processes):
91         self._child_processes = child_processes
92
93     def pid(self):
94         return self._pid
95
96     def _reset(self):
97         if getattr(self, '_proc', None):
98             if self._proc.stdin:
99                 self._proc.stdin.close()
100                 self._proc.stdin = None
101             if self._proc.stdout:
102                 self._proc.stdout.close()
103                 self._proc.stdout = None
104             if self._proc.stderr:
105                 self._proc.stderr.close()
106                 self._proc.stderr = None
107
108         self._proc = None
109         self._output = str()  # bytesarray() once we require Python 2.6
110         self._error = str()  # bytesarray() once we require Python 2.6
111         self._crashed = False
112         self.timed_out = False
113
114     def process_name(self):
115         return self._name
116
117     @staticmethod
118     def _set_file_nonblocking(file):
119         flags = fcntl.fcntl(file.fileno(), fcntl.F_GETFL)
120         fcntl.fcntl(file.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
121
122     def _start(self):
123         if self._proc:
124             raise ValueError("%s already running" % self._name)
125         self._reset()
126         self._proc = self._target_host.executive.popen(self._cmd, stdin=self._target_host.executive.PIPE,
127             stdout=self._target_host.executive.PIPE,
128             stderr=self._target_host.executive.PIPE,
129             close_fds=self._should_close_fds(),
130             env=self._env,
131             universal_newlines=self._universal_newlines)
132         self._pid = self._proc.pid
133         self._child_processes = {}
134         if not self._use_win32_apis:
135             self._set_file_nonblocking(self._proc.stdout)
136             self._set_file_nonblocking(self._proc.stderr)
137
138     def _should_close_fds(self):
139         # We need to pass close_fds=True to work around Python bug #2320
140         # (otherwise we can hang when we kill DumpRenderTree when we are running
141         # multiple threads). See http://bugs.python.org/issue2320 .
142         # In Python 2.7.10, close_fds is also supported on Windows.
143         # However, "you cannot set close_fds to true and also redirect the standard
144         # handles by setting stdin, stdout or stderr.".
145         platform = self._port.host.platform
146         if platform.is_win() and not platform.is_cygwin():
147             return False
148         else:
149             return True
150
151     def _handle_possible_interrupt(self):
152         """This routine checks to see if the process crashed or exited
153         because of a keyboard interrupt and raises KeyboardInterrupt
154         accordingly."""
155         # FIXME: Linux and Mac set the returncode to -signal.SIGINT if a
156         # subprocess is killed with a ctrl^C.  Previous comments in this
157         # routine said that supposedly Windows returns 0xc000001d, but that's not what
158         # -1073741510 evaluates to. Figure out what the right value is
159         # for win32 and cygwin here ...
160         if self._proc.returncode in (-1073741510, -signal.SIGINT):
161             raise KeyboardInterrupt
162
163     def poll(self):
164         """Check to see if the underlying process is running; returns None
165         if it still is (wrapper around subprocess.poll)."""
166         if self._proc:
167             return self._proc.poll()
168         return None
169
170     def write(self, bytes, ignore_crash=False):
171         """Write a request to the subprocess. The subprocess is (re-)start()'ed
172         if is not already running."""
173         if not self._proc:
174             self._start()
175         try:
176             self._proc.stdin.write(bytes)
177         except IOError as e:
178             self.stop(0.0)
179             # stop() calls _reset(), so we have to set crashed to True after calling stop()
180             # unless we already know that this is a timeout.
181             if not ignore_crash:
182                 _log.debug('This test marked as a crash because of a broken pipe when writing to stdin of the server process.')
183                 self._crashed = True
184
185     def _pop_stdout_line_if_ready(self):
186         index_after_newline = self._output.find('\n') + 1
187         if index_after_newline > 0:
188             return self._pop_output_bytes(index_after_newline)
189         return None
190
191     def _pop_stderr_line_if_ready(self):
192         index_after_newline = self._error.find('\n') + 1
193         if index_after_newline > 0:
194             return self._pop_error_bytes(index_after_newline)
195         return None
196
197     def pop_all_buffered_stdout(self):
198         return self._pop_output_bytes(len(self._output))
199
200     def pop_all_buffered_stderr(self):
201         return self._pop_error_bytes(len(self._error))
202
203     def read_stdout_line(self, deadline):
204         return self._read(deadline, self._pop_stdout_line_if_ready)
205
206     def has_available_stdout(self):
207         if not self.has_crashed() and self._use_win32_apis:
208             self._wait_for_data_and_update_buffers_using_win32_apis(0)
209         elif not self.has_crashed():
210             self._wait_for_data_and_update_buffers_using_select(0)
211
212         return bool(self._output)
213
214     def read_stderr_line(self, deadline):
215         return self._read(deadline, self._pop_stderr_line_if_ready)
216
217     def read_either_stdout_or_stderr_line(self, deadline):
218         def retrieve_bytes_from_buffers():
219             stdout_line = self._pop_stdout_line_if_ready()
220             if stdout_line:
221                 return stdout_line, None
222             stderr_line = self._pop_stderr_line_if_ready()
223             if stderr_line:
224                 return None, stderr_line
225             return None  # Instructs the caller to keep waiting.
226
227         return_value = self._read(deadline, retrieve_bytes_from_buffers)
228         # 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.
229         if return_value is None:
230             return None, None
231         return return_value
232
233     def read_stdout(self, deadline, size):
234         if size <= 0:
235             raise ValueError('ServerProcess.read() called with a non-positive size: %d ' % size)
236
237         def retrieve_bytes_from_stdout_buffer():
238             if len(self._output) >= size:
239                 return self._pop_output_bytes(size)
240             return None
241
242         return self._read(deadline, retrieve_bytes_from_stdout_buffer)
243
244     def _log(self, message):
245         # This is a bit of a hack, but we first log a blank line to avoid
246         # messing up the master process's output.
247         _log.info('')
248         _log.info(message)
249
250     def _handle_timeout(self):
251         self.timed_out = True
252         if self._port.get_option("sample_on_timeout"):
253             self._port.sample_process(self._name, self._proc.pid, self._target_host)
254
255     def _split_string_after_index(self, string, index):
256         return string[:index], string[index:]
257
258     def _pop_output_bytes(self, bytes_count):
259         output, self._output = self._split_string_after_index(self._output, bytes_count)
260         return output
261
262     def _pop_error_bytes(self, bytes_count):
263         output, self._error = self._split_string_after_index(self._error, bytes_count)
264         return output
265
266     def _wait_for_data_and_update_buffers_using_select(self, deadline, stopping=False):
267         if not self._proc or self._proc.stdout.closed or self._proc.stderr.closed:
268             # If the process crashed and is using FIFOs, like Chromium Android, the
269             # stdout and stderr pipes will be closed.
270             return
271
272         out_fd = self._proc.stdout.fileno()
273         err_fd = self._proc.stderr.fileno()
274         select_fds = (out_fd, err_fd)
275         try:
276             read_fds, _, _ = select.select(select_fds, [], select_fds, max(deadline - time.time(), 0))
277         except select.error as e:
278             # We can ignore EINVAL since it's likely the process just crashed and we'll
279             # figure that out the next time through the loop in _read().
280             # We also ignore EINTR as we can resume reading the next time
281             # through the loop in _read().
282             if e.args[0] in [errno.EINVAL, errno.EINTR]:
283                 return
284             raise
285
286         try:
287             # Note that we may get no data during read() even though
288             # select says we got something; see the select() man page
289             # on linux. I don't know if this happens on Mac OS and
290             # other Unixen as well, but we don't bother special-casing
291             # Linux because it's relatively harmless either way.
292             if out_fd in read_fds:
293                 data = self._proc.stdout.read()
294                 if not data and not stopping and (self._treat_no_data_as_crash or self._proc.poll()):
295                     _log.debug('This test marked as a crash because of no data while reading stdout for the server process.')
296                     self._crashed = True
297                 self._output += data
298
299             if err_fd in read_fds:
300                 data = self._proc.stderr.read()
301                 if not data and not stopping and (self._treat_no_data_as_crash or self._proc.poll()):
302                     _log.debug('This test marked as a crash because of no data while reading stdout for the server process.')
303                     self._crashed = True
304                 self._error += data
305         except IOError as e:
306             # We can ignore the IOErrors because we will detect if the subporcess crashed
307             # the next time through the loop in _read()
308             pass
309
310     def _wait_for_data_and_update_buffers_using_win32_apis(self, deadline):
311         if not self._proc:
312             return
313
314         # See http://code.activestate.com/recipes/440554-module-to-allow-asynchronous-subprocess-use-on-win/
315         # and http://docs.activestate.com/activepython/2.6/pywin32/modules.html
316         # for documentation on all of these win32-specific modules.
317         out_fh = msvcrt.get_osfhandle(self._proc.stdout.fileno())
318         err_fh = msvcrt.get_osfhandle(self._proc.stderr.fileno())
319         checking = True
320         while checking:
321             output = self._non_blocking_read_win32(out_fh)
322             error = self._non_blocking_read_win32(err_fh)
323             if output or error:
324                 if output:
325                     self._output += output
326                 if error:
327                     self._error += error
328                 return
329             if self._proc.poll() is not None:
330                 return
331             time.sleep(0.01)
332             checking = time.time() < deadline
333
334     def _non_blocking_read_win32(self, handle):
335         try:
336             _, avail, _ = win32pipe.PeekNamedPipe(handle, 0)
337             if avail > 0:
338                 _, buf = win32file.ReadFile(handle, avail, None)
339                 return buf
340         except Exception as e:
341             if e[0] not in (109, errno.ESHUTDOWN):  # 109 == win32 ERROR_BROKEN_PIPE
342                 raise
343         return None
344
345     def has_crashed(self):
346         if not self._crashed and self.poll():
347             _log.debug('This test marked as a crash because of failure to poll the server process (return code was %s).' % self._proc.returncode)
348             self._crashed = True
349             self._handle_possible_interrupt()
350         return self._crashed
351
352     # This read function is a bit oddly-designed, as it polls both stdout and stderr, yet
353     # only reads/returns from one of them (buffering both in local self._output/self._error).
354     # It might be cleaner to pass in the file descriptor to poll instead.
355     def _read(self, deadline, fetch_bytes_from_buffers_callback):
356         while True:
357             # Polling does not need to occur before bytes are fetched from the buffer.
358             if self._crashed:
359                 return None
360
361             if time.time() > deadline:
362                 self._handle_timeout()
363                 return None
364
365             bytes = fetch_bytes_from_buffers_callback()
366             if bytes is not None:
367                 return bytes
368
369             if self.has_crashed():
370                 return None
371
372             if self._use_win32_apis:
373                 self._wait_for_data_and_update_buffers_using_win32_apis(deadline)
374             else:
375                 self._wait_for_data_and_update_buffers_using_select(deadline)
376
377     def start(self):
378         if not self._proc:
379             self._start()
380
381     def stop(self, timeout_secs=3.0):
382         if not self._proc:
383             return (None, None)
384
385         # Only bother to check for leaks or stderr if the process is still running.
386         if self.poll() is None:
387             self._port.check_for_leaks(self.process_name(), self.pid())
388             for child_process_name in self._child_processes.keys():
389                 for child_process_id in self._child_processes[child_process_name]:
390                     self._port.check_for_leaks(child_process_name, child_process_id)
391
392         if self._proc.stdin:
393             self._proc.stdin.close()
394             self._proc.stdin = None
395
396         return self._wait_for_stop(timeout_secs)
397
398     def _wait_for_stop(self, timeout_secs=3.0):
399         now = time.time()
400         killed = False
401         if timeout_secs:
402             deadline = now + timeout_secs
403             while self._proc and self._proc.poll() is None and time.time() < deadline:
404                 time.sleep(0.01)
405             if self._proc and self._proc.poll() is None:
406                 _log.warning('stopping %s(pid %d) timed out, killing it' % (self._name, self._proc.pid))
407
408         if self._proc and self._proc.poll() is None:
409             self._kill()
410             killed = True
411             _log.debug('killed pid %d' % self._proc.pid)
412
413         # read any remaining data on the pipes and return it.
414         if self._proc and not killed:
415             if self._use_win32_apis:
416                 self._wait_for_data_and_update_buffers_using_win32_apis(now)
417             else:
418                 self._wait_for_data_and_update_buffers_using_select(now, stopping=True)
419         out, err = self._output, self._error
420         self._reset()
421         return (out, err)
422
423     def kill(self):
424         self.stop(0.0)
425
426     def _kill(self):
427         self._target_host.executive.kill_process(self._proc.pid)
428         if self._proc.poll() is None:
429             self._proc.wait()
430
431     def replace_outputs(self, stdout, stderr):
432         assert self._proc
433         if stdout:
434             self._proc.stdout.close()
435             self._proc.stdout = stdout
436         if stderr:
437             self._proc.stderr.close()
438             self._proc.stderr = stderr