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