Repository: gruntwork-io/terragrunt (language: go)

Issue:
fatal error: concurrent map writes
## Describe the bug

cli/commands/terraform/version_check.go:87

```go
terragruntOptions.FeatureFlags[flagName] = defaultValue
```

```
fatal error: concurrent map writes

goroutine 9478 [running]:
github.com/gruntwork-io/terragrunt/cli/commands/terraform.checkVersionConstraints({0x2ed76f8, 0xc0058c9dd0}, 0xc009763b08)
	/home/circleci/project/cli/commands/terraform/version_check.go:87 +0x315
github.com/gruntwork-io/terragrunt/cli/commands/terraform.runTerraform({0x2ed76f8, 0xc0058c9dd0}, 0xc009763b08, 0xc006da9890)
	/home/circleci/project/cli/commands/terraform/action.go:97 +0x146
github.com/gruntwork-io/terragrunt/cli/commands/terraform.Run({0x2ed76f8?, 0xc0058c9dd0?}, 0x5?)
	/home/circleci/project/cli/commands/terraform/action.go:83 +0x52
github.com/gruntwork-io/terragrunt/cli/commands/run-all.NewCommand.action.func1.1({0x2ed76f8, 0xc0058c9dd0}, 0xc009763b08)
	/home/circleci/project/cli/commands/run-all/command.go:60 +0x9f
github.com/gruntwork-io/terragrunt/configstack.(*RunningModule).runTerragrunt(0xc006ca2870, {0x2ed76f8, 0xc0058c9dd0}, 0xc009763b08)
	/home/circleci/project/configstack/running_module.go:119 +0x1db
github.com/gruntwork-io/terragrunt/configstack.(*RunningModule).runNow(0xc006ca2870, {0x2ed76f8, 0xc0058c9dd0}, 0xc004198008)
	/home/circleci/project/configstack/running_module.go:130 +0xfc
github.com/gruntwork-io/terragrunt/configstack.(*RunningModule).runModuleWhenReady.func3({0x0?, 0x0?})
	/home/circleci/project/configstack/running_module.go:82 +0x25
github.com/gruntwork-io/terragrunt/telemetry.Time({0x2ed76f8?, 0xc0058c9dd0?}, {0x27bceda?, 0x0?}, 0xc00191bc00?, 0xc002703bd8?)
	/home/circleci/project/telemetry/metrics.go:42 +0x293
github.com/gruntwork-io/terragrunt/configstack.(*RunningModule).runModuleWhenReady.Telemetry.func5({0x2ed76f8?, 0xc0058c9dd0?})
	/home/circleci/project/telemetry/telemetry.go:80 +0x2d
github.com/gruntwork-io/terragrunt/telemetry.Trace({0x2ed76f8?, 0xc0058c9dd0?}, {0x27bceda?, 0x10?}, 0x0?, 0x0?)
	/home/circleci/project/telemetry/traces.go:38 +0x14f
github.com/gruntwork-io/terragrunt/telemetry.Telemetry(...)
	/home/circleci/project/telemetry/telemetry.go:79
github.com/gruntwork-io/terragrunt/configstack.(*RunningModule).runModuleWhenReady(0xc006ca2870, {0x2ed76f8, 0xc0058c9dd0}, 0xc004198008, 0xc00a77f030)
	/home/circleci/project/configstack/running_module.go:78 +0x4ad
github.com/gruntwork-io/terragrunt/configstack.RunningModules.runModules.func1(0xc00228f2b0?)
	/home/circleci/project/configstack/running_module.go:322 +0x53
created by github.com/gruntwork-io/terragrunt/configstack.RunningModules.runModules in goroutine 9477
	/home/circleci/project/configstack/running_module.go:319 +0xbb
```

## Versions

- Terragrunt version: [v0.71.1](https://github.com/gruntwork-io/terragrunt/releases/tag/v0.71.1)

## Additional context

[Commit](https://github.com/gruntwork-io/terragrunt/commit/959c17a026c41f44b4cff6e7099635879044ab2e)

[Circleci crash log
](https://circleci.com/api/v1.1/project/github/gruntwork-io/terragrunt/67688/output/105/0?file=true&allocation-id=676efdfd8347564d0b42b378-0-build%2FABCDEFGH)



Discussion / hints:

Fix patch:
diff --git a/cli/commands/flags.go b/cli/commands/flags.go
index ff5ed06ab3..77b60aca43 100644
--- a/cli/commands/flags.go
+++ b/cli/commands/flags.go
@@ -610,11 +610,17 @@ func NewGlobalFlags(opts *options.TerragruntOptions) cli.Flags {
 			Usage:       "The command and arguments that can be used to fetch authentication configurations.",
 		},
 		&cli.MapFlag[string, string]{
-			Name:        TerragruntFeatureMapFlagName,
-			EnvVar:      TerragruntFeatureMapEnvName,
-			Destination: &opts.FeatureFlags,
-			Usage:       "Set feature flags for the HCL code.",
-			Splitter:    util.SplitComma,
+			Name:     TerragruntFeatureMapFlagName,
+			EnvVar:   TerragruntFeatureMapEnvName,
+			Usage:    "Set feature flags for the HCL code.",
+			Splitter: util.SplitComma,
+			Action: func(_ *cli.Context, value map[string]string) error {
+				for key, val := range value {
+					opts.FeatureFlags.Store(key, val)
+				}
+
+				return nil
+			},
 		},
 		// Terragrunt engine flags
 		&cli.BoolFlag{
diff --git a/cli/commands/terraform/version_check.go b/cli/commands/terraform/version_check.go
index 97080300d9..ea6aa0e4da 100644
--- a/cli/commands/terraform/version_check.go
+++ b/cli/commands/terraform/version_check.go
@@ -83,8 +83,8 @@ func checkVersionConstraints(ctx context.Context, terragruntOptions *options.Ter
 				return err
 			}
 
-			if _, exists := terragruntOptions.FeatureFlags[flagName]; !exists {
-				terragruntOptions.FeatureFlags[flagName] = defaultValue
+			if _, exists := terragruntOptions.FeatureFlags.Load(flagName); !exists {
+				terragruntOptions.FeatureFlags.Store(flagName, defaultValue)
 			}
 		}
 	}
diff --git a/config/config_partial.go b/config/config_partial.go
index dc834611d3..996b135f1b 100644
--- a/config/config_partial.go
+++ b/config/config_partial.go
@@ -187,36 +187,15 @@ func DecodeBaseBlocks(ctx *ParsingContext, file *hclparse.File, includeFromChild
 }
 
 func flagsAsCty(ctx *ParsingContext, tgFlags FeatureFlags) (cty.Value, error) {
-	evaluatedFlags := map[string]cty.Value{}
 	// extract all flags in map by name
 	flagByName := map[string]*FeatureFlag{}
 	for _, flag := range tgFlags {
 		flagByName[flag.Name] = flag
 	}
 
-	for name, value := range ctx.TerragruntOptions.FeatureFlags {
-		// convert flag value to respective type
-		var evaluatedFlag cty.Value
-
-		existingFlag, exists := flagByName[name]
-
-		if exists {
-			flag, err := flagToTypedCtyValue(name, existingFlag.Default.Type(), value)
-			if err != nil {
-				return cty.NilVal, err
-			}
-
-			evaluatedFlag = flag
-		} else {
-			flag, err := flagToCtyValue(name, value)
-			if err != nil {
-				return cty.NilVal, err
-			}
-
-			evaluatedFlag = flag
-		}
-
-		evaluatedFlags[name] = evaluatedFlag
+	evaluatedFlags, err := cliFlagsToCty(ctx, flagByName)
+	if err != nil {
+		return cty.NilVal, err
 	}
 
 	for _, flag := range tgFlags {
@@ -240,6 +219,46 @@ func flagsAsCty(ctx *ParsingContext, tgFlags FeatureFlags) (cty.Value, error) {
 	return flagsAsCtyVal, nil
 }
 
+// cliFlagsToCty converts CLI feature flags to Cty values. It returns a map of flag names
+// to their corresponding Cty values and any error encountered during conversion.
+func cliFlagsToCty(ctx *ParsingContext, flagByName map[string]*FeatureFlag) (map[string]cty.Value, error) {
+	if ctx.TerragruntOptions.FeatureFlags == nil {
+		return make(map[string]cty.Value), nil
+	}
+
+	evaluatedFlags := make(map[string]cty.Value)
+
+	var conversionErr error
+
+	ctx.TerragruntOptions.FeatureFlags.Range(func(name, value string) bool {
+		var flag cty.Value
+
+		var err error
+
+		if existingFlag, ok := flagByName[name]; ok {
+			flag, err = flagToTypedCtyValue(name, existingFlag.Default.Type(), value)
+		} else {
+			flag, err = flagToCtyValue(name, value)
+		}
+
+		if err != nil {
+			conversionErr = err
+
+			return false
+		}
+
+		evaluatedFlags[name] = flag
+
+		return true
+	})
+
+	if conversionErr != nil {
+		return nil, conversionErr
+	}
+
+	return evaluatedFlags, nil
+}
+
 func PartialParseConfigFile(ctx *ParsingContext, configPath string, include *IncludeConfig) (*TerragruntConfig, error) {
 	hclCache := cache.ContextCache[*hclparse.File](ctx, HclCacheContextKey)
 
diff --git a/options/options.go b/options/options.go
index 99924fb7dd..7d99ece67e 100644
--- a/options/options.go
+++ b/options/options.go
@@ -377,8 +377,8 @@ type TerragruntOptions struct {
 	// Experiments is a map of experiments, and their status.
 	Experiments experiment.Experiments
 
-	// FeatureFlags is a map of feature flags to enable.
-	FeatureFlags map[string]string
+	// ]FeatureFlags is a map of feature flags to enable.
+	FeatureFlags *xsync.MapOf[string, string]
 
 	// ReadFiles is a map of files to the Units
 	// that read them using HCL functions in the unit.
@@ -512,7 +512,7 @@ func NewTerragruntOptionsWithWriters(stdout, stderr io.Writer) *TerragruntOption
 		ProviderCacheRegistryNames: defaultProviderCacheRegistryNames,
 		OutputFolder:               "",
 		JSONOutputFolder:           "",
-		FeatureFlags:               map[string]string{},
+		FeatureFlags:               xsync.NewMapOf[string, string](),
 		ReadFiles:                  xsync.NewMapOf[string, []string](),
 		ExperimentMode:             false,
 		Experiments:                experiment.NewExperiments(),


Test patch: