3 This module defines the Serial and Parallel classes that execute tasks to
4 complete a build. The Jobs class provides a higher level interface to start,
5 stop, and wait on jobs.
10 # Copyright (c) 2001, 2002, 2003 Steven Knight
12 # Permission is hereby granted, free of charge, to any person obtaining
13 # a copy of this software and associated documentation files (the
14 # "Software"), to deal in the Software without restriction, including
15 # without limitation the rights to use, copy, modify, merge, publish,
16 # distribute, sublicense, and/or sell copies of the Software, and to
17 # permit persons to whom the Software is furnished to do so, subject to
18 # the following conditions:
20 # The above copyright notice and this permission notice shall be included
21 # in all copies or substantial portions of the Software.
23 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
24 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
25 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
37 """An instance of this class initializes N jobs, and provides
38 methods for starting, stopping, and waiting on all N jobs.
41 def __init__(self, num, taskmaster):
43 create 'num' jobs using the given taskmaster.
45 If 'num' is 1 or less, then a serial job will be used,
46 otherwise 'num' parallel jobs will be used.
49 # Keeps track of keyboard interrupts:
50 self.keyboard_interrupt = 0
55 self.jobs.append(Parallel(taskmaster, self))
57 self.jobs = [Serial(taskmaster, self)]
62 """run the jobs, and wait for them to finish"""
67 self.running.append(job)
69 self.running[-1].wait()
71 except KeyboardInterrupt:
72 # mask any further keyboard interrupts so that scons
73 # can shutdown cleanly:
74 # (this only masks the keyboard interrupt for Python,
75 # child processes can still get the keyboard interrupt)
77 signal.signal(signal.SIGINT, signal.SIG_IGN)
79 for job in self.running:
80 job.keyboard_interrupt()
82 self.keyboard_interrupt = 1
84 # wait on any remaining jobs to finish:
85 for job in self.running:
88 if self.keyboard_interrupt:
89 raise KeyboardInterrupt
92 """This class is used to execute tasks in series, and is more efficient
93 than Parallel, but is only appropriate for non-parallel builds. Only
94 one instance of this class should be in existence at a time.
96 This class is not thread safe.
99 def __init__(self, taskmaster, jobs):
100 """Create a new serial job given a taskmaster.
102 The taskmaster's next_task() method should return the next task
103 that needs to be executed, or None if there are no more tasks. The
104 taskmaster's executed() method will be called for each task when it
105 is successfully executed or failed() will be called if it failed to
106 execute (e.g. execute() raised an exception). The taskmaster's
107 is_blocked() method will not be called. """
109 self.taskmaster = taskmaster
114 """Start the job. This will begin pulling tasks from the taskmaster
115 and executing them, and return when there are no more tasks. If a task
116 fails to execute (i.e. execute() raises an exception), then the job will
119 while not self.jobs.keyboard_interrupt:
120 task = self.taskmaster.next_task()
128 except KeyboardInterrupt:
131 # Let the failed() callback function arrange for the
132 # build to stop if that's appropriate.
138 """Serial jobs are always finished when start() returns, so there
139 is nothing to do here"""
142 def keyboard_interrupt(self):
143 self.jobs.keyboard_interrupt = 1
146 # The will hold a condition variable once the first parallel task
151 """This class is used to execute tasks in parallel, and is less
152 efficient than Serial, but is appropriate for parallel builds. Create
153 an instance of this class for each job or thread you want.
155 This class is thread safe.
159 def __init__(self, taskmaster, jobs):
160 """Create a new parallel job given a taskmaster, and a Jobs instance.
161 Multiple jobs will be using the taskmaster in parallel, but all
162 method calls to taskmaster methods are serialized by the jobs
165 The taskmaster's next_task() method should return the next task
166 that needs to be executed, or None if there are no more tasks. The
167 taskmaster's executed() method will be called for each task when it
168 is successfully executed or failed() will be called if the task
169 failed to execute (i.e. execute() raised an exception). The
170 taskmaster's is_blocked() method should return true iff there are
171 more tasks, but they can't be executed until one or more other
172 tasks have been executed. next_task() will be called iff
173 is_blocked() returned false.
175 Note: calls to taskmaster are serialized, but calls to execute() on
176 distinct tasks are not serialized, because that is the whole point
177 of parallel jobs: they can execute multiple tasks
182 # import threading here so that everything in the Job module
183 # but the Parallel class will work if the interpreter doesn't
187 self.taskmaster = taskmaster
189 self.thread = threading.Thread(None, self.__run)
192 cv = threading.Condition()
195 """Start the job. This will spawn a thread that will begin pulling
196 tasks from the task master and executing them. This method returns
197 immediately and doesn't wait for the jobs to be executed.
199 To wait for the job to finish, call wait().
204 """Wait for the job to finish. A job is finished when there
207 This method should only be called after start() has been called.
210 # Sleeping in a loop like this is lame. Calling
211 # self.thread.join() would be much nicer, but
212 # on Linux self.thread.join() doesn't always
213 # return when a KeyboardInterrupt happens, and when
214 # it does return, it causes Python to hang on shutdown.
215 # In other words this is just
216 # a work-around for some bugs/limitations in the
217 # self.thread.join() method.
218 while self.thread.isAlive():
221 def keyboard_interrupt(self):
223 self.jobs.keyboard_interrupt = 1
228 """private method that actually executes the tasks"""
235 while (self.taskmaster.is_blocked() and
236 not self.jobs.keyboard_interrupt):
239 if self.jobs.keyboard_interrupt:
242 task = self.taskmaster.next_task()
254 except KeyboardInterrupt:
255 self.jobs.keyboard_interrupt = 1
257 # Let the failed() callback function arrange for
258 # calling self.jobs.stop() to to stop the build
259 # if that's appropriate.
264 # signal the cv whether the task failed or not,
265 # or otherwise the other Jobs might
267 if (not self.taskmaster.is_blocked() or
268 self.jobs.keyboard_interrupt):