terraform: Context.Stop() calls Stop on providers if running

This commit is contained in:
Mitchell Hashimoto 2016-10-23 17:55:45 -07:00
parent d338a1ef88
commit a61b7227f5
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
2 changed files with 48 additions and 0 deletions

View File

@ -105,6 +105,7 @@ type Context struct {
parallelSem Semaphore parallelSem Semaphore
providerInputConfig map[string]map[string]interface{} providerInputConfig map[string]map[string]interface{}
runCh <-chan struct{} runCh <-chan struct{}
stopCh chan struct{}
shadowErr error shadowErr error
} }
@ -587,6 +588,9 @@ func (c *Context) Stop() {
// Tell the hook we want to stop // Tell the hook we want to stop
c.sh.Stop() c.sh.Stop()
// Close the stop channel
close(c.stopCh)
// Wait for us to stop // Wait for us to stop
c.l.Unlock() c.l.Unlock()
<-ch <-ch
@ -672,6 +676,9 @@ func (c *Context) acquireRun() chan<- struct{} {
ch := make(chan struct{}) ch := make(chan struct{})
c.runCh = ch c.runCh = ch
// Reset the stop channel so we can watch that
c.stopCh = make(chan struct{})
// Reset the stop hook so we're not stopped // Reset the stop hook so we're not stopped
c.sh.Reset() c.sh.Reset()
@ -687,6 +694,7 @@ func (c *Context) releaseRun(ch chan<- struct{}) {
close(ch) close(ch)
c.runCh = nil c.runCh = nil
c.stopCh = nil
} }
func (c *Context) walk( func (c *Context) walk(
@ -714,9 +722,16 @@ func (c *Context) walk(
log.Printf("[DEBUG] Starting graph walk: %s", operation.String()) log.Printf("[DEBUG] Starting graph walk: %s", operation.String())
walker := &ContextGraphWalker{Context: realCtx, Operation: operation} walker := &ContextGraphWalker{Context: realCtx, Operation: operation}
// Watch for a stop so we can call the provider Stop() API.
doneCh := make(chan struct{})
go c.watchStop(walker, c.stopCh, doneCh)
// Walk the real graph, this will block until it completes // Walk the real graph, this will block until it completes
realErr := graph.Walk(walker) realErr := graph.Walk(walker)
// Close the done channel so the watcher stops
close(doneCh)
// If we have a shadow graph and we interrupted the real graph, then // If we have a shadow graph and we interrupted the real graph, then
// we just close the shadow and never verify it. It is non-trivial to // we just close the shadow and never verify it. It is non-trivial to
// recreate the exact execution state up until an interruption so this // recreate the exact execution state up until an interruption so this
@ -796,6 +811,35 @@ func (c *Context) walk(
return walker, realErr return walker, realErr
} }
func (c *Context) watchStop(walker *ContextGraphWalker, stopCh, doneCh <-chan struct{}) {
// Wait for a stop or completion
select {
case <-stopCh:
// Stop was triggered. Fall out of the select
case <-doneCh:
// Done, just exit completely
return
}
// If we're here, we're stopped, trigger the call.
// Copy the providers so that a misbehaved blocking Stop doesn't
// completely hang Terraform.
walker.providerLock.Lock()
ps := make([]ResourceProvider, 0, len(walker.providerCache))
for _, p := range walker.providerCache {
ps = append(ps, p)
}
defer walker.providerLock.Unlock()
for _, p := range ps {
// We ignore the error for now since there isn't any reasonable
// action to take if there is an error here, since the stop is still
// advisory: Terraform will exit once the graph node completes.
p.Stop()
}
}
// parseVariableAsHCL parses the value of a single variable as would have been specified // parseVariableAsHCL parses the value of a single variable as would have been specified
// on the command line via -var or in an environment variable named TF_VAR_x, where x is // on the command line via -var or in an environment variable named TF_VAR_x, where x is
// the name of the variable. In order to get around the restriction of HCL requiring a // the name of the variable. In order to get around the restriction of HCL requiring a

View File

@ -1043,6 +1043,10 @@ func TestContext2Apply_cancel(t *testing.T) {
if actual != expected { if actual != expected {
t.Fatalf("bad: \n%s", actual) t.Fatalf("bad: \n%s", actual)
} }
if !p.StopCalled {
t.Fatal("stop should be called")
}
} }
func TestContext2Apply_compute(t *testing.T) { func TestContext2Apply_compute(t *testing.T) {