1# Copyright (c) 2009 Google Inc. All rights reserved. 2# Copyright (c) 2009 Apple Inc. All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are 6# met: 7# 8# * Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# * Redistributions in binary form must reproduce the above 11# copyright notice, this list of conditions and the following disclaimer 12# in the documentation and/or other materials provided with the 13# distribution. 14# * Neither the name of Google Inc. nor the names of its 15# contributors may be used to endorse or promote products derived from 16# this software without specific prior written permission. 17# 18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30import os 31import time 32import traceback 33 34from datetime import datetime, timedelta 35 36from webkitpy.common.system.executive import ScriptError 37from webkitpy.common.system.deprecated_logging import log, OutputTee 38 39 40class TerminateQueue(Exception): 41 pass 42 43 44class QueueEngineDelegate: 45 def queue_log_path(self): 46 raise NotImplementedError, "subclasses must implement" 47 48 def work_item_log_path(self, work_item): 49 raise NotImplementedError, "subclasses must implement" 50 51 def begin_work_queue(self): 52 raise NotImplementedError, "subclasses must implement" 53 54 def should_continue_work_queue(self): 55 raise NotImplementedError, "subclasses must implement" 56 57 def next_work_item(self): 58 raise NotImplementedError, "subclasses must implement" 59 60 def should_proceed_with_work_item(self, work_item): 61 # returns (safe_to_proceed, waiting_message, patch) 62 raise NotImplementedError, "subclasses must implement" 63 64 def process_work_item(self, work_item): 65 raise NotImplementedError, "subclasses must implement" 66 67 def handle_unexpected_error(self, work_item, message): 68 raise NotImplementedError, "subclasses must implement" 69 70 71class QueueEngine: 72 def __init__(self, name, delegate, wakeup_event): 73 self._name = name 74 self._delegate = delegate 75 self._wakeup_event = wakeup_event 76 self._output_tee = OutputTee() 77 78 log_date_format = "%Y-%m-%d %H:%M:%S" 79 sleep_duration_text = "2 mins" # This could be generated from seconds_to_sleep 80 seconds_to_sleep = 120 81 handled_error_code = 2 82 83 # Child processes exit with a special code to the parent queue process can detect the error was handled. 84 @classmethod 85 def exit_after_handled_error(cls, error): 86 log(error) 87 exit(cls.handled_error_code) 88 89 def run(self): 90 self._begin_logging() 91 92 self._delegate.begin_work_queue() 93 while (self._delegate.should_continue_work_queue()): 94 try: 95 self._ensure_work_log_closed() 96 work_item = self._delegate.next_work_item() 97 if not work_item: 98 self._sleep("No work item.") 99 continue 100 if not self._delegate.should_proceed_with_work_item(work_item): 101 self._sleep("Not proceeding with work item.") 102 continue 103 104 # FIXME: Work logs should not depend on bug_id specificaly. 105 # This looks fixed, no? 106 self._open_work_log(work_item) 107 try: 108 if not self._delegate.process_work_item(work_item): 109 log("Unable to process work item.") 110 continue 111 except ScriptError, e: 112 # Use a special exit code to indicate that the error was already 113 # handled in the child process and we should just keep looping. 114 if e.exit_code == self.handled_error_code: 115 continue 116 message = "Unexpected failure when processing patch! Please file a bug against webkit-patch.\n%s" % e.message_with_output() 117 self._delegate.handle_unexpected_error(work_item, message) 118 except TerminateQueue, e: 119 self._stopping("TerminateQueue exception received.") 120 return 0 121 except KeyboardInterrupt, e: 122 self._stopping("User terminated queue.") 123 return 1 124 except Exception, e: 125 traceback.print_exc() 126 # Don't try tell the status bot, in case telling it causes an exception. 127 self._sleep("Exception while preparing queue") 128 self._stopping("Delegate terminated queue.") 129 return 0 130 131 def _stopping(self, message): 132 log("\n%s" % message) 133 self._delegate.stop_work_queue(message) 134 # Be careful to shut down our OutputTee or the unit tests will be unhappy. 135 self._ensure_work_log_closed() 136 self._output_tee.remove_log(self._queue_log) 137 138 def _begin_logging(self): 139 self._queue_log = self._output_tee.add_log(self._delegate.queue_log_path()) 140 self._work_log = None 141 142 def _open_work_log(self, work_item): 143 work_item_log_path = self._delegate.work_item_log_path(work_item) 144 if not work_item_log_path: 145 return 146 self._work_log = self._output_tee.add_log(work_item_log_path) 147 148 def _ensure_work_log_closed(self): 149 # If we still have a bug log open, close it. 150 if self._work_log: 151 self._output_tee.remove_log(self._work_log) 152 self._work_log = None 153 154 def _now(self): 155 """Overriden by the unit tests to allow testing _sleep_message""" 156 return datetime.now() 157 158 def _sleep_message(self, message): 159 wake_time = self._now() + timedelta(seconds=self.seconds_to_sleep) 160 return "%s Sleeping until %s (%s)." % (message, wake_time.strftime(self.log_date_format), self.sleep_duration_text) 161 162 def _sleep(self, message): 163 log(self._sleep_message(message)) 164 self._wakeup_event.wait(self.seconds_to_sleep) 165 self._wakeup_event.clear() 166