func (build *dbBuild) Abort() error { // the order below is very important to avoid races with build creation. lock, err := build.locker.AcquireWriteLockImmediately([]db.NamedLock{db.BuildTrackingLock(build.id)}) if err != nil { // someone else is tracking the build; abort it, which will notify them return build.db.AbortBuild(build.id) } defer lock.Release() // no one is tracking the build; abort it ourselves // first save the status so that CreateBuild will see a conflict when it // tries to mark the build as started. err = build.db.AbortBuild(build.id) if err != nil { return err } // reload the model *after* saving the status for the following check to see // if it was already started model, err := build.db.GetBuild(build.id) if err != nil { return err } // if there's an engine, there's a real build to abort if model.Engine == "" { // otherwise, CreateBuild had not yet tried to start the build, and so it // will see the conflict when it tries to transition, and abort itself. // // finish the build so that the aborted event is put into the event stream // even if the build has not started yet return build.db.FinishBuild(build.id, db.StatusAborted) } buildEngine, found := build.engines.Lookup(model.Engine) if !found { return UnknownEngineError{model.Engine} } // find the real build to abort... engineBuild, err := buildEngine.LookupBuild(model) if err != nil { return err } // ...and abort it. return engineBuild.Abort() }
BeforeEach(func() { fakeLock = new(dbfakes.FakeLock) fakeLocker.AcquireWriteLockImmediatelyReturns(fakeLock, nil) }) Context("when the build is active", func() { BeforeEach(func() { model.Engine = "fake-engine-b" fakeBuildDB.GetBuildReturns(model, nil) fakeBuildDB.AbortBuildStub = func(int) error { Ω(fakeLocker.AcquireWriteLockImmediatelyCallCount()).Should(Equal(1)) lockedBuild := fakeLocker.AcquireWriteLockImmediatelyArgsForCall(0) Ω(lockedBuild).Should(Equal([]db.NamedLock{db.BuildTrackingLock(model.ID)})) Ω(fakeLock.ReleaseCallCount()).Should(BeZero()) return nil } }) Context("when the engine build exists", func() { var realBuild *fakes.FakeBuild BeforeEach(func() { fakeBuildDB.GetBuildReturns(model, nil) realBuild = new(fakes.FakeBuild) fakeEngineB.LookupBuildReturns(realBuild, nil)
func (build *dbBuild) Resume(logger lager.Logger) { lock, err := build.locker.AcquireWriteLockImmediately([]db.NamedLock{db.BuildTrackingLock(build.id)}) if err != nil { // already being tracked somewhere; short-circuit return } defer lock.Release() model, err := build.db.GetBuild(build.id) if err != nil { logger.Error("failed-to-load-build-from-db", err) return } if model.Engine == "" { logger.Error("build-has-no-engine", err) return } if !model.IsRunning() { logger.Info("build-already-finished", lager.Data{ "build-id": build.id, }) return } buildEngine, found := build.engines.Lookup(model.Engine) if !found { logger.Error("unknown-build-engine", nil, lager.Data{ "engine": model.Engine, }) build.finishWithError(model.ID, logger) return } engineBuild, err := buildEngine.LookupBuild(model) if err != nil { logger.Error("failed-to-lookup-build-from-engine", err) build.finishWithError(model.ID, logger) return } aborts, err := build.db.AbortNotifier(build.id) if err != nil { logger.Error("failed-to-listen-for-aborts", err) return } defer aborts.Close() done := make(chan struct{}) defer close(done) go func() { select { case <-aborts.Notify(): logger.Info("aborting") err := engineBuild.Abort() if err != nil { logger.Error("failed-to-abort", err) } case <-done: } }() engineBuild.Resume(logger) }