jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

367 lines
8.5KB

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2010 (ita)
  4. """
  5. Runner.py: Task scheduling and execution
  6. """
  7. import random, atexit
  8. try:
  9. from queue import Queue
  10. except ImportError:
  11. from Queue import Queue
  12. from waflib import Utils, Task, Errors, Logs
  13. GAP = 10
  14. """
  15. Wait for free tasks if there are at least ``GAP * njobs`` in queue
  16. """
  17. class TaskConsumer(Utils.threading.Thread):
  18. """
  19. Task consumers belong to a pool of workers
  20. They wait for tasks in the queue and then use ``task.process(...)``
  21. """
  22. def __init__(self):
  23. Utils.threading.Thread.__init__(self)
  24. self.ready = Queue()
  25. """
  26. Obtain :py:class:`waflib.Task.TaskBase` instances from this queue.
  27. """
  28. self.setDaemon(1)
  29. self.start()
  30. def run(self):
  31. """
  32. Loop over the tasks to execute
  33. """
  34. try:
  35. self.loop()
  36. except Exception:
  37. pass
  38. def loop(self):
  39. """
  40. Obtain tasks from :py:attr:`waflib.Runner.TaskConsumer.ready` and call
  41. :py:meth:`waflib.Task.TaskBase.process`. If the object is a function, execute it.
  42. """
  43. while 1:
  44. tsk = self.ready.get()
  45. if not isinstance(tsk, Task.TaskBase):
  46. tsk(self)
  47. else:
  48. tsk.process()
  49. pool = Queue()
  50. """
  51. Pool of task consumer objects
  52. """
  53. def get_pool():
  54. """
  55. Obtain a task consumer from :py:attr:`waflib.Runner.pool`.
  56. Do not forget to put it back by using :py:func:`waflib.Runner.put_pool`
  57. and reset properly (original waiting queue).
  58. :rtype: :py:class:`waflib.Runner.TaskConsumer`
  59. """
  60. try:
  61. return pool.get(False)
  62. except Exception:
  63. return TaskConsumer()
  64. def put_pool(x):
  65. """
  66. Return a task consumer to the thread pool :py:attr:`waflib.Runner.pool`
  67. :param x: task consumer object
  68. :type x: :py:class:`waflib.Runner.TaskConsumer`
  69. """
  70. pool.put(x)
  71. def _free_resources():
  72. global pool
  73. lst = []
  74. while pool.qsize():
  75. lst.append(pool.get())
  76. for x in lst:
  77. x.ready.put(None)
  78. for x in lst:
  79. x.join()
  80. pool = None
  81. atexit.register(_free_resources)
  82. class Parallel(object):
  83. """
  84. Schedule the tasks obtained from the build context for execution.
  85. """
  86. def __init__(self, bld, j=2):
  87. """
  88. The initialization requires a build context reference
  89. for computing the total number of jobs.
  90. """
  91. self.numjobs = j
  92. """
  93. Number of consumers in the pool
  94. """
  95. self.bld = bld
  96. """
  97. Instance of :py:class:`waflib.Build.BuildContext`
  98. """
  99. self.outstanding = []
  100. """List of :py:class:`waflib.Task.TaskBase` that may be ready to be executed"""
  101. self.frozen = []
  102. """List of :py:class:`waflib.Task.TaskBase` that cannot be executed immediately"""
  103. self.out = Queue(0)
  104. """List of :py:class:`waflib.Task.TaskBase` returned by the task consumers"""
  105. self.count = 0
  106. """Amount of tasks that may be processed by :py:class:`waflib.Runner.TaskConsumer`"""
  107. self.processed = 1
  108. """Amount of tasks processed"""
  109. self.stop = False
  110. """Error flag to stop the build"""
  111. self.error = []
  112. """Tasks that could not be executed"""
  113. self.biter = None
  114. """Task iterator which must give groups of parallelizable tasks when calling ``next()``"""
  115. self.dirty = False
  116. """Flag to indicate that tasks have been executed, and that the build cache must be saved (call :py:meth:`waflib.Build.BuildContext.store`)"""
  117. def get_next_task(self):
  118. """
  119. Obtain the next task to execute.
  120. :rtype: :py:class:`waflib.Task.TaskBase`
  121. """
  122. if not self.outstanding:
  123. return None
  124. return self.outstanding.pop(0)
  125. def postpone(self, tsk):
  126. """
  127. A task cannot be executed at this point, put it in the list :py:attr:`waflib.Runner.Parallel.frozen`.
  128. :param tsk: task
  129. :type tsk: :py:class:`waflib.Task.TaskBase`
  130. """
  131. if random.randint(0, 1):
  132. self.frozen.insert(0, tsk)
  133. else:
  134. self.frozen.append(tsk)
  135. def refill_task_list(self):
  136. """
  137. Put the next group of tasks to execute in :py:attr:`waflib.Runner.Parallel.outstanding`.
  138. """
  139. while self.count > self.numjobs * GAP:
  140. self.get_out()
  141. while not self.outstanding:
  142. if self.count:
  143. self.get_out()
  144. elif self.frozen:
  145. try:
  146. cond = self.deadlock == self.processed
  147. except AttributeError:
  148. pass
  149. else:
  150. if cond:
  151. msg = 'check the build order for the tasks'
  152. for tsk in self.frozen:
  153. if not tsk.run_after:
  154. msg = 'check the methods runnable_status'
  155. break
  156. lst = []
  157. for tsk in self.frozen:
  158. lst.append('%s\t-> %r' % (repr(tsk), [id(x) for x in tsk.run_after]))
  159. raise Errors.WafError('Deadlock detected: %s%s' % (msg, ''.join(lst)))
  160. self.deadlock = self.processed
  161. if self.frozen:
  162. self.outstanding += self.frozen
  163. self.frozen = []
  164. elif not self.count:
  165. self.outstanding.extend(next(self.biter))
  166. self.total = self.bld.total()
  167. break
  168. def add_more_tasks(self, tsk):
  169. """
  170. Tasks may be added dynamically during the build by binding them to the task :py:attr:`waflib.Task.TaskBase.more_tasks`
  171. :param tsk: task
  172. :type tsk: :py:attr:`waflib.Task.TaskBase`
  173. """
  174. if getattr(tsk, 'more_tasks', None):
  175. self.outstanding += tsk.more_tasks
  176. self.total += len(tsk.more_tasks)
  177. def get_out(self):
  178. """
  179. Obtain one task returned from the task consumers, and update the task count. Add more tasks if necessary through
  180. :py:attr:`waflib.Runner.Parallel.add_more_tasks`.
  181. :rtype: :py:attr:`waflib.Task.TaskBase`
  182. """
  183. tsk = self.out.get()
  184. if not self.stop:
  185. self.add_more_tasks(tsk)
  186. self.count -= 1
  187. self.dirty = True
  188. return tsk
  189. def add_task(self, tsk):
  190. """
  191. Pass a task to a consumer.
  192. :param tsk: task
  193. :type tsk: :py:attr:`waflib.Task.TaskBase`
  194. """
  195. try:
  196. self.pool
  197. except AttributeError:
  198. self.init_task_pool()
  199. self.ready.put(tsk)
  200. def init_task_pool(self):
  201. # lazy creation, and set a common pool for all task consumers
  202. pool = self.pool = [get_pool() for i in range(self.numjobs)]
  203. self.ready = Queue(0)
  204. def setq(consumer):
  205. consumer.ready = self.ready
  206. for x in pool:
  207. x.ready.put(setq)
  208. return pool
  209. def free_task_pool(self):
  210. # return the consumers, setting a different queue for each of them
  211. def setq(consumer):
  212. consumer.ready = Queue(0)
  213. self.out.put(self)
  214. try:
  215. pool = self.pool
  216. except AttributeError:
  217. pass
  218. else:
  219. for x in pool:
  220. self.ready.put(setq)
  221. for x in pool:
  222. self.get_out()
  223. for x in pool:
  224. put_pool(x)
  225. self.pool = []
  226. def skip(self, tsk):
  227. tsk.hasrun = Task.SKIPPED
  228. def error_handler(self, tsk):
  229. """
  230. Called when a task cannot be executed. The flag :py:attr:`waflib.Runner.Parallel.stop` is set, unless
  231. the build is executed with::
  232. $ waf build -k
  233. :param tsk: task
  234. :type tsk: :py:attr:`waflib.Task.TaskBase`
  235. """
  236. if not self.bld.keep:
  237. self.stop = True
  238. self.error.append(tsk)
  239. def task_status(self, tsk):
  240. try:
  241. return tsk.runnable_status()
  242. except Exception:
  243. self.processed += 1
  244. tsk.err_msg = Utils.ex_stack()
  245. if not self.stop and self.bld.keep:
  246. self.skip(tsk)
  247. if self.bld.keep == 1:
  248. # if -k stop at the first exception, if -kk try to go as far as possible
  249. if Logs.verbose > 1 or not self.error:
  250. self.error.append(tsk)
  251. self.stop = True
  252. else:
  253. if Logs.verbose > 1:
  254. self.error.append(tsk)
  255. return Task.EXCEPTION
  256. tsk.hasrun = Task.EXCEPTION
  257. self.error_handler(tsk)
  258. return Task.EXCEPTION
  259. def start(self):
  260. """
  261. Give tasks to :py:class:`waflib.Runner.TaskConsumer` instances until the build finishes or the ``stop`` flag is set.
  262. If only one job is used, then execute the tasks one by one, without consumers.
  263. """
  264. self.total = self.bld.total()
  265. while not self.stop:
  266. self.refill_task_list()
  267. # consider the next task
  268. tsk = self.get_next_task()
  269. if not tsk:
  270. if self.count:
  271. # tasks may add new ones after they are run
  272. continue
  273. else:
  274. # no tasks to run, no tasks running, time to exit
  275. break
  276. if tsk.hasrun:
  277. # if the task is marked as "run", just skip it
  278. self.processed += 1
  279. continue
  280. if self.stop: # stop immediately after a failure was detected
  281. break
  282. st = self.task_status(tsk)
  283. if st == Task.RUN_ME:
  284. tsk.position = (self.processed, self.total)
  285. self.count += 1
  286. tsk.master = self
  287. self.processed += 1
  288. if self.numjobs == 1:
  289. tsk.process()
  290. else:
  291. self.add_task(tsk)
  292. if st == Task.ASK_LATER:
  293. self.postpone(tsk)
  294. elif st == Task.SKIP_ME:
  295. self.processed += 1
  296. self.skip(tsk)
  297. self.add_more_tasks(tsk)
  298. # self.count represents the tasks that have been made available to the consumer threads
  299. # collect all the tasks after an error else the message may be incomplete
  300. while self.error and self.count:
  301. self.get_out()
  302. #print loop
  303. assert (self.count == 0 or self.stop)
  304. # free the task pool, if any
  305. self.free_task_pool()