js/src/tests/lib/tasks_unix.py

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 # A unix-oriented process dispatcher. Uses a single thread with select and
michael@0 2 # waitpid to dispatch tasks. This avoids several deadlocks that are possible
michael@0 3 # with fork/exec + threads + Python.
michael@0 4
michael@0 5 import errno, os, select
michael@0 6 from datetime import datetime, timedelta
michael@0 7 from results import TestOutput
michael@0 8
michael@0 9 class Task(object):
michael@0 10 def __init__(self, test, pid, stdout, stderr):
michael@0 11 self.test = test
michael@0 12 self.cmd = test.get_command(test.js_cmd_prefix)
michael@0 13 self.pid = pid
michael@0 14 self.stdout = stdout
michael@0 15 self.stderr = stderr
michael@0 16 self.start = datetime.now()
michael@0 17 self.out = []
michael@0 18 self.err = []
michael@0 19
michael@0 20 def spawn_test(test, passthrough = False):
michael@0 21 """Spawn one child, return a task struct."""
michael@0 22 if not passthrough:
michael@0 23 (rout, wout) = os.pipe()
michael@0 24 (rerr, werr) = os.pipe()
michael@0 25
michael@0 26 rv = os.fork()
michael@0 27
michael@0 28 # Parent.
michael@0 29 if rv:
michael@0 30 os.close(wout)
michael@0 31 os.close(werr)
michael@0 32 return Task(test, rv, rout, rerr)
michael@0 33
michael@0 34 # Child.
michael@0 35 os.close(rout)
michael@0 36 os.close(rerr)
michael@0 37
michael@0 38 os.dup2(wout, 1)
michael@0 39 os.dup2(werr, 2)
michael@0 40
michael@0 41 cmd = test.get_command(test.js_cmd_prefix)
michael@0 42 os.execvp(cmd[0], cmd)
michael@0 43
michael@0 44 def total_seconds(td):
michael@0 45 """
michael@0 46 Return the total number of seconds contained in the duration as a float
michael@0 47 """
michael@0 48 return (float(td.microseconds) + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
michael@0 49
michael@0 50 def get_max_wait(tasks, results, timeout):
michael@0 51 """
michael@0 52 Return the maximum time we can wait before any task should time out.
michael@0 53 """
michael@0 54
michael@0 55 # If we have a progress-meter, we need to wake up to update it frequently.
michael@0 56 wait = results.pb.update_granularity()
michael@0 57
michael@0 58 # If a timeout is supplied, we need to wake up for the first task to
michael@0 59 # timeout if that is sooner.
michael@0 60 if timeout:
michael@0 61 now = datetime.now()
michael@0 62 timeout_delta = timedelta(seconds=timeout)
michael@0 63 for task in tasks:
michael@0 64 remaining = task.start + timeout_delta - now
michael@0 65 if remaining < wait:
michael@0 66 wait = remaining
michael@0 67
michael@0 68 # Return the wait time in seconds, clamped to zero.
michael@0 69 return max(total_seconds(wait), 0)
michael@0 70
michael@0 71 def flush_input(fd, frags):
michael@0 72 """
michael@0 73 Read any pages sitting in the file descriptor 'fd' into the list 'frags'.
michael@0 74 """
michael@0 75 rv = os.read(fd, 4096)
michael@0 76 frags.append(rv)
michael@0 77 while len(rv) == 4096:
michael@0 78 # If read() returns a full buffer, it may indicate there was 1 buffer
michael@0 79 # worth of data, or that there is more data to read. Poll the socket
michael@0 80 # before we read again to ensure that we will not block indefinitly.
michael@0 81 readable, _, _ = select.select([fd], [], [], 0)
michael@0 82 if not readable:
michael@0 83 return
michael@0 84
michael@0 85 rv = os.read(fd, 4096)
michael@0 86 frags.append(rv)
michael@0 87
michael@0 88 def read_input(tasks, timeout):
michael@0 89 """
michael@0 90 Select on input or errors from the given task list for a max of timeout
michael@0 91 seconds.
michael@0 92 """
michael@0 93 rlist = []
michael@0 94 exlist = []
michael@0 95 outmap = {} # Fast access to fragment list given fd.
michael@0 96 for t in tasks:
michael@0 97 rlist.append(t.stdout)
michael@0 98 rlist.append(t.stderr)
michael@0 99 outmap[t.stdout] = t.out
michael@0 100 outmap[t.stderr] = t.err
michael@0 101 # This will trigger with a close event when the child dies, allowing
michael@0 102 # us to respond immediately and not leave cores idle.
michael@0 103 exlist.append(t.stdout)
michael@0 104
michael@0 105 readable, _, _ = select.select(rlist, [], exlist, timeout)
michael@0 106 for fd in readable:
michael@0 107 flush_input(fd, outmap[fd])
michael@0 108
michael@0 109 def remove_task(tasks, pid):
michael@0 110 """
michael@0 111 Return a pair with the removed task and the new, modified tasks list.
michael@0 112 """
michael@0 113 index = None
michael@0 114 for i, t in enumerate(tasks):
michael@0 115 if t.pid == pid:
michael@0 116 index = i
michael@0 117 break
michael@0 118 else:
michael@0 119 raise KeyError("No such pid: %s" % pid)
michael@0 120
michael@0 121 out = tasks[index]
michael@0 122 tasks.pop(index)
michael@0 123 return out
michael@0 124
michael@0 125 def timed_out(task, timeout):
michael@0 126 """
michael@0 127 Return True if the given task has been running for longer than |timeout|.
michael@0 128 |timeout| may be falsy, indicating an infinite timeout (in which case
michael@0 129 timed_out always returns False).
michael@0 130 """
michael@0 131 if timeout:
michael@0 132 now = datetime.now()
michael@0 133 return (now - task.start) > timedelta(seconds=timeout)
michael@0 134 return False
michael@0 135
michael@0 136 def reap_zombies(tasks, results, timeout):
michael@0 137 """
michael@0 138 Search for children of this process that have finished. If they are tasks,
michael@0 139 then this routine will clean up the child and send a TestOutput to the
michael@0 140 results channel. This method returns a new task list that has had the ended
michael@0 141 tasks removed.
michael@0 142 """
michael@0 143 while True:
michael@0 144 try:
michael@0 145 pid, status = os.waitpid(0, os.WNOHANG)
michael@0 146 if pid == 0:
michael@0 147 break
michael@0 148 except OSError, e:
michael@0 149 if e.errno == errno.ECHILD:
michael@0 150 break
michael@0 151 raise e
michael@0 152
michael@0 153 ended = remove_task(tasks, pid)
michael@0 154 flush_input(ended.stdout, ended.out)
michael@0 155 flush_input(ended.stderr, ended.err)
michael@0 156 os.close(ended.stdout)
michael@0 157 os.close(ended.stderr)
michael@0 158
michael@0 159 returncode = os.WEXITSTATUS(status)
michael@0 160 if os.WIFSIGNALED(status):
michael@0 161 returncode = -os.WTERMSIG(status)
michael@0 162
michael@0 163 out = TestOutput(
michael@0 164 ended.test,
michael@0 165 ended.cmd,
michael@0 166 ''.join(ended.out),
michael@0 167 ''.join(ended.err),
michael@0 168 returncode,
michael@0 169 total_seconds(datetime.now() - ended.start),
michael@0 170 timed_out(ended, timeout))
michael@0 171 results.push(out)
michael@0 172 return tasks
michael@0 173
michael@0 174 def kill_undead(tasks, results, timeout):
michael@0 175 """
michael@0 176 Signal all children that are over the given timeout.
michael@0 177 """
michael@0 178 for task in tasks:
michael@0 179 if timed_out(task, timeout):
michael@0 180 os.kill(task.pid, 9)
michael@0 181
michael@0 182 def run_all_tests(tests, results, options):
michael@0 183 # Copy and reverse for fast pop off end.
michael@0 184 tests = tests[:]
michael@0 185 tests.reverse()
michael@0 186
michael@0 187 # The set of currently running tests.
michael@0 188 tasks = []
michael@0 189
michael@0 190 while len(tests) or len(tasks):
michael@0 191 while len(tests) and len(tasks) < options.worker_count:
michael@0 192 tasks.append(spawn_test(tests.pop(), options.passthrough))
michael@0 193
michael@0 194 timeout = get_max_wait(tasks, results, options.timeout)
michael@0 195 read_input(tasks, timeout)
michael@0 196
michael@0 197 kill_undead(tasks, results, options.timeout)
michael@0 198 tasks = reap_zombies(tasks, results, options.timeout)
michael@0 199
michael@0 200 results.pb.poke()
michael@0 201
michael@0 202 return True

mercurial