This repository has been archived by the owner on Mar 29, 2019. It is now read-only.
/
talos_process.py
120 lines (101 loc) · 4 KB
/
talos_process.py
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
113
114
115
116
117
118
119
120
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import time
import logging
import psutil
import mozcrash
from mozprocess import ProcessHandler
from threading import Event
from utils import TalosError
class ProcessContext(object):
"""
Store useful results of the browser execution.
"""
def __init__(self):
self.output = None
self.process = None
@property
def pid(self):
return self.process and self.process.pid
def kill_process(self):
if self.process and self.process.is_running():
logging.debug("Terminating %s", self.process)
self.process.terminate()
try:
self.process.wait(3)
except psutil.TimeoutExpired:
self.process.kill()
# will raise TimeoutExpired if unable to kill
self.process.wait(3)
class Reader(object):
def __init__(self, event):
self.output = []
self.got_end_timestamp = False
self.event = event
def __call__(self, line):
if line.find('__endTimestamp') != -1:
self.got_end_timestamp = True
self.event.set()
if not (line.startswith('JavaScript error:') or
line.startswith('JavaScript warning:')):
logging.debug('BROWSER_OUTPUT: %s', line)
self.output.append(line)
def run_browser(command, timeout=None, on_started=None, **kwargs):
"""
Run the browser using the given `command`.
After the browser prints __endTimestamp, we give it 5
seconds to quit and kill it if it's still alive at that point.
Note that this method ensure that the process is killed at
the end. If this is not possible, an exception will be raised.
:param command: the commad (as a string list) to run the browser
:param timeout: if specified, timeout to wait for the browser before
we raise a :class:`TalosError`
:param on_started: a callback that can be used to do things just after
the browser has been started
:param kwargs: additional keyword arguments for the :class:`ProcessHandler`
instance
Returns a ProcessContext instance, with available output and pid used.
"""
context = ProcessContext()
first_time = int(time.time()) * 1000
wait_for_quit_timeout = 5
event = Event()
reader = Reader(event)
kwargs['storeOutput'] = False
kwargs['processOutputLine'] = reader
kwargs['onFinish'] = event.set
proc = ProcessHandler(command, **kwargs)
proc.run()
try:
context.process = psutil.Process(proc.pid)
if on_started:
on_started()
# wait until we saw __endTimestamp in the proc output,
# or the browser just terminated - or we have a timeout
if not event.wait(timeout):
# try to extract the minidump stack if the browser hangs
mozcrash.kill_and_get_minidump(proc.pid)
raise TalosError("timeout")
if reader.got_end_timestamp:
for i in range(1, wait_for_quit_timeout):
if proc.wait(1) is not None:
break
if proc.poll() is None:
logging.info(
"Browser shutdown timed out after {0} seconds, terminating"
" process.".format(wait_for_quit_timeout)
)
finally:
# this also handle KeyboardInterrupt
# ensure early the process is really terminated
context.kill_process()
reader.output.append(
"__startBeforeLaunchTimestamp%d__endBeforeLaunchTimestamp"
% first_time)
reader.output.append(
"__startAfterTerminationTimestamp%d__endAfterTerminationTimestamp"
% (int(time.time()) * 1000))
logging.info("Browser exited with error code: {0}".format(proc.returncode))
context.output = reader.output
return context