diff -r 6eb7c69356f6 buildbot/scheduler.py
--- a/buildbot/scheduler.py	Wed Jun 25 10:47:24 2008 -0400
+++ b/buildbot/scheduler.py	Wed Jun 25 16:13:08 2008 -0400
@@ -393,16 +393,73 @@
         return []
 
     def doPeriodicBuild(self):
         bs = buildset.BuildSet(self.builderNames,
                                SourceStamp(branch=self.branch),
                                self.reason,
                                properties=self.properties)
         self.submitBuildSet(bs)
+
+
+
+class Idle(BaseUpstreamScheduler):
+    compare_attrs = ('name', 'builderNames', 'upstream', 'idleBuildTimer',
+                     'branch', 'properties')
+
+    def __init__(self, name, builderNames, upstream, idleBuildTimer,
+                 branch=None, properties={}):
+        BaseUpstreamScheduler.__init__(self, name, properties)
+        self.builderNames = builderNames
+        self.branch = branch
+        self.idleBuildTimer = idleBuildTimer
+        self.reason = ("The Idle scheduler name '%s' triggered this build"
+                       % name)
+        self.runningBuilds = 0
+
+        assert isinstance(upstream, (list, tuple)), \
+          "Upstream schedulers in Idle scheduler must be a list"
+        assert len(upstream) > 0, \
+          "You must provide at least 1 upstream scheduler to an Idle scheduler"
+        self.upstream = upstream
+        for s in self.upstream:
+            assert interfaces.IUpstreamScheduler.providedBy(s)
+            s.subscribeToBuilds(self.stopTimer, start=True, success=False)
+            s.subscribeToBuilds(self.restartTimer, failure=True)
+            # we can't add ourselves to self.upstream because it will cause
+            # BuildMaster.loadConfig_Schedulers to recurse infinitely
+            self.subscribeToBuilds(self.stopTimer, start=True, success=False)
+            self.subscribeToBuilds(self.restartTimer, failure=True)
+
+        self.timer = internet.TimerService(self.idleBuildTimer,
+                                           self.doPeriodicBuild)
+        self.timer.setServiceParent(self)
+
+    def listBuilderNames(self):
+        return self.builderNames
+
+    def getPendingBuildTimes(self):
+        return []
+
+    def doPeriodicBuild(self):
+        bs = buildset.BuildSet(self.builderNames,
+                               SourceStamp(branch=self.branch),
+                               self.reason,
+                               properties=self.properties)
+        self.submitBuildSet(bs)
+
+    def stopTimer(self, ss):
+        self.runningBuilds += 1
+        if self.timer._loop.running:
+            self.timer._loop.stop()
+
+    def restartTimer(self, ss):
+        self.runningBuilds -= 1
+        if self.runningBuilds == 0:
+            self.timer._loop.start(self.idleBuildTimer, now=False)
 
 
 
 class Nightly(BaseUpstreamScheduler):
     """Imitate 'cron' scheduling. This can be used to schedule a nightly
     build, or one which runs are certain times of the day, week, or month.
 
     Pass some subset of minute, hour, dayOfMonth, month, and dayOfWeek; each
diff -r 6eb7c69356f6 buildbot/test/test_run.py
--- a/buildbot/test/test_run.py	Wed Jun 25 10:47:24 2008 -0400
+++ b/buildbot/test/test_run.py	Wed Jun 25 16:13:08 2008 -0400
@@ -62,16 +62,32 @@
 
 from buildbot.scheduler import Scheduler
 c['schedulers'] = [Scheduler('dummy', None, 0.1, ['dummy', 'dummy2'])]
 
 c['builders'].append({'name': 'dummy', 'slavename': 'bot1',
                       'builddir': 'dummy', 'factory': f2})
 c['builders'].append({'name': 'dummy2', 'slavename': 'bot1',
                       'builddir': 'dummy2', 'factory': f2})
+"""
+
+config_idle = config_base + """
+c['slaves'].append(BuildSlave('bot2', 'sekrit'))
+
+from buildbot.scheduler import Idle, Scheduler
+
+f2 = factory.BuildFactory([s(dummy.Dummy, timeout=2)])
+
+c['schedulers'] = []
+s = Scheduler('dummy2', None, 0, ['dummy'])
+c['schedulers'].append(s)
+c['schedulers'].append(Idle('dummy', ['dummy'], [s], 4))
+
+c['builders'] = [{'name': 'dummy', 'slavenames': ['bot1', 'bot2'],
+                 'builddir': 'dummy', 'factory': f2}]
 """
 
 config_2 = config_base + """
 c['builders'] = [{'name': 'dummy', 'slavename': 'bot1',
                   'builddir': 'dummy1', 'factory': f2},
                  {'name': 'testdummy', 'slavename': 'bot1',
                   'builddir': 'dummy2', 'factory': f2, 'category': 'test'}]
 """
@@ -209,16 +225,93 @@
                             if builder.slaves[0].state == BUILDING ]
             # assert that only one build is running right now. If the
             # max_builds= weren't in effect, this would be 2.
             self.failUnlessEqual(len(building_bs), 1)
         d.addCallback(_check)
 
         return d
 
+
+class Idle(RunMixin, unittest.TestCase):
+    def setUp(self):
+        RunMixin.setUp(self)
+
+        self.master.loadConfig(config_idle)
+        self.master.readConfig = True
+        self.master.startService()
+
+        return self.connectSlave()
+
+    def isBuilding(self):
+        b = self.master.botmaster.builders['dummy']
+        return len(b.building)
+
+    def forceBuild(self):
+        c = changes.Change("foo", ["blah"], "stuff")
+        self.master.change_svc.addChange(c)
+
+    def testIdleTimerReset(self):
+        # idle timer is set to 4, if we wait that long we should have a build
+        # going
+        self.failUnlessEqual(self.isBuilding(), 1)
+        d = defer.Deferred()
+        reactor.callLater(2.1, d.callback, None)
+        d.addCallback(self._testIdleTimerReset_1)
+        return d
+
+    def _testIdleTimerReset_1(self, res):
+        self.failUnlessEqual(self.isBuilding(), 0)
+        # now wait until after it is done and force a build
+        d = defer.Deferred()
+        reactor.callLater(2, d.callback, None)
+        d.addCallback(self._testIdleTimerReset_2)
+        return d
+
+    def _testIdleTimerReset_2(self, res):
+        self.forceBuild()
+        d = defer.Deferred()
+        # need extra time here because changes are delayed slightly
+        reactor.callLater(4, d.callback, None)
+        d.addCallback(self._testIdleTimerReset_3)
+        return d
+
+    def _testIdleTimerReset_3(self, res):
+        self.failUnlessEqual(self.isBuilding(), 0)
+        d = defer.Deferred()
+        reactor.callLater(4, d.callback, None)
+        d.addCallback(self._testIdleTimerReset_4)
+        return d
+
+    def _testIdleTimerReset_4(self, res):
+        self.failUnlessEqual(self.isBuilding(), 1)
+
+    def testIdleTimerManyBuilds(self):
+        d = self.connectSlave(slavename='bot2')
+        d.addCallback(self._testIdleTimerManyBuilds_1)
+        return d
+
+    def _testIdleTimerManyBuilds_1(self, res):
+        self.forceBuild()
+        self.forceBuild()
+        d = defer.Deferred()
+        reactor.callLater(1, d.callback, None)
+        d.addCallback(self._testIdleTimerManyBuilds_2)
+        return d
+
+    def _testIdleTimerManyBuilds_2(self, res):
+        b = self.master.botmaster.builders['dummy']
+        self.failUnlessEqual(self.isBuilding(), 2)
+        d = defer.Deferred()
+        reactor.callLater(4, d.callback, None)
+        d.addCallback(self._testIdleTimerManyBuilds_3)
+        return d
+
+    def _testIdleTimerManyBuilds_3(self, res):
+        self.failUnlessEqual(self.isBuilding(), 0)
 
 class Ping(RunMixin, unittest.TestCase):
     def testPing(self):
         self.master.loadConfig(config_2)
         self.master.readConfig = True
         self.master.startService()
 
         d = self.connectSlave()
diff -r 6eb7c69356f6 docs/buildbot.texinfo
--- a/docs/buildbot.texinfo	Wed Jun 25 10:47:24 2008 -0400
+++ b/docs/buildbot.texinfo	Wed Jun 25 16:13:08 2008 -0400
@@ -140,16 +140,17 @@
 * Debug options::               
 
 Change Sources and Schedulers
 
 * Scheduler Scheduler::             
 * AnyBranchScheduler::          
 * Dependent Scheduler::          
 * Periodic Scheduler::          
+* Idle Scheduler::
 * Nightly Scheduler::          
 * Try Schedulers::          
 * Triggerable Scheduler::          
 
 Buildslave Specifiers
 
 * When Buildslaves Go Missing::  
 
@@ -2229,16 +2230,17 @@
 docstrings there are the best source of documentation on the arguments
 taken by each one.
 
 @menu
 * Scheduler Scheduler::             
 * AnyBranchScheduler::          
 * Dependent Scheduler::          
 * Periodic Scheduler::          
+* Idle Scheduler::
 * Nightly Scheduler::          
 * Try Schedulers::          
 * Triggerable Scheduler::          
 @end menu
 
 @node Scheduler Scheduler
 @subsection Scheduler Scheduler
 @slindex buildbot.scheduler.Scheduler
@@ -2432,16 +2434,56 @@
 c['schedulers'] = [nightly]
 @end example
 
 The Scheduler in this example just runs the full solaris build once
 per day. Note that this Scheduler only lets you control the time
 between builds, not the absolute time-of-day of each Build, so this
 could easily wind up a ``daily'' or ``every afternoon'' scheduler
 depending upon when it was first activated.
+
+@node Idle Scheduler
+@subsection Idle Scheduler
+@slindex buildbot.scheduler.Idle
+
+This scheduler triggers builds after N seconds of idle time. Idle time
+is determined by subscribing to builds from upstream schedulers, and
+builds triggered by this scheduler.
+
+@table @code
+@item name
+
+@item builderNames
+
+@item upstream
+A list of upstream scheduler instances to watch. Whenever a build triggered
+by any of these schedulers is running, the idle timer is stopped. When
+all running builds from these schedulers are completed the timer is
+restarted.
+
+@item idleBuildTimer
+The amount of idle time, in seconds, required before a build is triggered.
+@end table
+
+@example
+from buildbot import scheduler
+s = scheduler.Scheduler(name="onchange",
+                        builderNames=["linux", "osx"],
+                        branch="feature-branch",
+                        treeStableTimer=60*5)
+idle = scheduler.Idle(name="idle",
+                      builderNames=["linux", "osx"],
+                      branch="feature-branch",
+                      idleBuildTimer=60*60*2, # 2 hours
+                      upstream=[s])
+c['schedulers'] = [s, idle]
+@end example
+
+In this scenario builds will be triggered when the tree changes, as well as
+every 2 hours of inactivity.
 
 @node Nightly Scheduler
 @subsection Nightly Scheduler
 @slindex buildbot.scheduler.Nightly
 
 This is highly configurable periodic build scheduler, which triggers
 a build at particular times of day, week, month, or year. The
 configuration syntax is very similar to the well-known @code{crontab}

