Sat, 03 Jan 2015 20:18:00 +0100
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 |