1# Copyright 2012 the V8 project authors. All rights reserved.
2# Redistribution and use in source and binary forms, with or without
3# modification, are permitted provided that the following conditions are
4# met:
5#
6#     * Redistributions of source code must retain the above copyright
7#       notice, this list of conditions and the following disclaimer.
8#     * Redistributions in binary form must reproduce the above
9#       copyright notice, this list of conditions and the following
10#       disclaimer in the documentation and/or other materials provided
11#       with the distribution.
12#     * Neither the name of Google Inc. nor the names of its
13#       contributors may be used to endorse or promote products derived
14#       from this software without specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28
29import os
30import signal
31import subprocess
32import sys
33import tempfile
34import time
35
36from ..local import utils
37from ..objects import output
38
39
40def KillProcessWithID(pid):
41  if utils.IsWindows():
42    os.popen('taskkill /T /F /PID %d' % pid)
43  else:
44    os.kill(pid, signal.SIGTERM)
45
46
47MAX_SLEEP_TIME = 0.1
48INITIAL_SLEEP_TIME = 0.0001
49SLEEP_TIME_FACTOR = 1.25
50
51SEM_INVALID_VALUE = -1
52SEM_NOGPFAULTERRORBOX = 0x0002  # Microsoft Platform SDK WinBase.h
53
54
55def Win32SetErrorMode(mode):
56  prev_error_mode = SEM_INVALID_VALUE
57  try:
58    import ctypes
59    prev_error_mode = \
60        ctypes.windll.kernel32.SetErrorMode(mode)  #@UndefinedVariable
61  except ImportError:
62    pass
63  return prev_error_mode
64
65
66def RunProcess(verbose, timeout, args, **rest):
67  if verbose: print "#", " ".join(args)
68  popen_args = args
69  prev_error_mode = SEM_INVALID_VALUE
70  if utils.IsWindows():
71    popen_args = subprocess.list2cmdline(args)
72    # Try to change the error mode to avoid dialogs on fatal errors. Don't
73    # touch any existing error mode flags by merging the existing error mode.
74    # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx.
75    error_mode = SEM_NOGPFAULTERRORBOX
76    prev_error_mode = Win32SetErrorMode(error_mode)
77    Win32SetErrorMode(error_mode | prev_error_mode)
78  process = subprocess.Popen(
79    shell=utils.IsWindows(),
80    args=popen_args,
81    **rest
82  )
83  if (utils.IsWindows() and prev_error_mode != SEM_INVALID_VALUE):
84    Win32SetErrorMode(prev_error_mode)
85  # Compute the end time - if the process crosses this limit we
86  # consider it timed out.
87  if timeout is None: end_time = None
88  else: end_time = time.time() + timeout
89  timed_out = False
90  # Repeatedly check the exit code from the process in a
91  # loop and keep track of whether or not it times out.
92  exit_code = None
93  sleep_time = INITIAL_SLEEP_TIME
94  try:
95    while exit_code is None:
96      if (not end_time is None) and (time.time() >= end_time):
97        # Kill the process and wait for it to exit.
98        KillProcessWithID(process.pid)
99        exit_code = process.wait()
100        timed_out = True
101      else:
102        exit_code = process.poll()
103        time.sleep(sleep_time)
104        sleep_time = sleep_time * SLEEP_TIME_FACTOR
105        if sleep_time > MAX_SLEEP_TIME:
106          sleep_time = MAX_SLEEP_TIME
107    return (exit_code, timed_out)
108  except KeyboardInterrupt:
109    raise
110
111
112def PrintError(string):
113  sys.stderr.write(string)
114  sys.stderr.write("\n")
115
116
117def CheckedUnlink(name):
118  # On Windows, when run with -jN in parallel processes,
119  # OS often fails to unlink the temp file. Not sure why.
120  # Need to retry.
121  # Idea from https://bugs.webkit.org/attachment.cgi?id=75982&action=prettypatch
122  retry_count = 0
123  while retry_count < 30:
124    try:
125      os.unlink(name)
126      return
127    except OSError, e:
128      retry_count += 1
129      time.sleep(retry_count * 0.1)
130  PrintError("os.unlink() " + str(e))
131
132
133def Execute(args, verbose=False, timeout=None):
134  args = [ c for c in args if c != "" ]
135  (fd_out, outname) = tempfile.mkstemp()
136  (fd_err, errname) = tempfile.mkstemp()
137  try:
138    (exit_code, timed_out) = RunProcess(
139      verbose,
140      timeout,
141      args=args,
142      stdout=fd_out,
143      stderr=fd_err
144    )
145  except:
146    raise
147  os.close(fd_out)
148  os.close(fd_err)
149  out = file(outname).read()
150  errors = file(errname).read()
151  CheckedUnlink(outname)
152  CheckedUnlink(errname)
153  return output.Output(exit_code, timed_out, out, errors)
154