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

Add context.Context to deployment.Environment. #15410

Merged
merged 1 commit into from
Nov 27, 2024

Conversation

cgruber
Copy link
Collaborator

@cgruber cgruber commented Nov 25, 2024

We expose the context.Context via a lambda/pure-function on deployment.Environment. Mostly the ctx was already present where we create development.Environment (for other reasons), but now we expose it through the environment for attaching to remote-calls that aren't already managed by a wrapper client. Or, for whatever other things changesets may need to attach to the context for. (We are considering global timeouts for changset operations, for instance)

Plumb this through in the test environment and devenv as well in chainlink/deployments as well.

TODO: Apply a more robust cancellation signal handling, rather than dumbly plumbing through existing outer contexts (e.g. a cancellation method on Environment, and registering it where important). This will be done in a subsequent PR.

@cgruber cgruber requested review from a team as code owners November 25, 2024 21:46
Copy link
Contributor

github-actions bot commented Nov 25, 2024

AER Report: CI Core ran successfully ✅

aer_workflow , commit

AER Report: Operator UI CI ran successfully ✅

aer_workflow , commit

deployment/environment.go Outdated Show resolved Hide resolved
@jmank88
Copy link
Contributor

jmank88 commented Nov 25, 2024

We need to be careful with how context is wired through. We should be able to follow the package docs without exception:

Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx

A common problem that can make this seem difficult is having constructors that also do operations depending on the context. If the plain construction is kept separate from the operations that use the context, then it will be easy to wire it everywhere trivially (and easier to test, compose, etc).

@jmank88
Copy link
Contributor

jmank88 commented Nov 25, 2024

We should be able to follow the package docs without exception:

I said "should" but in a formal sense this is literally always possible, e.g. by passing a closure func(fn func(ctx.Context)) to invoke things with the context. This may seem like a hack outside of exceptions like channel communication, but wiring it up that way first often illuminates a better approach.

@cgruber cgruber force-pushed the chore/cgruber/add_context_to_env branch 2 times, most recently from 652236b to 8e687c5 Compare November 26, 2024 20:55
@cgruber cgruber requested a review from a team as a code owner November 26, 2024 20:55
@cgruber cgruber force-pushed the chore/cgruber/add_context_to_env branch from 8e687c5 to 49b0d84 Compare November 26, 2024 22:03
@cgruber cgruber requested review from a team as code owners November 26, 2024 22:03
@cgruber cgruber force-pushed the chore/cgruber/add_context_to_env branch from 49b0d84 to a493c54 Compare November 26, 2024 23:19
@smartcontractkit smartcontractkit deleted a comment from github-actions bot Nov 26, 2024
@cgruber cgruber enabled auto-merge November 26, 2024 23:43
…his through in the test environment and devenv also. Mostly the ctx was already present for other reasons, but now we expose it through the environment for attaching to remote-calls that aren't already managed by a wrapper client.
@cgruber cgruber force-pushed the chore/cgruber/add_context_to_env branch from a493c54 to 01a2108 Compare November 27, 2024 00:02
@cgruber cgruber requested a review from a team as a code owner November 27, 2024 00:02
@@ -100,7 +103,7 @@ func TestDeploy(t *testing.T) {
maps.Copy(allNodes, wfNodes)
maps.Copy(allNodes, cwNodes)
maps.Copy(allNodes, assetNodes)
env := memory.NewMemoryEnvironmentFromChainsNodes(t, lggr, allChains, allNodes)
env := memory.NewMemoryEnvironmentFromChainsNodes(func() context.Context { return ctx }, lggr, allChains, allNodes)
Copy link
Contributor

Choose a reason for hiding this comment

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

When these constructors are called normally, is there always an appropriate context on hand to pass in already? If not (or even if so?), this signature could be simplified by making a new context internally and returning (or providing a method to invoke) cancel().

Suggested change
env := memory.NewMemoryEnvironmentFromChainsNodes(func() context.Context { return ctx }, lggr, allChains, allNodes)
env, cancel := memory.NewMemoryEnvironmentFromChainsNodes(lggr, allChains, allNodes)
t.Cleanup(cancel)

This is also compatible with cases where there is a context already:

Suggested change
env := memory.NewMemoryEnvironmentFromChainsNodes(func() context.Context { return ctx }, lggr, allChains, allNodes)
env := memory.NewMemoryEnvironmentFromChainsNodes(lggr, allChains, allNodes)
context.AfterFunc(ctx, env.Cancel)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There is already a context in the other case - I plan to normalize them. I am trying to keep things somewhat stable. I have a lot of change to stack on this, and I'm trying to not change too much of the signatures in one PR. I want to do a pass through all of this. They were pulling a context off of testing.T internally, and this was intended to make them both work the same way, pending future fixes. I'm not trying to fix all the things in this PR.

This is a good pattern to keep in mind, however, and I'll look at it as an option for a followup.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also, that doesn't exactly simplify the signature, it just moves a parameter to a return type, so it's the same number of parameters (if you count return values as "out" params)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It does simplify having to construct a lambda, but I have a cleanup there planned as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please consider the method variant.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Per our discussion, I'll definitely look at the method variant, but for now, plumbing through the lambda is enough to unblock other work, and I'll make a point of revisiting this for a more mature signal handling once we're out of the crunch.

@@ -39,7 +39,7 @@ func NewEnvironment(ctx context.Context, lggr logger.Logger, config EnvironmentC
}
var nodeIDs []string
if jd.don != nil {
err = jd.don.CreateSupportedChains(ctx, config.Chains, *jd)
err = jd.don.CreateSupportedChains(ctx(), config.Chains, *jd)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is fine as-is, but declaring one ctx := ctx() at the top of the func can be less error prone over time.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, but this is a more complicated case where the context is being used in the constructor rather than a method. This is often problematic because it leaves the user unable to control the two separately. I think it would be appropriate to pass a context normally to these calls that use it synchronously. But OFC that would be messy alongside the func() context.Context, so probably only along with https://github.com/smartcontractkit/chainlink/pull/15410/files#r1859485133

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree in principle - my plan was to fix those constructors in a subsequent PR, where possible (or use the mentioned pattern). This was an enabling change for some other situations where contexts are being used, but naively in the middle of downstream code in this and chainlink-deployments, and I wanted the plumbing all the way through, to at least make sure we HAD cancellation, and had a way to avoid folks using weird lambdas (not like what you're suggesting, but as complete workarounds of the outer API, to plumb through things like the context, and certain secrets, etc.).

Now that I understand context better, I can do a cleanup of context more generally of code in /development and chainlink-deployments.

@cgruber cgruber added this pull request to the merge queue Nov 27, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Nov 27, 2024
@cgruber cgruber added this pull request to the merge queue Nov 27, 2024
Merged via the queue into develop with commit 90ee880 Nov 27, 2024
167 checks passed
@cgruber cgruber deleted the chore/cgruber/add_context_to_env branch November 27, 2024 22:24
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

Successfully merging this pull request may close these issues.

3 participants