We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
enable evaluation of coverage for buildbot-worker


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `master/buildbot/schedulers/basic.py`
Content:
```
1 # This file is part of Buildbot.  Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
4 #
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8 # details.
9 #
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 #
14 # Copyright Buildbot Team Members
15 
16 from twisted.internet import defer, reactor
17 from twisted.python import log
18 from buildbot import util, config
19 from buildbot.util import NotABranch
20 from collections import defaultdict
21 from buildbot.changes import filter, changes
22 from buildbot.schedulers import base, dependent
23 
24 class BaseBasicScheduler(base.BaseScheduler):
25     """
26     @param onlyImportant: If True, only important changes will be added to the
27                           buildset.
28     @type onlyImportant: boolean
29 
30     """
31 
32     compare_attrs = (base.BaseScheduler.compare_attrs +
33                      ('treeStableTimer', 'change_filter', 'fileIsImportant',
34                       'onlyImportant') )
35 
36     _reactor = reactor # for tests
37 
38     class NotSet: pass
39     def __init__(self, name, shouldntBeSet=NotSet, treeStableTimer=None,
40                 builderNames=None, branch=NotABranch, branches=NotABranch,
41                 fileIsImportant=None, properties={}, categories=None,
42                 change_filter=None, onlyImportant=False, **kwargs):
43         if shouldntBeSet is not self.NotSet:
44             config.error(
45                 "pass arguments to schedulers using keyword arguments")
46         if fileIsImportant and not callable(fileIsImportant):
47             config.error(
48                 "fileIsImportant must be a callable")
49 
50         # initialize parent classes
51         base.BaseScheduler.__init__(self, name, builderNames, properties, **kwargs)
52 
53         self.treeStableTimer = treeStableTimer
54         self.fileIsImportant = fileIsImportant
55         self.onlyImportant = onlyImportant
56         self.change_filter = self.getChangeFilter(branch=branch,
57                 branches=branches, change_filter=change_filter,
58                 categories=categories)
59 
60         # the IDelayedCall used to wake up when this scheduler's
61         # treeStableTimer expires.
62         self._stable_timers = defaultdict(lambda : None)
63         self._stable_timers_lock = defer.DeferredLock()
64 
65     def getChangeFilter(self, branch, branches, change_filter, categories):
66         raise NotImplementedError
67 
68     def startService(self, _returnDeferred=False):
69         base.BaseScheduler.startService(self)
70 
71         d = self.startConsumingChanges(fileIsImportant=self.fileIsImportant,
72                                        change_filter=self.change_filter,
73                                        onlyImportant=self.onlyImportant)
74 
75         # if treeStableTimer is False, then we don't care about classified
76         # changes, so get rid of any hanging around from previous
77         # configurations
78         if not self.treeStableTimer:
79             d.addCallback(lambda _ :
80                 self.master.db.schedulers.flushChangeClassifications(
81                                                         self.objectid))
82 
83         # otherwise, if there are classified changes out there, start their
84         # treeStableTimers again
85         else:
86             d.addCallback(lambda _ :
87                 self.scanExistingClassifiedChanges())
88 
89         # handle Deferred errors, since startService does not return a Deferred
90         d.addErrback(log.err, "while starting SingleBranchScheduler '%s'"
91                               % self.name)
92 
93         if _returnDeferred:
94             return d # only used in tests
95 
96     def stopService(self):
97         # the base stopService will unsubscribe from new changes
98         d = base.BaseScheduler.stopService(self)
99         d.addCallback(lambda _ :
100                 self._stable_timers_lock.acquire())
101         def cancel_timers(_):
102             for timer in self._stable_timers.values():
103                 if timer:
104                     timer.cancel()
105             self._stable_timers = {}
106             self._stable_timers_lock.release()
107         d.addCallback(cancel_timers)
108         return d
109 
110     @util.deferredLocked('_stable_timers_lock')
111     def gotChange(self, change, important):
112         if not self.treeStableTimer:
113             # if there's no treeStableTimer, we can completely ignore
114             # unimportant changes
115             if not important:
116                 return defer.succeed(None)
117             # otherwise, we'll build it right away
118             return self.addBuildsetForChanges(reason='scheduler',
119                             changeids=[ change.number ])
120 
121         timer_name = self.getTimerNameForChange(change)
122 
123         # if we have a treeStableTimer, then record the change's importance
124         # and:
125         # - for an important change, start the timer
126         # - for an unimportant change, reset the timer if it is running
127         d = self.master.db.schedulers.classifyChanges(
128                 self.objectid, { change.number : important })
129         def fix_timer(_):
130             if not important and not self._stable_timers[timer_name]:
131                 return
132             if self._stable_timers[timer_name]:
133                 self._stable_timers[timer_name].cancel()
134             def fire_timer():
135                 d = self.stableTimerFired(timer_name)
136                 d.addErrback(log.err, "while firing stable timer")
137             self._stable_timers[timer_name] = self._reactor.callLater(
138                     self.treeStableTimer, fire_timer)
139         d.addCallback(fix_timer)
140         return d
141 
142     @defer.inlineCallbacks
143     def scanExistingClassifiedChanges(self):
144         # call gotChange for each classified change.  This is called at startup
145         # and is intended to re-start the treeStableTimer for any changes that
146         # had not yet been built when the scheduler was stopped.
147 
148         # NOTE: this may double-call gotChange for changes that arrive just as
149         # the scheduler starts up.  In practice, this doesn't hurt anything.
150         classifications = \
151                 yield self.master.db.schedulers.getChangeClassifications(
152                                                                 self.objectid)
153 
154         # call gotChange for each change, after first fetching it from the db
155         for changeid, important in classifications.iteritems():
156             chdict = yield self.master.db.changes.getChange(changeid)
157 
158             if not chdict:
159                 continue
160 
161             change = yield changes.Change.fromChdict(self.master, chdict)
162             yield self.gotChange(change, important)
163 
164     def getTimerNameForChange(self, change):
165         raise NotImplementedError # see subclasses
166 
167     def getChangeClassificationsForTimer(self, objectid, timer_name):
168         """similar to db.schedulers.getChangeClassifications, but given timer
169         name"""
170         raise NotImplementedError # see subclasses
171 
172     @util.deferredLocked('_stable_timers_lock')
173     @defer.inlineCallbacks
174     def stableTimerFired(self, timer_name):
175         # if the service has already been stoppd then just bail out
176         if not self._stable_timers[timer_name]:
177             return
178 
179         # delete this now-fired timer
180         del self._stable_timers[timer_name]
181 
182         classifications = \
183             yield self.getChangeClassificationsForTimer(self.objectid,
184                                                             timer_name)
185 
186         # just in case: databases do weird things sometimes!
187         if not classifications: # pragma: no cover
188             return
189 
190         changeids = sorted(classifications.keys())
191         yield self.addBuildsetForChanges(reason='scheduler',
192                                            changeids=changeids)
193 
194         max_changeid = changeids[-1] # (changeids are sorted)
195         yield self.master.db.schedulers.flushChangeClassifications(
196                             self.objectid, less_than=max_changeid+1)
197 
198 class SingleBranchScheduler(BaseBasicScheduler):
199     def getChangeFilter(self, branch, branches, change_filter, categories):
200         if branch is NotABranch and not change_filter and self.codebases is not None:
201             config.error(
202                 "the 'branch' argument to SingleBranchScheduler is " +
203                 "mandatory unless change_filter or codebases is provided")
204         elif branches is not NotABranch:
205             config.error(
206                 "the 'branches' argument is not allowed for " +
207                 "SingleBranchScheduler")
208 
209 
210         return filter.ChangeFilter.fromSchedulerConstructorArgs(
211                 change_filter=change_filter, branch=branch,
212                 categories=categories)
213 
214     def getTimerNameForChange(self, change):
215         return "only" # this class only uses one timer
216 
217     def getChangeClassificationsForTimer(self, objectid, timer_name):
218         return self.master.db.schedulers.getChangeClassifications(
219                                                         self.objectid)
220 
221 
222 class Scheduler(SingleBranchScheduler):
223     "alias for SingleBranchScheduler"
224     def __init__(self, *args, **kwargs):
225         log.msg("WARNING: the name 'Scheduler' is deprecated; use " +
226                 "buildbot.schedulers.basic.SingleBranchScheduler instead " +
227                 "(note that this may require you to change your import " +
228                 "statement)")
229         SingleBranchScheduler.__init__(self, *args, **kwargs)
230 
231 
232 class AnyBranchScheduler(BaseBasicScheduler):
233     def getChangeFilter(self, branch, branches, change_filter, categories):
234         assert branch is NotABranch
235         return filter.ChangeFilter.fromSchedulerConstructorArgs(
236                 change_filter=change_filter, branch=branches,
237                 categories=categories)
238 
239     def getTimerNameForChange(self, change):
240         return change.branch
241 
242     def getChangeClassificationsForTimer(self, objectid, timer_name):
243         branch = timer_name # set in getTimerNameForChange
244         return self.master.db.schedulers.getChangeClassifications(
245                 self.objectid, branch=branch)
246 
247 # now at buildbot.schedulers.dependent, but keep the old name alive
248 Dependent = dependent.Dependent
249
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/master/buildbot/schedulers/basic.py b/master/buildbot/schedulers/basic.py
--- a/master/buildbot/schedulers/basic.py
+++ b/master/buildbot/schedulers/basic.py
@@ -96,14 +96,12 @@
     def stopService(self):
         # the base stopService will unsubscribe from new changes
         d = base.BaseScheduler.stopService(self)
-        d.addCallback(lambda _ :
-                self._stable_timers_lock.acquire())
+        @util.deferredLocked(self._stable_timers_lock)
         def cancel_timers(_):
             for timer in self._stable_timers.values():
                 if timer:
                     timer.cancel()
-            self._stable_timers = {}
-            self._stable_timers_lock.release()
+            self._stable_timers.clear()
         d.addCallback(cancel_timers)
         return d
 
@@ -195,6 +193,11 @@
         yield self.master.db.schedulers.flushChangeClassifications(
                             self.objectid, less_than=max_changeid+1)
 
+    def getPendingBuildTimes(self):
+        # This isn't locked, since the caller expects and immediate value,
+        # and in any case, this is only an estimate.
+        return [timer.getTime() for timer in self._stable_timers.values() if timer and timer.active()]
+
 class SingleBranchScheduler(BaseBasicScheduler):
     def getChangeFilter(self, branch, branches, change_filter, categories):
         if branch is NotABranch and not change_filter and self.codebases is not None:

{"golden_diff": "diff --git a/master/buildbot/schedulers/basic.py b/master/buildbot/schedulers/basic.py\n--- a/master/buildbot/schedulers/basic.py\n+++ b/master/buildbot/schedulers/basic.py\n@@ -96,14 +96,12 @@\n     def stopService(self):\n         # the base stopService will unsubscribe from new changes\n         d = base.BaseScheduler.stopService(self)\n-        d.addCallback(lambda _ :\n-                self._stable_timers_lock.acquire())\n+        @util.deferredLocked(self._stable_timers_lock)\n         def cancel_timers(_):\n             for timer in self._stable_timers.values():\n                 if timer:\n                     timer.cancel()\n-            self._stable_timers = {}\n-            self._stable_timers_lock.release()\n+            self._stable_timers.clear()\n         d.addCallback(cancel_timers)\n         return d\n \n@@ -195,6 +193,11 @@\n         yield self.master.db.schedulers.flushChangeClassifications(\n                             self.objectid, less_than=max_changeid+1)\n \n+    def getPendingBuildTimes(self):\n+        # This isn't locked, since the caller expects and immediate value,\n+        # and in any case, this is only an estimate.\n+        return [timer.getTime() for timer in self._stable_timers.values() if timer and timer.active()]\n+\n class SingleBranchScheduler(BaseBasicScheduler):\n     def getChangeFilter(self, branch, branches, change_filter, categories):\n         if branch is NotABranch and not change_filter and self.codebases is not None:\n", "issue": "enable evaluation of coverage for buildbot-worker\n\n", "before_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nfrom twisted.internet import defer, reactor\nfrom twisted.python import log\nfrom buildbot import util, config\nfrom buildbot.util import NotABranch\nfrom collections import defaultdict\nfrom buildbot.changes import filter, changes\nfrom buildbot.schedulers import base, dependent\n\nclass BaseBasicScheduler(base.BaseScheduler):\n    \"\"\"\n    @param onlyImportant: If True, only important changes will be added to the\n                          buildset.\n    @type onlyImportant: boolean\n\n    \"\"\"\n\n    compare_attrs = (base.BaseScheduler.compare_attrs +\n                     ('treeStableTimer', 'change_filter', 'fileIsImportant',\n                      'onlyImportant') )\n\n    _reactor = reactor # for tests\n\n    class NotSet: pass\n    def __init__(self, name, shouldntBeSet=NotSet, treeStableTimer=None,\n                builderNames=None, branch=NotABranch, branches=NotABranch,\n                fileIsImportant=None, properties={}, categories=None,\n                change_filter=None, onlyImportant=False, **kwargs):\n        if shouldntBeSet is not self.NotSet:\n            config.error(\n                \"pass arguments to schedulers using keyword arguments\")\n        if fileIsImportant and not callable(fileIsImportant):\n            config.error(\n                \"fileIsImportant must be a callable\")\n\n        # initialize parent classes\n        base.BaseScheduler.__init__(self, name, builderNames, properties, **kwargs)\n\n        self.treeStableTimer = treeStableTimer\n        self.fileIsImportant = fileIsImportant\n        self.onlyImportant = onlyImportant\n        self.change_filter = self.getChangeFilter(branch=branch,\n                branches=branches, change_filter=change_filter,\n                categories=categories)\n\n        # the IDelayedCall used to wake up when this scheduler's\n        # treeStableTimer expires.\n        self._stable_timers = defaultdict(lambda : None)\n        self._stable_timers_lock = defer.DeferredLock()\n\n    def getChangeFilter(self, branch, branches, change_filter, categories):\n        raise NotImplementedError\n\n    def startService(self, _returnDeferred=False):\n        base.BaseScheduler.startService(self)\n\n        d = self.startConsumingChanges(fileIsImportant=self.fileIsImportant,\n                                       change_filter=self.change_filter,\n                                       onlyImportant=self.onlyImportant)\n\n        # if treeStableTimer is False, then we don't care about classified\n        # changes, so get rid of any hanging around from previous\n        # configurations\n        if not self.treeStableTimer:\n            d.addCallback(lambda _ :\n                self.master.db.schedulers.flushChangeClassifications(\n                                                        self.objectid))\n\n        # otherwise, if there are classified changes out there, start their\n        # treeStableTimers again\n        else:\n            d.addCallback(lambda _ :\n                self.scanExistingClassifiedChanges())\n\n        # handle Deferred errors, since startService does not return a Deferred\n        d.addErrback(log.err, \"while starting SingleBranchScheduler '%s'\"\n                              % self.name)\n\n        if _returnDeferred:\n            return d # only used in tests\n\n    def stopService(self):\n        # the base stopService will unsubscribe from new changes\n        d = base.BaseScheduler.stopService(self)\n        d.addCallback(lambda _ :\n                self._stable_timers_lock.acquire())\n        def cancel_timers(_):\n            for timer in self._stable_timers.values():\n                if timer:\n                    timer.cancel()\n            self._stable_timers = {}\n            self._stable_timers_lock.release()\n        d.addCallback(cancel_timers)\n        return d\n\n    @util.deferredLocked('_stable_timers_lock')\n    def gotChange(self, change, important):\n        if not self.treeStableTimer:\n            # if there's no treeStableTimer, we can completely ignore\n            # unimportant changes\n            if not important:\n                return defer.succeed(None)\n            # otherwise, we'll build it right away\n            return self.addBuildsetForChanges(reason='scheduler',\n                            changeids=[ change.number ])\n\n        timer_name = self.getTimerNameForChange(change)\n\n        # if we have a treeStableTimer, then record the change's importance\n        # and:\n        # - for an important change, start the timer\n        # - for an unimportant change, reset the timer if it is running\n        d = self.master.db.schedulers.classifyChanges(\n                self.objectid, { change.number : important })\n        def fix_timer(_):\n            if not important and not self._stable_timers[timer_name]:\n                return\n            if self._stable_timers[timer_name]:\n                self._stable_timers[timer_name].cancel()\n            def fire_timer():\n                d = self.stableTimerFired(timer_name)\n                d.addErrback(log.err, \"while firing stable timer\")\n            self._stable_timers[timer_name] = self._reactor.callLater(\n                    self.treeStableTimer, fire_timer)\n        d.addCallback(fix_timer)\n        return d\n\n    @defer.inlineCallbacks\n    def scanExistingClassifiedChanges(self):\n        # call gotChange for each classified change.  This is called at startup\n        # and is intended to re-start the treeStableTimer for any changes that\n        # had not yet been built when the scheduler was stopped.\n\n        # NOTE: this may double-call gotChange for changes that arrive just as\n        # the scheduler starts up.  In practice, this doesn't hurt anything.\n        classifications = \\\n                yield self.master.db.schedulers.getChangeClassifications(\n                                                                self.objectid)\n\n        # call gotChange for each change, after first fetching it from the db\n        for changeid, important in classifications.iteritems():\n            chdict = yield self.master.db.changes.getChange(changeid)\n\n            if not chdict:\n                continue\n\n            change = yield changes.Change.fromChdict(self.master, chdict)\n            yield self.gotChange(change, important)\n\n    def getTimerNameForChange(self, change):\n        raise NotImplementedError # see subclasses\n\n    def getChangeClassificationsForTimer(self, objectid, timer_name):\n        \"\"\"similar to db.schedulers.getChangeClassifications, but given timer\n        name\"\"\"\n        raise NotImplementedError # see subclasses\n\n    @util.deferredLocked('_stable_timers_lock')\n    @defer.inlineCallbacks\n    def stableTimerFired(self, timer_name):\n        # if the service has already been stoppd then just bail out\n        if not self._stable_timers[timer_name]:\n            return\n\n        # delete this now-fired timer\n        del self._stable_timers[timer_name]\n\n        classifications = \\\n            yield self.getChangeClassificationsForTimer(self.objectid,\n                                                            timer_name)\n\n        # just in case: databases do weird things sometimes!\n        if not classifications: # pragma: no cover\n            return\n\n        changeids = sorted(classifications.keys())\n        yield self.addBuildsetForChanges(reason='scheduler',\n                                           changeids=changeids)\n\n        max_changeid = changeids[-1] # (changeids are sorted)\n        yield self.master.db.schedulers.flushChangeClassifications(\n                            self.objectid, less_than=max_changeid+1)\n\nclass SingleBranchScheduler(BaseBasicScheduler):\n    def getChangeFilter(self, branch, branches, change_filter, categories):\n        if branch is NotABranch and not change_filter and self.codebases is not None:\n            config.error(\n                \"the 'branch' argument to SingleBranchScheduler is \" +\n                \"mandatory unless change_filter or codebases is provided\")\n        elif branches is not NotABranch:\n            config.error(\n                \"the 'branches' argument is not allowed for \" +\n                \"SingleBranchScheduler\")\n\n\n        return filter.ChangeFilter.fromSchedulerConstructorArgs(\n                change_filter=change_filter, branch=branch,\n                categories=categories)\n\n    def getTimerNameForChange(self, change):\n        return \"only\" # this class only uses one timer\n\n    def getChangeClassificationsForTimer(self, objectid, timer_name):\n        return self.master.db.schedulers.getChangeClassifications(\n                                                        self.objectid)\n\n\nclass Scheduler(SingleBranchScheduler):\n    \"alias for SingleBranchScheduler\"\n    def __init__(self, *args, **kwargs):\n        log.msg(\"WARNING: the name 'Scheduler' is deprecated; use \" +\n                \"buildbot.schedulers.basic.SingleBranchScheduler instead \" +\n                \"(note that this may require you to change your import \" +\n                \"statement)\")\n        SingleBranchScheduler.__init__(self, *args, **kwargs)\n\n\nclass AnyBranchScheduler(BaseBasicScheduler):\n    def getChangeFilter(self, branch, branches, change_filter, categories):\n        assert branch is NotABranch\n        return filter.ChangeFilter.fromSchedulerConstructorArgs(\n                change_filter=change_filter, branch=branches,\n                categories=categories)\n\n    def getTimerNameForChange(self, change):\n        return change.branch\n\n    def getChangeClassificationsForTimer(self, objectid, timer_name):\n        branch = timer_name # set in getTimerNameForChange\n        return self.master.db.schedulers.getChangeClassifications(\n                self.objectid, branch=branch)\n\n# now at buildbot.schedulers.dependent, but keep the old name alive\nDependent = dependent.Dependent\n", "path": "master/buildbot/schedulers/basic.py"}], "after_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nfrom twisted.internet import defer, reactor\nfrom twisted.python import log\nfrom buildbot import util, config\nfrom buildbot.util import NotABranch\nfrom collections import defaultdict\nfrom buildbot.changes import filter, changes\nfrom buildbot.schedulers import base, dependent\n\nclass BaseBasicScheduler(base.BaseScheduler):\n    \"\"\"\n    @param onlyImportant: If True, only important changes will be added to the\n                          buildset.\n    @type onlyImportant: boolean\n\n    \"\"\"\n\n    compare_attrs = (base.BaseScheduler.compare_attrs +\n                     ('treeStableTimer', 'change_filter', 'fileIsImportant',\n                      'onlyImportant') )\n\n    _reactor = reactor # for tests\n\n    class NotSet: pass\n    def __init__(self, name, shouldntBeSet=NotSet, treeStableTimer=None,\n                builderNames=None, branch=NotABranch, branches=NotABranch,\n                fileIsImportant=None, properties={}, categories=None,\n                change_filter=None, onlyImportant=False, **kwargs):\n        if shouldntBeSet is not self.NotSet:\n            config.error(\n                \"pass arguments to schedulers using keyword arguments\")\n        if fileIsImportant and not callable(fileIsImportant):\n            config.error(\n                \"fileIsImportant must be a callable\")\n\n        # initialize parent classes\n        base.BaseScheduler.__init__(self, name, builderNames, properties, **kwargs)\n\n        self.treeStableTimer = treeStableTimer\n        self.fileIsImportant = fileIsImportant\n        self.onlyImportant = onlyImportant\n        self.change_filter = self.getChangeFilter(branch=branch,\n                branches=branches, change_filter=change_filter,\n                categories=categories)\n\n        # the IDelayedCall used to wake up when this scheduler's\n        # treeStableTimer expires.\n        self._stable_timers = defaultdict(lambda : None)\n        self._stable_timers_lock = defer.DeferredLock()\n\n    def getChangeFilter(self, branch, branches, change_filter, categories):\n        raise NotImplementedError\n\n    def startService(self, _returnDeferred=False):\n        base.BaseScheduler.startService(self)\n\n        d = self.startConsumingChanges(fileIsImportant=self.fileIsImportant,\n                                       change_filter=self.change_filter,\n                                       onlyImportant=self.onlyImportant)\n\n        # if treeStableTimer is False, then we don't care about classified\n        # changes, so get rid of any hanging around from previous\n        # configurations\n        if not self.treeStableTimer:\n            d.addCallback(lambda _ :\n                self.master.db.schedulers.flushChangeClassifications(\n                                                        self.objectid))\n\n        # otherwise, if there are classified changes out there, start their\n        # treeStableTimers again\n        else:\n            d.addCallback(lambda _ :\n                self.scanExistingClassifiedChanges())\n\n        # handle Deferred errors, since startService does not return a Deferred\n        d.addErrback(log.err, \"while starting SingleBranchScheduler '%s'\"\n                              % self.name)\n\n        if _returnDeferred:\n            return d # only used in tests\n\n    def stopService(self):\n        # the base stopService will unsubscribe from new changes\n        d = base.BaseScheduler.stopService(self)\n        @util.deferredLocked(self._stable_timers_lock)\n        def cancel_timers(_):\n            for timer in self._stable_timers.values():\n                if timer:\n                    timer.cancel()\n            self._stable_timers.clear()\n        d.addCallback(cancel_timers)\n        return d\n\n    @util.deferredLocked('_stable_timers_lock')\n    def gotChange(self, change, important):\n        if not self.treeStableTimer:\n            # if there's no treeStableTimer, we can completely ignore\n            # unimportant changes\n            if not important:\n                return defer.succeed(None)\n            # otherwise, we'll build it right away\n            return self.addBuildsetForChanges(reason='scheduler',\n                            changeids=[ change.number ])\n\n        timer_name = self.getTimerNameForChange(change)\n\n        # if we have a treeStableTimer, then record the change's importance\n        # and:\n        # - for an important change, start the timer\n        # - for an unimportant change, reset the timer if it is running\n        d = self.master.db.schedulers.classifyChanges(\n                self.objectid, { change.number : important })\n        def fix_timer(_):\n            if not important and not self._stable_timers[timer_name]:\n                return\n            if self._stable_timers[timer_name]:\n                self._stable_timers[timer_name].cancel()\n            def fire_timer():\n                d = self.stableTimerFired(timer_name)\n                d.addErrback(log.err, \"while firing stable timer\")\n            self._stable_timers[timer_name] = self._reactor.callLater(\n                    self.treeStableTimer, fire_timer)\n        d.addCallback(fix_timer)\n        return d\n\n    @defer.inlineCallbacks\n    def scanExistingClassifiedChanges(self):\n        # call gotChange for each classified change.  This is called at startup\n        # and is intended to re-start the treeStableTimer for any changes that\n        # had not yet been built when the scheduler was stopped.\n\n        # NOTE: this may double-call gotChange for changes that arrive just as\n        # the scheduler starts up.  In practice, this doesn't hurt anything.\n        classifications = \\\n                yield self.master.db.schedulers.getChangeClassifications(\n                                                                self.objectid)\n\n        # call gotChange for each change, after first fetching it from the db\n        for changeid, important in classifications.iteritems():\n            chdict = yield self.master.db.changes.getChange(changeid)\n\n            if not chdict:\n                continue\n\n            change = yield changes.Change.fromChdict(self.master, chdict)\n            yield self.gotChange(change, important)\n\n    def getTimerNameForChange(self, change):\n        raise NotImplementedError # see subclasses\n\n    def getChangeClassificationsForTimer(self, objectid, timer_name):\n        \"\"\"similar to db.schedulers.getChangeClassifications, but given timer\n        name\"\"\"\n        raise NotImplementedError # see subclasses\n\n    @util.deferredLocked('_stable_timers_lock')\n    @defer.inlineCallbacks\n    def stableTimerFired(self, timer_name):\n        # if the service has already been stoppd then just bail out\n        if not self._stable_timers[timer_name]:\n            return\n\n        # delete this now-fired timer\n        del self._stable_timers[timer_name]\n\n        classifications = \\\n            yield self.getChangeClassificationsForTimer(self.objectid,\n                                                            timer_name)\n\n        # just in case: databases do weird things sometimes!\n        if not classifications: # pragma: no cover\n            return\n\n        changeids = sorted(classifications.keys())\n        yield self.addBuildsetForChanges(reason='scheduler',\n                                           changeids=changeids)\n\n        max_changeid = changeids[-1] # (changeids are sorted)\n        yield self.master.db.schedulers.flushChangeClassifications(\n                            self.objectid, less_than=max_changeid+1)\n\n    def getPendingBuildTimes(self):\n        # This isn't locked, since the caller expects and immediate value,\n        # and in any case, this is only an estimate.\n        return [timer.getTime() for timer in self._stable_timers.values() if timer and timer.active()]\n\nclass SingleBranchScheduler(BaseBasicScheduler):\n    def getChangeFilter(self, branch, branches, change_filter, categories):\n        if branch is NotABranch and not change_filter and self.codebases is not None:\n            config.error(\n                \"the 'branch' argument to SingleBranchScheduler is \" +\n                \"mandatory unless change_filter or codebases is provided\")\n        elif branches is not NotABranch:\n            config.error(\n                \"the 'branches' argument is not allowed for \" +\n                \"SingleBranchScheduler\")\n\n\n        return filter.ChangeFilter.fromSchedulerConstructorArgs(\n                change_filter=change_filter, branch=branch,\n                categories=categories)\n\n    def getTimerNameForChange(self, change):\n        return \"only\" # this class only uses one timer\n\n    def getChangeClassificationsForTimer(self, objectid, timer_name):\n        return self.master.db.schedulers.getChangeClassifications(\n                                                        self.objectid)\n\n\nclass Scheduler(SingleBranchScheduler):\n    \"alias for SingleBranchScheduler\"\n    def __init__(self, *args, **kwargs):\n        log.msg(\"WARNING: the name 'Scheduler' is deprecated; use \" +\n                \"buildbot.schedulers.basic.SingleBranchScheduler instead \" +\n                \"(note that this may require you to change your import \" +\n                \"statement)\")\n        SingleBranchScheduler.__init__(self, *args, **kwargs)\n\n\nclass AnyBranchScheduler(BaseBasicScheduler):\n    def getChangeFilter(self, branch, branches, change_filter, categories):\n        assert branch is NotABranch\n        return filter.ChangeFilter.fromSchedulerConstructorArgs(\n                change_filter=change_filter, branch=branches,\n                categories=categories)\n\n    def getTimerNameForChange(self, change):\n        return change.branch\n\n    def getChangeClassificationsForTimer(self, objectid, timer_name):\n        branch = timer_name # set in getTimerNameForChange\n        return self.master.db.schedulers.getChangeClassifications(\n                self.objectid, branch=branch)\n\n# now at buildbot.schedulers.dependent, but keep the old name alive\nDependent = dependent.Dependent\n", "path": "master/buildbot/schedulers/basic.py"}]}