summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Job.py
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2001-09-17 04:57:00 (GMT)
committerSteven Knight <knight@baldmt.com>2001-09-17 04:57:00 (GMT)
commit3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab (patch)
tree1a08c189644909cdadc489cce0eaa487e2c6f578 /src/engine/SCons/Job.py
parente2faf9c21bc7712fcdc547b7df0f12a6b2177601 (diff)
downloadSCons-3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab.zip
SCons-3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab.tar.gz
SCons-3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab.tar.bz2
Run setup.py on the unpacked .tar.gz for testing.
Diffstat (limited to 'src/engine/SCons/Job.py')
-rw-r--r--src/engine/SCons/Job.py239
1 files changed, 239 insertions, 0 deletions
diff --git a/src/engine/SCons/Job.py b/src/engine/SCons/Job.py
new file mode 100644
index 0000000..aa9d92a
--- /dev/null
+++ b/src/engine/SCons/Job.py
@@ -0,0 +1,239 @@
+"""SCons.Job
+
+This module defines the Serial and Parallel classes that execute tasks to
+complete a build. The Jobs class provides a higher level interface to start,
+stop, and wait on jobs.
+
+"""
+
+__revision__ = "Job.py __REVISION__ __DATE__ __DEVELOPER__"
+
+class Jobs:
+ """An instance of this class initializes N jobs, and provides
+ methods for starting, stopping, and waiting on all N jobs.
+ """
+
+ def __init__(self, num, taskmaster):
+ """
+ create 'num' jobs using the given taskmaster.
+
+ If 'num' is equal to 0, then a serial job will be used,
+ otherwise 'num' parallel jobs will be used.
+ """
+
+ if num > 1:
+ self.jobs = []
+ for i in range(num):
+ self.jobs.append(Parallel(taskmaster, self))
+ else:
+ self.jobs = [Serial(taskmaster)]
+
+ def start(self):
+ """start the jobs"""
+
+ for job in self.jobs:
+ job.start()
+
+ def wait(self):
+ """ wait for the jobs started with start() to finish"""
+
+ for job in self.jobs:
+ job.wait()
+
+ def stop(self):
+ """
+ stop the jobs started with start()
+
+ This function does not wait for the jobs to finish.
+ """
+
+ for job in self.jobs:
+ job.stop()
+
+class Serial:
+ """This class is used to execute tasks in series, and is more efficient
+ than Parallel, but is only appropriate for non-parallel builds. Only
+ one instance of this class should be in existence at a time.
+
+ This class is not thread safe.
+ """
+
+ def __init__(self, taskmaster):
+ """Create a new serial job given a taskmaster.
+
+ The taskmaster's next_task() method should return the next task
+ that needs to be executed, or None if there are no more tasks. The
+ taskmaster's executed() method will be called for each task when it
+ is successfully executed or failed() will be called if it failed to
+ execute (e.g. execute() raised an exception). The taskmaster's
+ is_blocked() method will not be called. """
+
+ self.taskmaster = taskmaster
+
+ def start(self):
+
+ """Start the job. This will begin pulling tasks from the taskmaster
+ and executing them, and return when there are no more tasks. If a task
+ fails to execute (i.e. execute() raises an exception), then the job will
+ stop."""
+
+ while 1:
+ task = self.taskmaster.next_task()
+
+ if task is None:
+ break
+
+ try:
+ task.execute()
+ except:
+ self.taskmaster.failed(task)
+ return
+ else:
+ self.taskmaster.executed(task)
+
+ def stop(self):
+ """Serial jobs are always finished when start() returns, so there
+ is nothing to do here"""
+
+ pass
+
+ def wait(self):
+ """Serial jobs are always finished when start() returns, so there
+ is nothing to do here"""
+ pass
+
+
+# The will hold a condition variable once the first parallel task
+# is created.
+cv = None
+
+class Parallel:
+ """This class is used to execute tasks in parallel, and is less
+ efficient than Serial, but is appropriate for parallel builds. Create
+ an instance of this class for each job or thread you want.
+
+ This class is thread safe.
+ """
+
+
+ def __init__(self, taskmaster, jobs):
+ """Create a new parallel job given a taskmaster, and a Jobs instance.
+ Multiple jobs will be using the taskmaster in parallel, but all
+ method calls to taskmaster methods are serialized by the jobs
+ themselves.
+
+ The taskmaster's next_task() method should return the next task
+ that needs to be executed, or None if there are no more tasks. The
+ taskmaster's executed() method will be called for each task when it
+ is successfully executed or failed() will be called if the task
+ failed to execute (i.e. execute() raised an exception). The
+ taskmaster's is_blocked() method should return true iff there are
+ more tasks, but they can't be executed until one or more other
+ tasks have been executed. next_task() will be called iff
+ is_blocked() returned false.
+
+ Note: calls to taskmaster are serialized, but calls to execute() on
+ distinct tasks are not serialized, because that is the whole point
+ of parallel jobs: they can execute multiple tasks
+ simultaneously. """
+
+ global cv
+
+ # import threading here so that everything in the Job module
+ # but the Parallel class will work if the interpreter doesn't
+ # support threads
+ import threading
+
+ self.taskmaster = taskmaster
+ self.jobs = jobs
+ self.thread = threading.Thread(None, self.__run)
+ self.stop_running = 0
+
+ if cv is None:
+ cv = threading.Condition()
+
+ def start(self):
+ """Start the job. This will spawn a thread that will begin pulling
+ tasks from the task master and executing them. This method returns
+ immediately and doesn't wait for the jobs to be executed.
+
+ If a task fails to execute (i.e. execute() raises an exception),
+ all jobs will be stopped.
+
+ To stop the job, call stop().
+ To wait for the job to finish, call wait().
+ """
+ self.thread.start()
+
+ def stop(self):
+ """Stop the job. This will cause the job to finish after the
+ currently executing task is done. A job that has been stopped can
+ not be restarted.
+
+ To wait for the job to finish, call wait().
+ """
+
+ cv.acquire()
+ self.stop_running = 1
+ # wake up the sleeping jobs so this job will end as soon as possible:
+ cv.notifyAll()
+ cv.release()
+
+ def wait(self):
+ """Wait for the job to finish. A job is finished when either there
+ are no more tasks or the job has been stopped and it is no longer
+ executing a task.
+
+ This method should only be called after start() has been called.
+
+ To stop the job, call stop().
+ """
+ self.thread.join()
+
+ def __run(self):
+ """private method that actually executes the tasks"""
+
+ cv.acquire()
+
+ try:
+
+ while 1:
+ while self.taskmaster.is_blocked() and not self.stop_running:
+ cv.wait(None)
+
+ # check this before calling next_task(), because
+ # this job may have been stopped because of a build
+ # failure:
+ if self.stop_running:
+ break
+
+ task = self.taskmaster.next_task()
+
+ if task == None:
+ break
+
+ cv.release()
+ try:
+ try:
+ task.execute()
+ finally:
+ cv.acquire()
+ except:
+ self.taskmaster.failed(task)
+ # stop all jobs since there was a failure:
+ # (this will wake up any waiting jobs, so
+ # it isn't necessary to explicitly wake them
+ # here)
+ self.jobs.stop()
+ else:
+ self.taskmaster.executed(task)
+
+ if not self.taskmaster.is_blocked():
+ cv.notifyAll()
+
+ finally:
+ cv.release()
+
+
+
+