summaryrefslogtreecommitdiffstats
path: root/tools/valgrind/common.py
blob: d273bf7c96faed4e459593b47afa5c2f53f8e8ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# Copyright (c) 2009 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import os
import signal
import subprocess
import sys
import time


class TimeoutError(Exception):
  pass


def _print_line(line, flush=True):
  # Printing to a text file (including stdout) on Windows always winds up
  # using \r\n automatically.  On buildbot, this winds up being read by a master
  # running on Linux, so we manually convert crlf to '\n'
  print line.rstrip() + '\n',
  if flush:
    sys.stdout.flush()


def RunSubprocess(proc, timeout=0, detach=False):
  """ Runs a subprocess, until it finishes or |timeout| is exceeded and the
  process is killed with taskkill.  A |timeout| <= 0  means no timeout.

  Args:
    proc: list of process components (exe + args)
    timeout: how long to wait before killing, <= 0 means wait forever
    detach: Whether to pass the DETACHED_PROCESS argument to CreateProcess
        on Windows.  This is used by Purify subprocesses on buildbot which
        seem to get confused by the parent console that buildbot sets up.
  """

  logging.info("running %s, timeout %d sec" % (" ".join(proc), timeout))
  if detach:
    # see MSDN docs for "Process Creation Flags"
    DETACHED_PROCESS = 0x8
    p = subprocess.Popen(proc, creationflags=DETACHED_PROCESS)
  else:
    # For non-detached processes, manually read and print out stdout and stderr.
    # By default, the subprocess is supposed to inherit these from its parent,
    # however when run under buildbot, it seems unable to read data from a
    # grandchild process, so we have to read the child and print the data as if
    # it came from us for buildbot to read it.  We're not sure why this is
    # necessary.
    # TODO(erikkay): should we buffer stderr and stdout separately?
    p = subprocess.Popen(proc, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

  logging.info("started subprocess")

  # How long to wait (in seconds) before printing progress log messages.
  progress_delay = 300
  progress_delay_time = time.time() + progress_delay
  did_timeout = False
  if timeout > 0:
    wait_until = time.time() + timeout
  while p.poll() is None and not did_timeout:
    if not detach:
      line = p.stdout.readline()
      while line and not did_timeout:
        _print_line(line)
        line = p.stdout.readline()
        if timeout > 0:
          did_timeout = time.time() > wait_until
    else:
      # When we detach, blocking on reading stdout doesn't work, so we sleep
      # a short time and poll.
      time.sleep(0.5)
      if time.time() >= progress_delay_time:
        # Force output on a periodic basis to avoid getting killed off by the
        # buildbot.
        # TODO(erikkay): I'd prefer a less obtrusive 'print ".",' with a flush
        # but because of how we're doing subprocesses, this doesn't appear to
        # work reliably.
        logging.info("%s still running..." % os.path.basename(proc[0]))
        progress_delay_time = time.time() + progress_delay
    if timeout > 0:
      did_timeout = time.time() > wait_until

  if did_timeout:
    logging.info("process timed out")
  else:
    logging.info("process ended, did not time out")

  if did_timeout:
    if sys.platform == "win32":
      subprocess.call(["taskkill", "/T", "/F", "/PID", str(p.pid)])
    else:
      # Does this kill all children, too?
      os.kill(p.pid, signal.SIGINT)
    logging.error("KILLED %d" % p.pid)
    # Give the process a chance to actually die before continuing
    # so that cleanup can happen safely.
    time.sleep(1.0)
    logging.error("TIMEOUT waiting for %s" % proc[0])
    raise TimeoutError(proc[0])
  elif not detach:
    for line in p.stdout.readlines():
      _print_line(line, False)
    if sys.platform != 'darwin':   # stdout flush fails on Mac
      logging.info("flushing stdout")
      p.stdout.flush()

  logging.info("collecting result code")
  result = p.poll()
  if result:
    logging.error("%s exited with non-zero result code %d" % (proc[0], result))
  return result