mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 09:48:32 -05:00
configgraph: Only ask ResourceInstanceGlue once for each value
Most of what we interact with in configgraph is other parts of the evaluator that automatically get memoized by patterns like OnceValuer, but the value for a resource instance is always provided by something outside of the evaluator that won't typically be able to use those mechanisms, and so the evaluator's ResourceInstance.Value implementation will now provide memoization on behalf of that external component, to ensure that we end up with only one value for each resource instance regardless of how that external component behaves. In the case of the current planning phase, in particular this means that we'll now only try to plan each resource instance once, whereas before we would ask it to make a separate plan for each call to Value. For now this is just retrofitted in an minimally-invasive way as part of our "walking skeleton" phase where we're just trying to wire the existing parts together end-to-end and then decide at the end whether we want to refactor things more. If this need for general-purpose memoization ends up appearing in other places too then maybe we'll choose to structure this a little differently. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"sync"
|
||||
|
||||
"github.com/apparentlymart/go-workgraph/workgraph"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
@@ -54,6 +55,19 @@ type ResourceInstance struct {
|
||||
// that arise dynamically during evaluation but whose results vary based
|
||||
// on concerns that our outside this package's scope.
|
||||
Glue ResourceInstanceGlue
|
||||
|
||||
// value memoizes the result from [ResourceInstance.Value] so that we'll
|
||||
// definitely return a consistent value to every call without re-running
|
||||
// whatever logic is behind the [ResourceInstance.Glue] implementation,
|
||||
// which might involve side-effects that could produce different results
|
||||
// on each call.
|
||||
//
|
||||
// Anything accessing value must hold valueLock.
|
||||
value struct {
|
||||
v cty.Value
|
||||
diags tfdiags.Diagnostics
|
||||
}
|
||||
valueLock sync.Mutex
|
||||
}
|
||||
|
||||
var _ exprs.Valuer = (*ResourceInstance)(nil)
|
||||
@@ -71,7 +85,25 @@ func (ri *ResourceInstance) StaticCheckTraversal(traversal hcl.Traversal) tfdiag
|
||||
}
|
||||
|
||||
// Value implements exprs.Valuer.
|
||||
func (ri *ResourceInstance) Value(ctx context.Context) (cty.Value, tfdiags.Diagnostics) {
|
||||
func (ri *ResourceInstance) Value(ctx context.Context) (v cty.Value, diags tfdiags.Diagnostics) {
|
||||
ri.valueLock.Lock()
|
||||
if ri.value.v != cty.NilVal {
|
||||
ri.valueLock.Unlock()
|
||||
// once ri.value.v is non-nil ri.value is never written again, so we can
|
||||
// safely access it without holding the lock here.
|
||||
return ri.value.v, ri.value.diags
|
||||
}
|
||||
defer func() {
|
||||
ri.value = struct {
|
||||
v cty.Value
|
||||
diags tfdiags.Diagnostics
|
||||
}{
|
||||
v: v,
|
||||
diags: diags,
|
||||
}
|
||||
ri.valueLock.Unlock()
|
||||
}()
|
||||
|
||||
// TODO: Preconditions? Or should that be handled in the parent [Resource]
|
||||
// before we even attempt instance expansion? (Need to check the current
|
||||
// behavior in the existing system, to see whether preconditions guard
|
||||
|
||||
Reference in New Issue
Block a user