mirror of
https://gitlab.com/ita1024/waf.git
synced 2024-11-25 03:09:30 +01:00
Add a new task semaphore system
This commit is contained in:
parent
a10822f688
commit
3f4acd6ff3
44
playground/task_semaphore/wscript
Normal file
44
playground/task_semaphore/wscript
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Task semaphore demo. Compare the runtimes:
|
||||||
|
|
||||||
|
waf configure build --fast # 0m08
|
||||||
|
waf configure build # 1m15
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random, time
|
||||||
|
from waflib import Task, TaskGen, Utils
|
||||||
|
|
||||||
|
def options(opt):
|
||||||
|
opt.add_option('--fast', action='store_true', default=False, help='Disable the semaphore to compare the runtime', dest='fast')
|
||||||
|
|
||||||
|
def configure(conf):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def build(bld):
|
||||||
|
# max 20 jobs globally
|
||||||
|
bld.jobs = 20
|
||||||
|
|
||||||
|
bld(features='make_100')
|
||||||
|
|
||||||
|
class Foo(Task.Task):
|
||||||
|
always_run = True
|
||||||
|
|
||||||
|
if not bld.options.fast:
|
||||||
|
semaphore = Task.TaskSemaphore(2) # 2 jobs maximum
|
||||||
|
|
||||||
|
def uid(self):
|
||||||
|
# unique id for each object
|
||||||
|
return Utils.h_list(self.num)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
time.sleep(random.randint(1000, 2000) / 1000.)
|
||||||
|
print("Task %r" % self.num)
|
||||||
|
|
||||||
|
@TaskGen.feature('make_100')
|
||||||
|
def make_100_bound_tasks(self):
|
||||||
|
for x in range(100):
|
||||||
|
tsk = self.create_task('Foo')
|
||||||
|
tsk.num = x
|
||||||
|
|
@ -329,6 +329,14 @@ class Parallel(object):
|
|||||||
try_unfreeze(x)
|
try_unfreeze(x)
|
||||||
del self.revdeps[tsk]
|
del self.revdeps[tsk]
|
||||||
|
|
||||||
|
if hasattr(tsk, 'semaphore'):
|
||||||
|
sem = tsk.semaphore
|
||||||
|
sem.release(tsk)
|
||||||
|
while sem.waiting and not sem.is_locked():
|
||||||
|
# take a frozen task, make it ready to run
|
||||||
|
x = sem.waiting.pop()
|
||||||
|
self._add_task(x)
|
||||||
|
|
||||||
def get_out(self):
|
def get_out(self):
|
||||||
"""
|
"""
|
||||||
Waits for a Task that task consumers add to :py:attr:`waflib.Runner.Parallel.out` after execution.
|
Waits for a Task that task consumers add to :py:attr:`waflib.Runner.Parallel.out` after execution.
|
||||||
@ -352,8 +360,29 @@ class Parallel(object):
|
|||||||
:param tsk: task instance
|
:param tsk: task instance
|
||||||
:type tsk: :py:attr:`waflib.Task.Task`
|
:type tsk: :py:attr:`waflib.Task.Task`
|
||||||
"""
|
"""
|
||||||
|
# TODO change in waf 2.1
|
||||||
self.ready.put(tsk)
|
self.ready.put(tsk)
|
||||||
|
|
||||||
|
def _add_task(self, tsk):
|
||||||
|
if hasattr(tsk, 'semaphore'):
|
||||||
|
sem = tsk.semaphore
|
||||||
|
try:
|
||||||
|
sem.acquire(tsk)
|
||||||
|
except IndexError:
|
||||||
|
sem.waiting.add(tsk)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.count += 1
|
||||||
|
self.processed += 1
|
||||||
|
if self.numjobs == 1:
|
||||||
|
tsk.log_display(tsk.generator.bld)
|
||||||
|
try:
|
||||||
|
self.process_task(tsk)
|
||||||
|
finally:
|
||||||
|
self.out.put(tsk)
|
||||||
|
else:
|
||||||
|
self.add_task(tsk)
|
||||||
|
|
||||||
def process_task(self, tsk):
|
def process_task(self, tsk):
|
||||||
"""
|
"""
|
||||||
Processes a task and attempts to stop the build in case of errors
|
Processes a task and attempts to stop the build in case of errors
|
||||||
@ -453,17 +482,7 @@ class Parallel(object):
|
|||||||
|
|
||||||
st = self.task_status(tsk)
|
st = self.task_status(tsk)
|
||||||
if st == Task.RUN_ME:
|
if st == Task.RUN_ME:
|
||||||
self.count += 1
|
self._add_task(tsk)
|
||||||
self.processed += 1
|
|
||||||
|
|
||||||
if self.numjobs == 1:
|
|
||||||
tsk.log_display(tsk.generator.bld)
|
|
||||||
try:
|
|
||||||
self.process_task(tsk)
|
|
||||||
finally:
|
|
||||||
self.out.put(tsk)
|
|
||||||
else:
|
|
||||||
self.add_task(tsk)
|
|
||||||
elif st == Task.ASK_LATER:
|
elif st == Task.ASK_LATER:
|
||||||
self.postpone(tsk)
|
self.postpone(tsk)
|
||||||
elif st == Task.SKIP_ME:
|
elif st == Task.SKIP_ME:
|
||||||
|
@ -133,10 +133,12 @@ evil = store_task_type('evil', (object,), {})
|
|||||||
|
|
||||||
class Task(evil):
|
class Task(evil):
|
||||||
"""
|
"""
|
||||||
This class deals with the filesystem (:py:class:`waflib.Node.Node`). The method :py:class:`waflib.Task.Task.runnable_status`
|
Task objects represents actions to perform such as commands to execute by calling the `run` method.
|
||||||
uses a hash value (from :py:class:`waflib.Task.Task.signature`) which is persistent from build to build. When the value changes,
|
|
||||||
the task has to be executed. The method :py:class:`waflib.Task.Task.post_run` will assign the task signature to the output
|
Detecting when to execute a task occurs in the method :py:meth:`waflib.Task.Task.runnable_status`.
|
||||||
nodes (if present).
|
|
||||||
|
Detecting which tasks to execute is performed through a hash value returned by
|
||||||
|
:py:meth:`waflib.Task.Task.signature`. The task signature is persistent from build to build.
|
||||||
"""
|
"""
|
||||||
vars = []
|
vars = []
|
||||||
"""ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
|
"""ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
|
||||||
@ -1339,3 +1341,54 @@ def deep_inputs(cls):
|
|||||||
TaskBase = Task
|
TaskBase = Task
|
||||||
"Provided for compatibility reasons, TaskBase should not be used"
|
"Provided for compatibility reasons, TaskBase should not be used"
|
||||||
|
|
||||||
|
class TaskSemaphore(object):
|
||||||
|
"""
|
||||||
|
Task semaphores provide a simple and efficient way of throttling the amount of
|
||||||
|
a particular task to run concurrently. The throttling value is capped
|
||||||
|
by the amount of maximum jobs, so for example, a `TaskSemaphore(10)`
|
||||||
|
has no effect in a `-j2` build.
|
||||||
|
|
||||||
|
Task semaphores are typically specified on the task class level::
|
||||||
|
|
||||||
|
class compile(waflib.Task.Task):
|
||||||
|
semaphore = waflib.Task.TaskSemaphore(2)
|
||||||
|
run_str = 'touch ${TGT}'
|
||||||
|
|
||||||
|
Task semaphores are meant to be used by the build scheduler in the main
|
||||||
|
thread, so there are no guarantees of thread safety.
|
||||||
|
"""
|
||||||
|
def __init__(self, num):
|
||||||
|
"""
|
||||||
|
:param num: maximum value of concurrent tasks
|
||||||
|
:type num: int
|
||||||
|
"""
|
||||||
|
self.num = num
|
||||||
|
self.locking = set()
|
||||||
|
self.waiting = set()
|
||||||
|
|
||||||
|
def is_locked(self):
|
||||||
|
"""Returns True if this semaphore cannot be acquired by more tasks"""
|
||||||
|
return len(self.locking) >= self.num
|
||||||
|
|
||||||
|
def acquire(self, tsk):
|
||||||
|
"""
|
||||||
|
Mark the semaphore as used by the given task (not re-entrant).
|
||||||
|
|
||||||
|
:param tsk: task object
|
||||||
|
:type tsk: :py:class:`waflib.Task.Task`
|
||||||
|
:raises: :py:class:`IndexError` in case the resource is already acquired
|
||||||
|
"""
|
||||||
|
if self.is_locked():
|
||||||
|
raise IndexError('Cannot lock more %r' % self.locking)
|
||||||
|
self.locking.add(tsk)
|
||||||
|
|
||||||
|
def release(self, tsk):
|
||||||
|
"""
|
||||||
|
Mark the semaphore as unused by the given task.
|
||||||
|
|
||||||
|
:param tsk: task object
|
||||||
|
:type tsk: :py:class:`waflib.Task.Task`
|
||||||
|
:raises: :py:class:`KeyError` in case the resource is not acquired by the task
|
||||||
|
"""
|
||||||
|
self.locking.remove(tsk)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user