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

deferred actions: load all values from deferred resources when building eval context #35526

Merged
merged 2 commits into from
Aug 5, 2024

Conversation

liamcervante
Copy link
Member

This PR fixes a bug in deferred actions when they are referenced but haven't been added to state. Reasons why a deferred action might not be in state are:

  • It's a data source
  • It's creating a resource for the first time

Essentially, the evaluate functionality for resources looks at the state to work out the base set of values. These values were then overridden by values from the deferrals or from the plan when appropriate. But, this means that if a deferred resource wasn't already in state (eg. it's just being created) then no values would be added to the context for it. Now, we precompute the set of available instances by looking at the deferrals and state in isolation. We add all the values that are being deferred first, and then add values from the state/plan only if they haven't already been added. This ensures that even if there is nothing in the state for a deferred resource then we'll still capture values for it in the context.

This means that deferred actions are actually propagating their data properly for the first time, and this exposed a missing feature in outputs that reference deferred actions. These are supposed to be returned as null, but this was only happening because we weren't properly referencing deferred resources and the outputs were naturally returning unknown as a result. Now, the output node consults the deferred actions to check if any of the nodes it references were deferred, and replaces the output value with null properly.

To facilitate this, I changed the interface for GraphNodeAttachDependencies so that it can also be implemented by the output nodes. The functions for the plan and apply nodes must now manually remove themselves, but the same functionality can be shared for working out if an output node or a plan/apply node should be deferred because of dependencies.

@liamcervante liamcervante requested a review from a team August 1, 2024 11:29
@@ -73,7 +73,14 @@ func (n *nodeExpandPlannableResource) expandsInstances() {

// GraphNodeAttachDependencies
func (n *nodeExpandPlannableResource) AttachDependencies(deps []addrs.ConfigResource) {
n.dependencies = deps
n.dependencies = make([]addrs.ConfigResource, 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While normally not an optimization that would matter in core, I would initialize this slice with the capacity of len(deps). This dependency handling is already a hot-spot in the code because large configs can add a lot of entries on a lot of nodes, so preventing the extra allocations might help at least a little.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up pushing the check back into the AttachDependencies via checking for if the source is a GraphNodeConfigResource directly. This avoids the whole problem, so seemed like the best thing to do.

AttachDependencies([]addrs.ConfigResource)
Name() string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is only for logging, I would just use dag.VertexName instead. It does the same thing, but we don't actually enforce Name() being defined and I don't want something to slip past this interface someday because it's missing that method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

// First, we're going to load any instances that we have written into the
// deferrals system. A deferred resource overrides anything that might be
// in the state for the resource, so we do this first.
for key, value := range d.Evaluator.Deferrals.GetDeferredResourceInstances(addr.Absolute(d.ModulePath)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it makes sense to eventually reconcile the difference between planned and deferred instances. Planned instances historically get an empty record in state so iterating on rs.Instances works for them, but deferred instances (which are still sort of like a planned instance) do not. Not suggesting any changes here, just something to think about when we encounter this inconsistency again ;)

Copy link
Member

@nfagerlund nfagerlund left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll defer to James on the performance and interface comments, but in terms of the main logic and the deferral lifecycle, this looks correct to me. Thank you for catching this!

Comment on lines +798 to +801
if deferred.DependenciesDeferred(n.Dependencies) {
// If the output is from deferred resources then we return a
// simple null value representing that the value is really
// unknown as the dependencies were not properly computed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah -- nice. I think this is more coherent.

@liamcervante liamcervante merged commit 70b93b2 into main Aug 5, 2024
6 checks passed
@liamcervante liamcervante deleted the liamcervante/deferred-actions/tf-19073 branch August 5, 2024 11:07
Copy link
Contributor

github-actions bot commented Aug 5, 2024

Reminder for the merging maintainer: if this is a user-visible change, please update the changelog on the appropriate release branch.

Copy link
Contributor

github-actions bot commented Sep 6, 2024

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 6, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants