Wait for availability of remote services before starting an application
When developing Swift services locally (macOS), I usually use Docker to run one or more databases. When quickly wanting to test on Linux, I use docker-compose to start both the database and my Swift service at the same time.
When doing so I'm running into the issue of the database container being booted quickly but not actually being available. If the Swift service is not set up to retry connecting to the database it might crash when the connection can't yet be established. To work around this I often use something like wait-for-it, which is a simple bash script that you add to the Dockerfile of the Swift service and use it as the entry point, before actually specifying your application's real entry point. The script then pings a specified port as long as it's not available every few seconds.
Long story short, I'm thinking there may be an opportunity for ServiceLifecycle to tackle this issue without including such a script that lets you wait for a specific port to be available before starting certain tasks such as starting the main app or running migrations. I didn't look into the source code deep enough to figure out how this might work but just wanted to get the idea out there so we can discuss it together.
Agreed that this can be a nice addition 👍
I think it boils down to allowing retries of start tasks with a specific backoff strategy, e.g.
lifecycle.register(
label: "migrator",
start: .async(migrator.migrate, retry: .times(3)), // retry aggressively, without backoff
shutdown: .async(migrator.shutdown)
)
lifecycle.register(
label: "migrator",
start: .async(migrator.migrate, retry: .withBackoff(times: 3, initialDelay: .seconds(1), max: ..., more config here)),
shutdown: .async(migrator.shutdown)
)
The start task would be retried as many times as configured, so it has to be able to survive that, but for most systems this is the "init" so it should be possible to work well.
We can accumulate and log failures if needed.
An observation is that this would be a bit interesting interaction in context of #49 - where you have multiple possible dependents on a task which might have specified different backoff strategies.
I think this can be closed now with the new APIs. If you want to wait for a service to start you can just inject the service into other services and the service should expose an async sequence of its state. This allows the other services to just wait for the state to transition to running and do their work.
This package specifically does not set out that services have to declare their state nor an enum of possible states since this can all be composed higher up. @slashmo Can we close this?
@FranzBusch Yep, let's close it. The approach you describe sounds good to me 👍