• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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