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