provider/template: fix race causing panic in template_file
The render code path in `template_file` was doing unsynchronized access to a shared mapping of functions in `config.Func`. This caused a race condition that was most often triggered when a `template_file` had a `count` of more than one, and expressed itself as a panic in the plugin followed by a cascade of "unexpected EOF" errors through the plugin system. Here, we simply turn the FuncMap from shared state into a generated value, which avoids the race. We do more re-initialization of the data structure, but the performance implications are minimal, and we can always revisit with a perf pass later now that the race is fixed.
This commit is contained in:
parent
ead4865b7f
commit
0739cf2348
|
@ -155,7 +155,7 @@ func execute(s string, vars map[string]interface{}) (string, error) {
|
||||||
cfg := lang.EvalConfig{
|
cfg := lang.EvalConfig{
|
||||||
GlobalScope: &ast.BasicScope{
|
GlobalScope: &ast.BasicScope{
|
||||||
VarMap: varmap,
|
VarMap: varmap,
|
||||||
FuncMap: config.Funcs,
|
FuncMap: config.Funcs(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
r "github.com/hashicorp/terraform/helper/resource"
|
r "github.com/hashicorp/terraform/helper/resource"
|
||||||
|
@ -76,6 +77,29 @@ func TestTemplateVariableChange(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test covers a panic due to config.Func formerly being a
|
||||||
|
// shared map, causing multiple template_file resources to try and
|
||||||
|
// accessing it parallel during their lang.Eval() runs.
|
||||||
|
//
|
||||||
|
// Before fix, test fails under `go test -race`
|
||||||
|
func TestTemplateSharedMemoryRace(t *testing.T) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
go func(wg sync.WaitGroup, t *testing.T, i int) {
|
||||||
|
wg.Add(1)
|
||||||
|
out, err := execute("don't panic!", map[string]interface{}{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if out != "don't panic!" {
|
||||||
|
t.Fatalf("bad output: %s", out)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}(wg, t, i)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func testTemplateConfig(template, vars string) string {
|
func testTemplateConfig(template, vars string) string {
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
resource "template_file" "t0" {
|
resource "template_file" "t0" {
|
||||||
|
|
|
@ -20,10 +20,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Funcs is the mapping of built-in functions for configuration.
|
// Funcs is the mapping of built-in functions for configuration.
|
||||||
var Funcs map[string]ast.Function
|
func Funcs() map[string]ast.Function {
|
||||||
|
return map[string]ast.Function{
|
||||||
func init() {
|
|
||||||
Funcs = map[string]ast.Function{
|
|
||||||
"cidrhost": interpolationFuncCidrHost(),
|
"cidrhost": interpolationFuncCidrHost(),
|
||||||
"cidrnetmask": interpolationFuncCidrNetmask(),
|
"cidrnetmask": interpolationFuncCidrNetmask(),
|
||||||
"cidrsubnet": interpolationFuncCidrSubnet(),
|
"cidrsubnet": interpolationFuncCidrSubnet(),
|
||||||
|
|
|
@ -300,7 +300,7 @@ type gobRawConfig struct {
|
||||||
// langEvalConfig returns the evaluation configuration we use to execute.
|
// langEvalConfig returns the evaluation configuration we use to execute.
|
||||||
func langEvalConfig(vs map[string]ast.Variable) *lang.EvalConfig {
|
func langEvalConfig(vs map[string]ast.Variable) *lang.EvalConfig {
|
||||||
funcMap := make(map[string]ast.Function)
|
funcMap := make(map[string]ast.Function)
|
||||||
for k, v := range Funcs {
|
for k, v := range Funcs() {
|
||||||
funcMap[k] = v
|
funcMap[k] = v
|
||||||
}
|
}
|
||||||
funcMap["lookup"] = interpolationFuncLookup(vs)
|
funcMap["lookup"] = interpolationFuncLookup(vs)
|
||||||
|
|
Loading…
Reference in New Issue