[Buildbot-commits] buildbot/buildbot/process base.py, 1.71, 1.72 builder.py, 1.37, 1.38

Brian Warner warner at users.sourceforge.net
Fri Nov 24 07:16:37 UTC 2006


Update of /cvsroot/buildbot/buildbot/buildbot/process
In directory sc8-pr-cvs3.sourceforge.net:/tmp/cvs-serv32609/buildbot/process

Modified Files:
	base.py builder.py 
Log Message:
[project @ reconfig no longer interrupts builds, nor does it disconnect/reconnect slaves]

Original author: warner at lothar.com
Date: 2006-11-23 21:32:36

Index: base.py
===================================================================
RCS file: /cvsroot/buildbot/buildbot/buildbot/process/base.py,v
retrieving revision 1.71
retrieving revision 1.72
diff -u -d -r1.71 -r1.72
--- base.py	25 Sep 2006 02:43:56 -0000	1.71
+++ base.py	24 Nov 2006 07:16:35 -0000	1.72
@@ -299,6 +299,10 @@
         self.remote = slavebuilder.remote
         self.remote.notifyOnDisconnect(self.lostRemote)
         d = self.deferred = defer.Deferred()
+        def _release_slave(res):
+            self.slavebuilder.buildFinished()
+            return res
+        d.addCallback(_release_slave)
 
         try:
             self.setupBuild(expectations) # create .steps

Index: builder.py
===================================================================
RCS file: /cvsroot/buildbot/buildbot/buildbot/process/builder.py,v
retrieving revision 1.37
retrieving revision 1.38
diff -u -d -r1.37 -r1.38
--- builder.py	6 Sep 2006 00:41:55 -0000	1.37
+++ builder.py	24 Nov 2006 07:16:35 -0000	1.38
@@ -24,13 +24,14 @@
     buildbot. When a remote builder connects, I query it for command versions
     and then make it available to any Builds that are ready to run. """
 
-    state = ATTACHING
-    remote = None
-    build = None
-
-    def __init__(self, builder):
-        self.builder = builder
+    def __init__(self):
         self.ping_watchers = []
+        self.state = ATTACHING
+        self.remote = None
+
+    def setBuilder(self, b):
+        self.builder = b
+        self.builder_name = b.name
 
     def getSlaveCommandVersion(self, command, oldversion=None):
         if self.remoteCommands is None:
@@ -38,12 +39,17 @@
             return oldversion
         return self.remoteCommands.get(command)
 
+    def isAvailable(self):
+        if self.state == IDLE:
+            return True
+        return False
+
     def attached(self, slave, remote, commands):
         self.slave = slave
         self.remote = remote
         self.remoteCommands = commands # maps command name to version
         log.msg("Buildslave %s attached to %s" % (slave.slavename,
-                                                  self.builder.name))
+                                                  self.builder_name))
         d = self.remote.callRemote("setMaster", self)
         d.addErrback(self._attachFailure, "Builder.setMaster")
         d.addCallback(self._attached2)
@@ -57,6 +63,7 @@
 
     def _attached3(self, res):
         # now we say they're really attached
+        self.state = IDLE
         return self
 
     def _attachFailure(self, why, where):
@@ -67,17 +74,17 @@
 
     def detached(self):
         log.msg("Buildslave %s detached from %s" % (self.slave.slavename,
-                                                    self.builder.name))
+                                                    self.builder_name))
         self.slave = None
         self.remote = None
         self.remoteCommands = None
 
-    def startBuild(self, build):
-        self.build = build
-
-    def finishBuild(self):
-        self.build = None
+    def buildStarted(self):
+        self.state = BUILDING
 
+    def buildFinished(self):
+        self.state = IDLE
+        reactor.callLater(0, self.builder.maybeStartBuild)
 
     def ping(self, timeout, status=None):
         """Ping the slave to make sure it is still there. Returns a Deferred
@@ -87,6 +94,7 @@
                        event will be pushed.
         """
 
+        self.state = PINGING
         newping = not self.ping_watchers
         d = defer.Deferred()
         self.ping_watchers.append(d)
@@ -287,7 +295,7 @@
         return diffs
 
     def __repr__(self):
-        return "<Builder '%s'>" % self.name
+        return "<Builder '%s' at %d>" % (self.name, id(self))
 
 
     def submitBuildRequest(self, req):
@@ -316,6 +324,64 @@
         self.building = []
         self.slaves = []
 
+    def consumeTheSoulOfYourPredecessor(self, old):
+        """Suck the brain out of an old Builder.
+
+        This takes all the runtime state from an existing Builder and moves
+        it into ourselves. This is used when a Builder is changed in the
+        master.cfg file: the new Builder has a different factory, but we want
+        all the builds that were queued for the old one to get processed by
+        the new one. Any builds which are already running will keep running.
+        The new Builder will get as many of the old SlaveBuilder objects as
+        it wants."""
+
+        log.msg("consumeTheSoulOfYourPredecessor: %s feeding upon %s" %
+                (self, old))
+        # we claim all the pending builds, removing them from the old
+        # Builder's queue. This insures that the old Builder will not start
+        # any new work.
+        log.msg(" stealing %s buildrequests" % len(old.buildable))
+        self.buildable.extend(old.buildable)
+        old.buildable = []
+
+        # old.building is not migrated: it keeps track of builds which were
+        # in progress in the old Builder. When those builds finish, the old
+        # Builder will be notified, not us. However, since the old
+        # SlaveBuilder will point to us, it is our maybeStartBuild() that
+        # will be triggered.
+        if old.building:
+            self.builder_status.setBigState("building")
+
+        # Our set of slavenames may be different. Steal any of the old
+        # buildslaves that we want to keep using.
+        for sb in old.slaves[:]:
+            if sb.slave.slavename in self.slavenames:
+                log.msg(" stealing buildslave %s" % sb)
+                self.slaves.append(sb)
+                old.slaves.remove(sb)
+                sb.setBuilder(self)
+
+        # old.attaching_slaves:
+        #  these SlaveBuilders are waiting on a sequence of calls:
+        #  remote.setMaster and remote.print . When these two complete,
+        #  old._attached will be fired, which will add a 'connect' event to
+        #  the builder_status and try to start a build. However, we've pulled
+        #  everything out of the old builder's queue, so it will have no work
+        #  to do. The outstanding remote.setMaster/print call will be holding
+        #  the last reference to the old builder, so it will disappear just
+        #  after that response comes back.
+        #
+        #  The BotMaster will ask the slave to re-set their list of Builders
+        #  shortly after this function returns, which will cause our
+        #  attached() method to be fired with a bunch of references to remote
+        #  SlaveBuilders, some of which we already have (by stealing them
+        #  from the old Builder), some of which will be new. The new ones
+        #  will be re-attached.
+
+        #  Therefore, we don't need to do anything about old.attaching_slaves
+
+        return # all done
+
     def fireTestEvent(self, name, with=None):
         if with is None:
             with = self
@@ -360,7 +426,8 @@
                 # re-vivifies sb
                 return defer.succeed(self)
 
-        sb = SlaveBuilder(self)
+        sb = SlaveBuilder()
+        sb.setBuilder(self)
         self.attaching_slaves.append(sb)
         d = sb.attached(slave, remote, commands)
         d.addCallback(self._attached)
@@ -370,7 +437,6 @@
     def _attached(self, sb):
         # TODO: make this .addSlaveEvent(slave.slavename, ['connect']) ?
         self.builder_status.addPointEvent(['connect', sb.slave.slavename])
-        sb.state = IDLE
         self.attaching_slaves.remove(sb)
         self.slaves.append(sb)
         self.maybeStartBuild()
@@ -433,13 +499,14 @@
             self.fireTestEvent('idle')
 
     def maybeStartBuild(self):
-        log.msg("maybeStartBuild: %s %s" % (self.buildable, self.slaves))
+        log.msg("maybeStartBuild %s: %s %s" %
+                (self, self.buildable, self.slaves))
         if not self.buildable:
             self.updateBigStatus()
             return # nothing to do
         # find the first idle slave
         for sb in self.slaves:
-            if sb.state == IDLE:
+            if sb.isAvailable():
                 break
         else:
             log.msg("%s: want to start build, but we don't have a remote"
@@ -480,12 +547,6 @@
         watch the Build as it runs. """
 
         self.building.append(build)
-
-        # claim the slave. TODO: consider moving changes to sb.state inside
-        # SlaveBuilder.. that would be cleaner.
-        sb.state = PINGING
-        sb.startBuild(build)
-
         self.updateBigStatus()
 
         log.msg("starting build %s.. pinging the slave" % build)
@@ -501,8 +562,10 @@
     def _startBuild_1(self, res, build, sb):
         if not res:
             return self._startBuildFailed("slave ping failed", build, sb)
-        # The buildslave is ready to go.
-        sb.state = BUILDING
+        # The buildslave is ready to go. sb.buildStarted() sets its state to
+        # BUILDING (so we won't try to use it for any other builds). This
+        # gets set back to IDLE by the Build itself when it finishes.
+        sb.buildStarted()
         d = sb.remote.callRemote("startBuild")
         d.addCallbacks(self._startBuild_2, self._startBuildFailed,
                        callbackArgs=(build,sb), errbackArgs=(build,sb))
@@ -528,38 +591,30 @@
         # put the build back on the buildable list
         log.msg("I tried to tell the slave that the build %s started, but "
                 "remote_startBuild failed: %s" % (build, why))
-        # release the slave
-        sb.finishBuild()
-        sb.state = IDLE
+        # release the slave. This will queue a call to maybeStartBuild, which
+        # will fire after other notifyOnDisconnect handlers have marked the
+        # slave as disconnected (so we don't try to use it again).
+        sb.buildFinished()
 
         log.msg("re-queueing the BuildRequest")
         self.building.remove(build)
         for req in build.requests:
-            self.buildable.insert(0, req) # they get first priority
+            self.buildable.insert(0, req) # the interrupted build gets first
+                                          # priority
             self.builder_status.addBuildRequest(req.status)
 
-        # other notifyOnDisconnect calls will mark the slave as disconnected.
-        # Re-try after they have fired, maybe there's another slave
-        # available. TODO: I don't like these un-synchronizable callLaters..
-        # a better solution is to mark the SlaveBuilder as disconnected
-        # ourselves, but we'll need to make sure that they can tolerate
-        # multiple disconnects first.
-        reactor.callLater(0, self.maybeStartBuild)
 
     def buildFinished(self, build, sb):
         """This is called when the Build has finished (either success or
         failure). Any exceptions during the build are reported with
         results=FAILURE, not with an errback."""
 
-        # release the slave
-        sb.finishBuild()
-        sb.state = IDLE
-        # otherwise the slave probably got removed in detach()
+        # by the time we get here, the Build has already released the slave
+        # (which queues a call to maybeStartBuild)
 
         self.building.remove(build)
         for req in build.requests:
             req.finished(build.build_status)
-        self.maybeStartBuild()
 
     def setExpectations(self, progress):
         """Mark the build as successful and update expectations for the next
@@ -608,7 +663,7 @@
         # see if there is an idle slave, so we can emit an appropriate error
         # message
         for sb in self.original.slaves:
-            if sb.state == IDLE:
+            if sb.isAvailable():
                 break
         else:
             if self.original.building:





More information about the Commits mailing list