github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/engine/engine.go (about) 1 package engine 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sync" 8 "time" 9 10 "code.cloudfoundry.org/lager" 11 "code.cloudfoundry.org/lager/lagerctx" 12 "github.com/pf-qiu/concourse/v6/atc" 13 "github.com/pf-qiu/concourse/v6/atc/creds" 14 "github.com/pf-qiu/concourse/v6/atc/db" 15 "github.com/pf-qiu/concourse/v6/atc/event" 16 "github.com/pf-qiu/concourse/v6/atc/exec" 17 "github.com/pf-qiu/concourse/v6/atc/metric" 18 "github.com/pf-qiu/concourse/v6/atc/util" 19 "github.com/pf-qiu/concourse/v6/tracing" 20 ) 21 22 //go:generate counterfeiter . Engine 23 24 type Engine interface { 25 NewBuild(db.Build) Runnable 26 27 Drain(context.Context) 28 } 29 30 //go:generate counterfeiter . Runnable 31 32 type Runnable interface { 33 Run(context.Context) 34 } 35 36 func NewEngine( 37 stepperFactory StepperFactory, 38 secrets creds.Secrets, 39 varSourcePool creds.VarSourcePool, 40 ) Engine { 41 return &engine{ 42 stepperFactory: stepperFactory, 43 release: make(chan bool), 44 trackedStates: new(sync.Map), 45 waitGroup: new(sync.WaitGroup), 46 47 globalSecrets: secrets, 48 varSourcePool: varSourcePool, 49 } 50 } 51 52 type engine struct { 53 stepperFactory StepperFactory 54 release chan bool 55 trackedStates *sync.Map 56 waitGroup *sync.WaitGroup 57 58 globalSecrets creds.Secrets 59 varSourcePool creds.VarSourcePool 60 } 61 62 func (engine *engine) Drain(ctx context.Context) { 63 logger := lagerctx.FromContext(ctx) 64 65 logger.Info("start") 66 defer logger.Info("done") 67 68 close(engine.release) 69 70 logger.Info("waiting") 71 72 engine.waitGroup.Wait() 73 } 74 75 func (engine *engine) NewBuild(build db.Build) Runnable { 76 return NewBuild( 77 build, 78 engine.stepperFactory, 79 engine.globalSecrets, 80 engine.varSourcePool, 81 engine.release, 82 engine.trackedStates, 83 engine.waitGroup, 84 ) 85 } 86 87 func NewBuild( 88 build db.Build, 89 builder StepperFactory, 90 globalSecrets creds.Secrets, 91 varSourcePool creds.VarSourcePool, 92 release chan bool, 93 trackedStates *sync.Map, 94 waitGroup *sync.WaitGroup, 95 ) Runnable { 96 return &engineBuild{ 97 build: build, 98 builder: builder, 99 100 globalSecrets: globalSecrets, 101 varSourcePool: varSourcePool, 102 103 release: release, 104 trackedStates: trackedStates, 105 waitGroup: waitGroup, 106 } 107 } 108 109 type engineBuild struct { 110 build db.Build 111 builder StepperFactory 112 113 globalSecrets creds.Secrets 114 varSourcePool creds.VarSourcePool 115 116 release chan bool 117 trackedStates *sync.Map 118 waitGroup *sync.WaitGroup 119 } 120 121 func (b *engineBuild) Run(ctx context.Context) { 122 b.waitGroup.Add(1) 123 defer b.waitGroup.Done() 124 125 logger := lagerctx.FromContext(ctx).WithData(b.build.LagerData()) 126 127 lock, acquired, err := b.build.AcquireTrackingLock(logger, time.Minute) 128 if err != nil { 129 logger.Error("failed-to-get-lock", err) 130 return 131 } 132 133 if !acquired { 134 logger.Debug("build-already-tracked") 135 return 136 } 137 138 defer lock.Release() 139 140 found, err := b.build.Reload() 141 if err != nil { 142 logger.Error("failed-to-load-build-from-db", err) 143 return 144 } 145 146 if !found { 147 logger.Info("build-not-found") 148 return 149 } 150 151 if !b.build.IsRunning() { 152 logger.Info("build-already-finished") 153 return 154 } 155 156 notifier, err := b.build.AbortNotifier() 157 if err != nil { 158 logger.Error("failed-to-listen-for-aborts", err) 159 return 160 } 161 162 defer notifier.Close() 163 164 ctx, span := tracing.StartSpanFollowing(ctx, b.build, "build", b.build.TracingAttrs()) 165 defer span.End() 166 167 stepper, err := b.builder.StepperForBuild(b.build) 168 if err != nil { 169 logger.Error("failed-to-construct-build-stepper", err) 170 171 // Fails the build if BuildStep returned an error because such unrecoverable 172 // errors will cause a build to never start to run. 173 b.buildStepErrored(logger, err.Error()) 174 b.finish(logger.Session("finish"), err, false) 175 176 return 177 } 178 179 b.trackStarted(logger) 180 defer b.trackFinished(logger) 181 182 logger.Info("running") 183 184 state, err := b.runState(logger, stepper) 185 if err != nil { 186 logger.Error("failed-to-create-run-state", err) 187 188 // Fails the build if fetching the pipeline variables fails, as these errors 189 // are unrecoverable - e.g. if pipeline var_sources is wrong 190 b.buildStepErrored(logger, err.Error()) 191 b.finish(logger.Session("finish"), err, false) 192 193 return 194 } 195 defer b.clearRunState() 196 197 ctx, cancel := context.WithCancel(ctx) 198 199 noleak := make(chan bool) 200 defer close(noleak) 201 202 go func() { 203 select { 204 case <-noleak: 205 case <-notifier.Notify(): 206 logger.Info("aborting") 207 cancel() 208 } 209 }() 210 211 var succeeded bool 212 var runErr error 213 214 done := make(chan struct{}) 215 go func() { 216 defer close(done) 217 defer func() { 218 err := util.DumpPanic(recover(), "running build plan %d", b.build.ID()) 219 if err != nil { 220 logger.Error("panic-in-engine-build-step-run", err) 221 runErr = err 222 } 223 }() 224 225 succeeded, runErr = state.Run(lagerctx.NewContext(ctx, logger), b.build.PrivatePlan()) 226 }() 227 228 select { 229 case <-b.release: 230 logger.Info("releasing") 231 232 case <-done: 233 if errors.As(runErr, &exec.Retriable{}) { 234 return 235 } 236 237 b.finish(logger.Session("finish"), runErr, succeeded) 238 } 239 } 240 241 func (b *engineBuild) buildStepErrored(logger lager.Logger, message string) { 242 err := b.build.SaveEvent(event.Error{ 243 Message: message, 244 Origin: event.Origin{ 245 ID: event.OriginID(b.build.PrivatePlan().ID), 246 }, 247 Time: time.Now().Unix(), 248 }) 249 if err != nil { 250 logger.Error("failed-to-save-error-event", err) 251 } 252 } 253 254 func (b *engineBuild) finish(logger lager.Logger, err error, succeeded bool) { 255 if errors.Is(err, context.Canceled) { 256 b.saveStatus(logger, atc.StatusAborted) 257 logger.Info("aborted") 258 259 } else if err != nil { 260 b.saveStatus(logger, atc.StatusErrored) 261 logger.Info("errored", lager.Data{"error": err.Error()}) 262 263 } else if succeeded { 264 b.saveStatus(logger, atc.StatusSucceeded) 265 logger.Info("succeeded") 266 267 } else { 268 b.saveStatus(logger, atc.StatusFailed) 269 logger.Info("failed") 270 } 271 } 272 273 func (b *engineBuild) saveStatus(logger lager.Logger, status atc.BuildStatus) { 274 if err := b.build.Finish(db.BuildStatus(status)); err != nil { 275 logger.Error("failed-to-finish-build", err) 276 } 277 } 278 279 func (b *engineBuild) trackStarted(logger lager.Logger) { 280 metric.BuildStarted{ 281 Build: b.build, 282 }.Emit(logger) 283 } 284 285 func (b *engineBuild) trackFinished(logger lager.Logger) { 286 found, err := b.build.Reload() 287 if err != nil { 288 logger.Error("failed-to-load-build-from-db", err) 289 return 290 } 291 292 if !found { 293 logger.Info("build-removed") 294 return 295 } 296 297 if !b.build.IsRunning() { 298 metric.BuildFinished{ 299 Build: b.build, 300 }.Emit(logger) 301 } 302 } 303 304 func (b *engineBuild) runState(logger lager.Logger, stepper exec.Stepper) (exec.RunState, error) { 305 id := fmt.Sprintf("build:%v", b.build.ID()) 306 existingState, ok := b.trackedStates.Load(id) 307 if ok { 308 return existingState.(exec.RunState), nil 309 } 310 credVars, err := b.build.Variables(logger, b.globalSecrets, b.varSourcePool) 311 if err != nil { 312 return nil, err 313 } 314 state, _ := b.trackedStates.LoadOrStore(id, exec.NewRunState(stepper, credVars, atc.EnableRedactSecrets)) 315 return state.(exec.RunState), nil 316 } 317 318 func (b *engineBuild) clearRunState() { 319 id := fmt.Sprintf("build:%v", b.build.ID()) 320 b.trackedStates.Delete(id) 321 }