PageRenderTime 95ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/external/webkit/Tools/Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker.py

https://gitlab.com/brian0218/rk3066_r-box_android4.2.2_sdk
Python | 349 lines | 181 code | 71 blank | 97 comment | 9 complexity | 6968bdb96b6e64e8e590db3510eb4a88 MD5 | raw file
  1. # Copyright (C) 2011 Google Inc. All rights reserved.
  2. #
  3. # Redistribution and use in source and binary forms, with or without
  4. # modification, are permitted provided that the following conditions are
  5. # met:
  6. #
  7. # * Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. # * Redistributions in binary form must reproduce the above
  10. # copyright notice, this list of conditions and the following disclaimer
  11. # in the documentation and/or other materials provided with the
  12. # distribution.
  13. # * Neither the name of Google Inc. nor the names of its
  14. # contributors may be used to endorse or promote products derived from
  15. # this software without specific prior written permission.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  18. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  19. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  20. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  21. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  22. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  23. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  24. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  25. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  27. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. """Module for handling messages and concurrency for run-webkit-tests.
  29. This module implements a message broker that connects the manager
  30. (TestRunner2) to the workers: it provides a messaging abstraction and
  31. message loops (building on top of message_broker2), and handles starting
  32. workers by launching threads and/or processes depending on the
  33. requested configuration.
  34. There are a lot of classes and objects involved in a fully connected system.
  35. They interact more or less like:
  36. TestRunner2 --> _InlineManager ---> _InlineWorker <-> Worker
  37. ^ \ / ^
  38. | v v |
  39. \-------------------- MessageBroker -------------/
  40. """
  41. import logging
  42. import optparse
  43. import printing
  44. import Queue
  45. import sys
  46. import thread
  47. import threading
  48. import time
  49. # Handle Python < 2.6 where multiprocessing isn't available.
  50. try:
  51. import multiprocessing
  52. except ImportError:
  53. multiprocessing = None
  54. from webkitpy.common.system import stack_utils
  55. from webkitpy.layout_tests import port
  56. from webkitpy.layout_tests.layout_package import message_broker2
  57. _log = logging.getLogger(__name__)
  58. #
  59. # Topic names for Manager <-> Worker messaging
  60. #
  61. MANAGER_TOPIC = 'managers'
  62. ANY_WORKER_TOPIC = 'workers'
  63. def runtime_options():
  64. """Return a list of optparse.Option objects for any runtime values used
  65. by this module."""
  66. options = [
  67. optparse.make_option("--worker-model", action="store",
  68. help=("controls worker model. Valid values are "
  69. "'inline', 'threads', and 'processes'.")),
  70. ]
  71. return options
  72. def get(port, options, client, worker_class):
  73. """Return a connection to a manager/worker message_broker
  74. Args:
  75. port - handle to layout_tests/port object for port-specific stuff
  76. options - optparse argument for command-line options
  77. client - message_broker2.BrokerClient implementation to dispatch
  78. replies to.
  79. worker_class - type of workers to create. This class must implement
  80. the methods in AbstractWorker.
  81. Returns:
  82. A handle to an object that will talk to a message broker configured
  83. for the normal manager/worker communication.
  84. """
  85. worker_model = options.worker_model
  86. if worker_model == 'inline':
  87. queue_class = Queue.Queue
  88. manager_class = _InlineManager
  89. elif worker_model == 'threads':
  90. queue_class = Queue.Queue
  91. manager_class = _ThreadedManager
  92. elif worker_model == 'processes' and multiprocessing:
  93. queue_class = multiprocessing.Queue
  94. manager_class = _MultiProcessManager
  95. else:
  96. raise ValueError("unsupported value for --worker-model: %s" %
  97. worker_model)
  98. broker = message_broker2.Broker(options, queue_class)
  99. return manager_class(broker, port, options, client, worker_class)
  100. class AbstractWorker(message_broker2.BrokerClient):
  101. def __init__(self, broker_connection, worker_number, options):
  102. """The constructor should be used to do any simple initialization
  103. necessary, but should not do anything that creates data structures
  104. that cannot be Pickled or sent across processes (like opening
  105. files or sockets). Complex initialization should be done at the
  106. start of the run() call.
  107. Args:
  108. broker_connection - handle to the BrokerConnection object creating
  109. the worker and that can be used for messaging.
  110. worker_number - identifier for this particular worker
  111. options - command-line argument object from optparse"""
  112. raise NotImplementedError
  113. def run(self, port):
  114. """Callback for the worker to start executing. Typically does any
  115. remaining initialization and then calls broker_connection.run_message_loop()."""
  116. raise NotImplementedError
  117. def cancel(self):
  118. """Called when possible to indicate to the worker to stop processing
  119. messages and shut down. Note that workers may be stopped without this
  120. method being called, so clients should not rely solely on this."""
  121. raise NotImplementedError
  122. class _ManagerConnection(message_broker2.BrokerConnection):
  123. def __init__(self, broker, options, client, worker_class):
  124. """Base initialization for all Manager objects.
  125. Args:
  126. broker: handle to the message_broker2 object
  127. options: command line options object
  128. client: callback object (the caller)
  129. worker_class: class object to use to create workers.
  130. """
  131. message_broker2.BrokerConnection.__init__(self, broker, client,
  132. MANAGER_TOPIC, ANY_WORKER_TOPIC)
  133. self._options = options
  134. self._worker_class = worker_class
  135. def start_worker(self, worker_number):
  136. raise NotImplementedError
  137. class _InlineManager(_ManagerConnection):
  138. def __init__(self, broker, port, options, client, worker_class):
  139. _ManagerConnection.__init__(self, broker, options, client, worker_class)
  140. self._port = port
  141. self._inline_worker = None
  142. def start_worker(self, worker_number):
  143. self._inline_worker = _InlineWorkerConnection(self._broker, self._port,
  144. self._client, self._worker_class, worker_number)
  145. return self._inline_worker
  146. def run_message_loop(self, delay_secs=None):
  147. # Note that delay_secs is ignored in this case since we can't easily
  148. # implement it.
  149. self._inline_worker.run()
  150. self._broker.run_all_pending(MANAGER_TOPIC, self._client)
  151. class _ThreadedManager(_ManagerConnection):
  152. def __init__(self, broker, port, options, client, worker_class):
  153. _ManagerConnection.__init__(self, broker, options, client, worker_class)
  154. self._port = port
  155. def start_worker(self, worker_number):
  156. worker_connection = _ThreadedWorkerConnection(self._broker, self._port,
  157. self._worker_class, worker_number)
  158. worker_connection.start()
  159. return worker_connection
  160. class _MultiProcessManager(_ManagerConnection):
  161. def __init__(self, broker, port, options, client, worker_class):
  162. # Note that this class does not keep a handle to the actual port
  163. # object, because it isn't Picklable. Instead it keeps the port
  164. # name and recreates the port in the child process from the name
  165. # and options.
  166. _ManagerConnection.__init__(self, broker, options, client, worker_class)
  167. self._platform_name = port.real_name()
  168. def start_worker(self, worker_number):
  169. worker_connection = _MultiProcessWorkerConnection(self._broker, self._platform_name,
  170. self._worker_class, worker_number, self._options)
  171. worker_connection.start()
  172. return worker_connection
  173. class _WorkerConnection(message_broker2.BrokerConnection):
  174. def __init__(self, broker, worker_class, worker_number, options):
  175. self._client = worker_class(self, worker_number, options)
  176. self.name = self._client.name()
  177. message_broker2.BrokerConnection.__init__(self, broker, self._client,
  178. ANY_WORKER_TOPIC, MANAGER_TOPIC)
  179. def cancel(self):
  180. raise NotImplementedError
  181. def is_alive(self):
  182. raise NotImplementedError
  183. def join(self, timeout):
  184. raise NotImplementedError
  185. def log_wedged_worker(self, test_name):
  186. raise NotImplementedError
  187. def yield_to_broker(self):
  188. pass
  189. class _InlineWorkerConnection(_WorkerConnection):
  190. def __init__(self, broker, port, manager_client, worker_class, worker_number):
  191. _WorkerConnection.__init__(self, broker, worker_class, worker_number, port._options)
  192. self._alive = False
  193. self._port = port
  194. self._manager_client = manager_client
  195. def cancel(self):
  196. self._client.cancel()
  197. def is_alive(self):
  198. return self._alive
  199. def join(self, timeout):
  200. assert not self._alive
  201. def log_wedged_worker(self, test_name):
  202. assert False, "_InlineWorkerConnection.log_wedged_worker() called"
  203. def run(self):
  204. self._alive = True
  205. self._client.run(self._port)
  206. self._alive = False
  207. def yield_to_broker(self):
  208. self._broker.run_all_pending(MANAGER_TOPIC, self._manager_client)
  209. class _Thread(threading.Thread):
  210. def __init__(self, worker_connection, port, client):
  211. threading.Thread.__init__(self)
  212. self._worker_connection = worker_connection
  213. self._port = port
  214. self._client = client
  215. def cancel(self):
  216. return self._client.cancel()
  217. def log_wedged_worker(self, test_name):
  218. stack_utils.log_thread_state(_log.error, self._client.name(), self.ident, " is wedged on test %s" % test_name)
  219. def run(self):
  220. # FIXME: We can remove this once everyone is on 2.6.
  221. if not hasattr(self, 'ident'):
  222. self.ident = thread.get_ident()
  223. self._client.run(self._port)
  224. class _ThreadedWorkerConnection(_WorkerConnection):
  225. def __init__(self, broker, port, worker_class, worker_number):
  226. _WorkerConnection.__init__(self, broker, worker_class, worker_number, port._options)
  227. self._thread = _Thread(self, port, self._client)
  228. def cancel(self):
  229. return self._thread.cancel()
  230. def is_alive(self):
  231. # FIXME: Change this to is_alive once everyone is on 2.6.
  232. return self._thread.isAlive()
  233. def join(self, timeout):
  234. return self._thread.join(timeout)
  235. def log_wedged_worker(self, test_name):
  236. return self._thread.log_wedged_worker(test_name)
  237. def start(self):
  238. self._thread.start()
  239. if multiprocessing:
  240. class _Process(multiprocessing.Process):
  241. def __init__(self, worker_connection, platform_name, options, client):
  242. multiprocessing.Process.__init__(self)
  243. self._worker_connection = worker_connection
  244. self._platform_name = platform_name
  245. self._options = options
  246. self._client = client
  247. def log_wedged_worker(self, test_name):
  248. _log.error("%s (pid %d) is wedged on test %s" % (self.name, self.pid, test_name))
  249. def run(self):
  250. options = self._options
  251. port_obj = port.get(self._platform_name, options)
  252. # FIXME: this won't work if the calling process is logging
  253. # somewhere other than sys.stderr and sys.stdout, but I'm not sure
  254. # if this will be an issue in practice.
  255. printer = printing.Printer(port_obj, options, sys.stderr, sys.stdout,
  256. int(options.child_processes), options.experimental_fully_parallel)
  257. self._client.run(port_obj)
  258. printer.cleanup()
  259. class _MultiProcessWorkerConnection(_WorkerConnection):
  260. def __init__(self, broker, platform_name, worker_class, worker_number, options):
  261. _WorkerConnection.__init__(self, broker, worker_class, worker_number, options)
  262. self._proc = _Process(self, platform_name, options, self._client)
  263. def cancel(self):
  264. return self._proc.terminate()
  265. def is_alive(self):
  266. return self._proc.is_alive()
  267. def join(self, timeout):
  268. return self._proc.join(timeout)
  269. def log_wedged_worker(self, test_name):
  270. return self._proc.log_wedged_worker(test_name)
  271. def start(self):
  272. self._proc.start()