Skip to content

Commit

Permalink
Fix custom component registry access (#6811)
Browse files Browse the repository at this point in the history
* lock before before evaluating to prevent using a not fully initialized custom component registry

* update changelog

* add test that was creating a panic
  • Loading branch information
wildum authored Apr 2, 2024
1 parent 787a094 commit 1570f97
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Main (unreleased)
whenever that argument is explicitly configured. This issue only affected a
small subset of arguments across 15 components. (@erikbaranowski, @rfratto)

- Fix a bug where a panic could occur when reloading custom components. (@wildum)

### Other changes

- Clustering for Grafana Agent in Flow mode has graduated from beta to stable.
Expand Down
33 changes: 17 additions & 16 deletions internal/flow/internal/controller/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,21 @@ type Loader struct {
// pool for evaluation in EvaluateDependants, because the queue is full. This is an unlikely scenario, but when
// it happens we should avoid retrying too often to give other goroutines a chance to progress. Having a backoff
// also prevents log spamming with errors.
backoffConfig backoff.Config
backoffConfig backoff.Config

mut sync.RWMutex
graph *dag.Graph
originalGraph *dag.Graph
componentNodes []ComponentNode
declareNodes map[string]*DeclareNode
importConfigNodes map[string]*ImportConfigNode
serviceNodes []*ServiceNode
cache *valueCache
blocks []*ast.BlockStmt // Most recently loaded blocks, used for writing
cm *controllerMetrics
cc *controllerCollector
moduleExportIndex int
componentNodeManager *ComponentNodeManager

mut sync.RWMutex
graph *dag.Graph
originalGraph *dag.Graph
componentNodes []ComponentNode
declareNodes map[string]*DeclareNode
importConfigNodes map[string]*ImportConfigNode
serviceNodes []*ServiceNode
cache *valueCache
blocks []*ast.BlockStmt // Most recently loaded blocks, used for writing
cm *controllerMetrics
cc *controllerCollector
moduleExportIndex int
}

// LoaderOptions holds options for creating a Loader.
Expand Down Expand Up @@ -787,10 +787,11 @@ func (l *Loader) concurrentEvalFn(n dag.Node, spanCtx context.Context, tracer tr
switch n := n.(type) {
case BlockNode:
ectx := l.cache.BuildContext()
evalErr := n.Evaluate(ectx)

// Only obtain loader lock after we have evaluated the node, allowing for concurrent evaluation.
// RLock before evaluate to prevent Evaluating while the config is being reloaded
l.mut.RLock()
evalErr := n.Evaluate(ectx)

err = l.postEvaluate(l.log, n, evalErr)

// Additional post-evaluation steps necessary for module exports.
Expand Down
65 changes: 65 additions & 0 deletions internal/flow/testdata/import_file/import_file_17.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
This is a simple test with several levels of nesting.
This test would often panic when re-evaluation could run concurrently with a config reload.
Now that it is protected with mutex, this test should always pass.

-- main.river --
import.file "a_namespace" {
filename = "module.river"
}

a_namespace.a "default" {
a_argument = 47
}

testcomponents.summation "sum" {
input = 10
}

-- module.river --
declare "a" {
import.file "b_namespace" {
filename = "nested_module.river"
}

argument "a_argument" {
comment = "Where to send collected metrics."
}

b_namespace.b "default" {
b_argument = 147
}

export "a_export" {
value = argument.a_argument.value + b_namespace.b.default.b_export + 1
}
}

-- nested_module.river --
declare "b" {
import.file "c_namespace" {
filename = "other_nested_module.river"
}

argument "b_argument" {
optional = false
}

c_namespace.c "default" {
c_argument = 101
}

export "b_export" {
value = argument.b_argument.value + c_namespace.c.default.c_export + 1
}
}

-- other_nested_module.river --
declare "c" {
argument "c_argument" {
optional = false
}

export "c_export" {
value = argument.c_argument.value + 1
}
}

0 comments on commit 1570f97

Please sign in to comment.