diff options
Diffstat (limited to 'src/engine/SCons/Job.py')
-rw-r--r-- | src/engine/SCons/Job.py | 124 |
1 files changed, 67 insertions, 57 deletions
diff --git a/src/engine/SCons/Job.py b/src/engine/SCons/Job.py index 1eb5fa0..21f4617 100644 --- a/src/engine/SCons/Job.py +++ b/src/engine/SCons/Job.py @@ -31,6 +31,8 @@ stop, and wait on jobs. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import time + class Jobs: """An instance of this class initializes N jobs, and provides methods for starting, stopping, and waiting on all N jobs. @@ -44,34 +46,47 @@ class Jobs: otherwise 'num' parallel jobs will be used. """ + # Keeps track of keyboard interrupts: + self.keyboard_interrupt = 0 + 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""" + self.jobs = [Serial(taskmaster, self)] + + self.running = [] - for job in self.jobs: - job.start() + def run(self): + """run the jobs, and wait for them to finish""" - 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() + try: + for job in self.jobs: + job.start() + self.running.append(job) + while self.running: + self.running[-1].wait() + self.running.pop() + except KeyboardInterrupt: + # mask any further keyboard interrupts so that scons + # can shutdown cleanly: + # (this only masks the keyboard interrupt for Python, + # child processes can still get the keyboard interrupt) + import signal + signal.signal(signal.SIGINT, signal.SIG_IGN) + + for job in self.running: + job.keyboard_interrupt() + else: + self.keyboard_interrupt = 1 - This function does not wait for the jobs to finish. - """ + # wait on any remaining jobs to finish: + for job in self.running: + job.wait() - for job in self.jobs: - job.stop() + if self.keyboard_interrupt: + raise KeyboardInterrupt class Serial: """This class is used to execute tasks in series, and is more efficient @@ -81,7 +96,7 @@ class Serial: This class is not thread safe. """ - def __init__(self, taskmaster): + def __init__(self, taskmaster, jobs): """Create a new serial job given a taskmaster. The taskmaster's next_task() method should return the next task @@ -92,6 +107,7 @@ class Serial: is_blocked() method will not be called. """ self.taskmaster = taskmaster + self.jobs = jobs def start(self): @@ -100,7 +116,7 @@ class Serial: fails to execute (i.e. execute() raises an exception), then the job will stop.""" - while 1: + while not self.jobs.keyboard_interrupt: task = self.taskmaster.next_task() if task is None: @@ -108,6 +124,8 @@ class Serial: try: task.execute() + except KeyboardInterrupt: + raise except: # Let the failed() callback function arrange for the # build to stop if that's appropriate. @@ -115,16 +133,13 @@ class Serial: else: task.executed() - 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 + + def keyboard_interrupt(self): + self.jobs.keyboard_interrupt = 1 # The will hold a condition variable once the first parallel task @@ -171,7 +186,6 @@ class Parallel: self.taskmaster = taskmaster self.jobs = jobs self.thread = threading.Thread(None, self.__run) - self.stop_running = 0 if cv is None: cv = threading.Condition() @@ -181,38 +195,33 @@ class Parallel: 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. + def wait(self): + """Wait for the job to finish. A job is finished when there + are no more tasks. - To wait for the job to finish, call wait(). + This method should only be called after start() has been called. """ + # Sleeping in a loop like this is lame. Calling + # self.thread.join() would be much nicer, but + # on Linux self.thread.join() doesn't always + # return when a KeyboardInterrupt happens, and when + # it does return, it causes Python to hang on shutdown. + # In other words this is just + # a work-around for some bugs/limitations in the + # self.thread.join() method. + while self.thread.isAlive(): + time.sleep(0.5) + + def keyboard_interrupt(self): cv.acquire() - self.stop_running = 1 - # wake up the sleeping jobs so this job will end as soon as possible: - cv.notifyAll() + self.jobs.keyboard_interrupt = 1 + 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""" @@ -222,13 +231,11 @@ class Parallel: try: while 1: - while self.taskmaster.is_blocked() and not self.stop_running: + while (self.taskmaster.is_blocked() and + not self.jobs.keyboard_interrupt): 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: + if self.jobs.keyboard_interrupt: break task = self.taskmaster.next_task() @@ -242,6 +249,8 @@ class Parallel: task.execute() finally: cv.acquire() + except KeyboardInterrupt: + self.jobs.keyboard_interrupt = 1 except: # Let the failed() callback function arrange for # calling self.jobs.stop() to to stop the build @@ -253,7 +262,8 @@ class Parallel: # signal the cv whether the task failed or not, # or otherwise the other Jobs might # remain blocked: - if not self.taskmaster.is_blocked(): + if (not self.taskmaster.is_blocked() or + self.jobs.keyboard_interrupt): cv.notifyAll() finally: |