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

A way to know that all OnStart hooks have completed #1245

Open
Southclaws opened this issue Nov 9, 2024 · 1 comment
Open

A way to know that all OnStart hooks have completed #1245

Southclaws opened this issue Nov 9, 2024 · 1 comment

Comments

@Southclaws
Copy link

This may be a bit of a hack due to my usage but it felt useful enough to bring up (maybe a way to do this exists already)

I've been looking for a way to execute some code after every OnStart hook has finished, or at least after a specific hook I know has run to completion. I thought that hooks were executed in order of dependency, so if A depends on B, then B's lifecycle hooks will run first then A's - but I'm not sure about this assumption, it doesn't appear to be the case possibly due to parallelism?

Describe the solution you'd like

I'm not entirely sure, an additional hook doesn't feel like the right solution but perhaps a channel on App similar to Done()

Describe alternatives you've considered

I could create my own signal channel on the hooks I'm interested in, which would work for my use-case as it's only a couple.

Is this a breaking change?
Not as far as I can see, it would merely be exposing something I assume fx is already aware of - but maybe it isn't if hooks are paralellised. Though I assumed it would be aware given the error handling and discussion in #1059

Additional context

This came about due to how I'm using fx in integration tests, and the way our app initialises external connections (database, message queue) in start hooks. The database is connected to inside a Provide() but the schema is migrated inside a start hook. This means tests must always run inside a start hook too, otherwise the database is empty. (example here) which works fine, as I assume the hooks run in order of dependency. Though I ran into an issue while writing tests involving my message queue, which operates in a similar way: connection is set up in a Provide() and then queues are prepared during start hooks, but even though the pubsub provider is a very shallow dependency and appears high up in the dependency tree of tests, its start hook runs after the test's start hook so the tests fail due to there being no queues available. I think this is due to the pubsub start hook running after the test itself and I couldn't really figure out why. (Perhaps my issue lies in how I've set it up and this issue is just a wild goose chase in the wrong direction)

Either way, I do feel like a "Run this code after all start hooks have completed" would be a useful addition, if not already available. Thanks!

@abhinav
Copy link
Collaborator

abhinav commented Nov 9, 2024

Hello!

I thought that hooks were executed in order of dependency, so if A depends on B, then B's lifecycle hooks will run first then A's - but I'm not sure about this assumption, it doesn't appear to be the case possibly due to parallelism?

That assumption is correct. There's no in hook execution. The hooks may spawn goroutines, but the hooks themselves run serially. General advice here is that the blocking part of the OnStart do any short-lived setup (open a connection, start a listener, etc.), and then spawn a goroutine to do any long-lived background work (sending requests on the connection, serving HTTP requests, etc.).

Hooks are executed in dependency order:
OnStart hooks added from fx.Provide(A) will run before OnStart hooks added in fx.Provide(B) if B depends on A. OnStop hooks run in the reverse order.
Hook ordering is undefined if A and B do not have a dependency relation.

execute some code after every OnStart hook has finished

I haven't thought this through fully, but does a hook like that introduce a "What about level N+1?" question?
If there's, say, an "OnStarted" hook, and a bunch of fx.Provides contribute to that, will there eventually be need for running code after all of those hooks have also finished running?

One way you to go about this is without an Fx-level feature: don't use Run and call Start and Stop manually:

app := fx.New(...)

if err := app.Start(ctx); err != nil {
   ...
}

codeThatShouldRunAfterAllHooks()

<-app.Wait()

if err := app.Stop(ctx); err != nil {
  ...
}

If the codeThatShouldRunAfterAllHooks needs access to anything from the Fx container, you can leverage fx.Populate to request those values in fx.New.

Separately, regarding this:

the pubsub provider is a very shallow dependency and appears high up in the dependency tree of tests, its start hook runs after the test's start hook so the tests fail due to there being no queues available. I think this is due to the pubsub start hook running after the test itself and I couldn't really figure out why. (Perhaps my issue lies in how I've set it up and this issue is just a wild goose chase in the wrong direction)

I haven't looked at the code, so just guessing here:
Are you perhaps scheduling the OnStart for queue setup in an fx.Invoke separate from the fx.Provide that sets up the pubsub client used by the test? If so, there might not be a dependency relation between the two.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants