Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug: values/secrets generation not running concurrently during 'diff' #810

Open
travisgroth opened this issue Aug 18, 2019 · 3 comments
Open

Comments

@travisgroth
Copy link
Contributor

I noticed that helmfile diff didn't appear to be decrypting secrets concurrently. Seems the mutex from #737 is effectively single-threading the call from higher in the stack:

helmfile/pkg/state/state.go

Lines 729 to 733 in a584aea

// TODO We need a long-term fix for this :)
// See https://github.com/roboll/helmfile/issues/737
mut.Lock()
flags, err := st.flagsForDiff(helm, release, workerIndex)
mut.Unlock()

I'm not totally clear on which map needs protecting, but after looking through the stack trace and code, I get the feeling it could be more granular. Happy to work through a PR to address the issue more directly, but I'm missing some context on where in the code the issue lies.

@mumoshu
Copy link
Collaborator

mumoshu commented Aug 19, 2019

@travisgroth Ah, good catch!

I remember that #737 was due to that concurrent go routines managed by scatterGather eventually calls HelmState.Values() to lazily build values that passed to the go template.
We needed exclusive read locking on st.Env.Defaults and st.Env.Values there to avoid the error concurrent map read and map write.

My initial attempt was to have a granular locking inside the HelmState.Values(): 41e44f7 which didn't work(I don't remember the cause though), resulted in #742 being adopted as the fix.

Perhaps we need to quit lazy builds of HelmState.Values. Maybe just sequentically call HelmState.Values for all the releases, and then do scatterGather?

@travisgroth
Copy link
Contributor Author

OK...I'm going down the same path. It looked like HelmState.Env, but I'm actually still not clear how we even wind up in this scenario. Mergo shouldn't be updating it. Multiple readers should be safe at that stage since we already have env constructed, I think?

Here's a race detector run with the mutex in question removed:

$ ~/workspace/helmfile-repo/helmfile -f helmfile.fail3.yaml -e test4 diff
==================
WARNING: DATA RACE
Read at 0x00c000369e30 by goroutine 63:
  runtime.mapaccess2()
      /opt/local/lib/go/src/runtime/map.go:455 +0x0
  reflect.mapaccess()
      /opt/local/lib/go/src/runtime/map.go:1321 +0x3e
  github.com/imdario/mergo.deepMerge()
      /Users/tgroth/workspace/go/pkg/mod/github.com/imdario/[email protected]/merge.go:93 +0x663
  github.com/imdario/mergo.deepMerge()
      /Users/tgroth/workspace/go/pkg/mod/github.com/imdario/[email protected]/merge.go:118 +0xc87
  github.com/imdario/mergo.merge()
      /Users/tgroth/workspace/go/pkg/mod/github.com/imdario/[email protected]/merge.go:254 +0x343
  github.com/roboll/helmfile/pkg/state.(*HelmState).Values()
      /Users/tgroth/workspace/go/pkg/mod/github.com/imdario/[email protected]/merge.go:209 +0x1d3
  github.com/roboll/helmfile/pkg/state.(*HelmState).valuesFileTemplateData()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state_exec_tmpl.go:33 +0x67
  github.com/roboll/helmfile/pkg/state.(*HelmState).RenderValuesFileToBytes()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:1306 +0x85
  github.com/roboll/helmfile/pkg/state.(*HelmState).generateTemporaryValuesFiles()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:1363 +0x3a2
  github.com/roboll/helmfile/pkg/state.(*HelmState).namespaceAndValuesFlags()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:1415 +0x2f7
  github.com/roboll/helmfile/pkg/state.(*HelmState).flagsForDiff()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:1275 +0x292
  github.com/roboll/helmfile/pkg/state.(*HelmState).prepareDiffReleases.func2()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:731 +0x8c5
  github.com/roboll/helmfile/pkg/state.(*HelmState).scatterGather.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state_run.go:40 +0x165

Previous write at 0x00c000369e30 by goroutine 62:
  runtime.mapassign()
      /opt/local/lib/go/src/runtime/map.go:576 +0x0
  reflect.mapassign()
      /opt/local/lib/go/src/runtime/map.go:1331 +0x3e
  github.com/imdario/mergo.deepMerge()
      /Users/tgroth/workspace/go/pkg/mod/github.com/imdario/[email protected]/merge.go:150 +0x8b3
  github.com/imdario/mergo.deepMerge()
      /Users/tgroth/workspace/go/pkg/mod/github.com/imdario/[email protected]/merge.go:118 +0xc87
  github.com/imdario/mergo.merge()
      /Users/tgroth/workspace/go/pkg/mod/github.com/imdario/[email protected]/merge.go:254 +0x343
  github.com/roboll/helmfile/pkg/state.(*HelmState).Values()
      /Users/tgroth/workspace/go/pkg/mod/github.com/imdario/[email protected]/merge.go:209 +0x1d3
  github.com/roboll/helmfile/pkg/state.(*HelmState).valuesFileTemplateData()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state_exec_tmpl.go:33 +0x67
  github.com/roboll/helmfile/pkg/state.(*HelmState).RenderValuesFileToBytes()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:1306 +0x85
  github.com/roboll/helmfile/pkg/state.(*HelmState).generateTemporaryValuesFiles()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:1363 +0x3a2
  github.com/roboll/helmfile/pkg/state.(*HelmState).namespaceAndValuesFlags()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:1415 +0x2f7
  github.com/roboll/helmfile/pkg/state.(*HelmState).flagsForDiff()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:1275 +0x292
  github.com/roboll/helmfile/pkg/state.(*HelmState).prepareDiffReleases.func2()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:731 +0x8c5
  github.com/roboll/helmfile/pkg/state.(*HelmState).scatterGather.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state_run.go:40 +0x165

Goroutine 63 (running) created at:
  github.com/roboll/helmfile/pkg/state.(*HelmState).scatterGather()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state_run.go:38 +0x25b
  github.com/roboll/helmfile/pkg/state.(*HelmState).prepareDiffReleases()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:713 +0x684
  github.com/roboll/helmfile/pkg/state.(*HelmState).DiffReleases()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:805 +0xbb
  github.com/roboll/helmfile/pkg/app.(*Run).Diff()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/run.go:226 +0x33e
  github.com/roboll/helmfile/pkg/app.(*App).Diff.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:109 +0x58
  github.com/roboll/helmfile/pkg/app.(*App).ForEachState.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:356 +0x127
  github.com/roboll/helmfile/pkg/app.(*App).VisitDesiredStatesWithReleasesFiltered.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:435 +0x6ce
  github.com/roboll/helmfile/pkg/app.(*App).visitStates.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:334 +0x9fb
  github.com/roboll/helmfile/pkg/app.(*App).visitStateFiles.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:221 +0x11a
  github.com/roboll/helmfile/pkg/app.(*App).within()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:163 +0x93a
  github.com/roboll/helmfile/pkg/app.(*App).visitStateFiles()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:215 +0x3b6
  github.com/roboll/helmfile/pkg/app.(*App).visitStates()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:257 +0x1d4
  github.com/roboll/helmfile/pkg/app.(*App).VisitDesiredStatesWithReleasesFiltered()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:405 +0x805
  github.com/roboll/helmfile/pkg/app.(*App).ForEachState()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:351 +0xd4
  github.com/roboll/helmfile/pkg/app.(*App).Diff()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:108 +0xa0
  main.main.func4()
      /Users/tgroth/workspace/helmfile-repo/main.go:187 +0x7e
  main.action.func1()
      /Users/tgroth/workspace/helmfile-repo/main.go:548 +0x1ae
  runtime.call32()
      /opt/local/lib/go/src/runtime/asm_amd64.s:519 +0x3a
  reflect.Value.Call()
      /opt/local/lib/go/src/reflect/value.go:308 +0xc0
  github.com/urfave/cli.HandleAction()
      /Users/tgroth/workspace/go/pkg/mod/github.com/urfave/[email protected]/app.go:483 +0x1ba
  github.com/urfave/cli.Command.Run()
      /Users/tgroth/workspace/go/pkg/mod/github.com/urfave/[email protected]/command.go:186 +0xbfe
  github.com/urfave/cli.(*App).Run()
      /Users/tgroth/workspace/go/pkg/mod/github.com/urfave/[email protected]/app.go:237 +0xa7f
  main.main()
      /Users/tgroth/workspace/helmfile-repo/main.go:397 +0x3602

Goroutine 62 (running) created at:
  github.com/roboll/helmfile/pkg/state.(*HelmState).scatterGather()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state_run.go:38 +0x25b
  github.com/roboll/helmfile/pkg/state.(*HelmState).prepareDiffReleases()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:713 +0x684
  github.com/roboll/helmfile/pkg/state.(*HelmState).DiffReleases()
      /Users/tgroth/workspace/helmfile-repo/pkg/state/state.go:805 +0xbb
  github.com/roboll/helmfile/pkg/app.(*Run).Diff()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/run.go:226 +0x33e
  github.com/roboll/helmfile/pkg/app.(*App).Diff.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:109 +0x58
  github.com/roboll/helmfile/pkg/app.(*App).ForEachState.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:356 +0x127
  github.com/roboll/helmfile/pkg/app.(*App).VisitDesiredStatesWithReleasesFiltered.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:435 +0x6ce
  github.com/roboll/helmfile/pkg/app.(*App).visitStates.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:334 +0x9fb
  github.com/roboll/helmfile/pkg/app.(*App).visitStateFiles.func1()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:221 +0x11a
  github.com/roboll/helmfile/pkg/app.(*App).within()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:163 +0x93a
  github.com/roboll/helmfile/pkg/app.(*App).visitStateFiles()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:215 +0x3b6
  github.com/roboll/helmfile/pkg/app.(*App).visitStates()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:257 +0x1d4
  github.com/roboll/helmfile/pkg/app.(*App).VisitDesiredStatesWithReleasesFiltered()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:405 +0x805
  github.com/roboll/helmfile/pkg/app.(*App).ForEachState()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:351 +0xd4
  github.com/roboll/helmfile/pkg/app.(*App).Diff()
      /Users/tgroth/workspace/helmfile-repo/pkg/app/app.go:108 +0xa0
  main.main.func4()
      /Users/tgroth/workspace/helmfile-repo/main.go:187 +0x7e
  main.action.func1()
      /Users/tgroth/workspace/helmfile-repo/main.go:548 +0x1ae
  runtime.call32()
      /opt/local/lib/go/src/runtime/asm_amd64.s:519 +0x3a
  reflect.Value.Call()
      /opt/local/lib/go/src/reflect/value.go:308 +0xc0
  github.com/urfave/cli.HandleAction()
      /Users/tgroth/workspace/go/pkg/mod/github.com/urfave/[email protected]/app.go:483 +0x1ba
  github.com/urfave/cli.Command.Run()
      /Users/tgroth/workspace/go/pkg/mod/github.com/urfave/[email protected]/command.go:186 +0xbfe
  github.com/urfave/cli.(*App).Run()
      /Users/tgroth/workspace/go/pkg/mod/github.com/urfave/[email protected]/app.go:237 +0xa7f
  main.main()
      /Users/tgroth/workspace/helmfile-repo/main.go:397 +0x3602
==================
==================
WARNING: DATA RACE
Read at 0x00c000369e30 by goroutine 61:
...

Changing the call to mergo.Merge() to use a deep copy of the HelmState.Env maps, removes the race condition:

	if err := mergo.Merge(&vals, st.Env.DeepCopy().Defaults, mergo.WithOverride); err != nil {
		return nil, err
	}
	if err := mergo.Merge(&vals, st.Env.DeepCopy().Values, mergo.WithOverride); err != nil {
		return nil, err
	}

To me, this looks as though merges of map sources are not concurrently safe in mergo. However, I can't reproduce with a contrived test of just HelmState.Values(). Any thoughts on what else might be at play here?

@travisgroth
Copy link
Contributor Author

Additional commentary - I think the Mutex inside Env didn't work because of some copying. Moving the mutex up to HelmState also eliminates the race:

func (st *HelmState) Values() (map[string]interface{}, error) {
	st.envMutex.Lock()
	defer st.envMutex.Unlock()
	vals := map[string]interface{}{}

	if err := mergo.Merge(&vals, st.Env.Defaults, mergo.WithOverride); err != nil {
		return nil, err
	}
	if err := mergo.Merge(&vals, st.Env.Values, mergo.WithOverride); err != nil {
		return nil, err
	}

	vals, err := maputil.CastKeysToStrings(vals)

	if err != nil {
		return nil, err
	}

	return vals, nil
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants