-
Notifications
You must be signed in to change notification settings - Fork 58
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
feat(plan): use lanes to start services with dependencies #437
feat(plan): use lanes to start services with dependencies #437
Conversation
Not in the scope of this PR (or the issue #231 we are trying to fix), but I'd like to point out the painful experience of using requires/before/after when implementing and testing this feature. This PR is my second one on this feature because, in the first draft, I made the mistake of assuming that "when handling task B, if B depends on task A, A should have already been processed, since the input is sorted already, so, simply put B in the lane where A already is." I ignored the possibility that you can configure A to require B but A before B, for example, the test case 3 mentioned above: services:
a:
override: replace
command: sh -c "sleep 999"
startup: enabled
requires:
- b
before:
- b
b:
override: replace
command: sh -c "sleep 999"
startup: enabled The logic in my first draft won't work, because the sorted order is (A, B), so when handling A, B's lane isn't known yet; and when handling B, since B doesn't require anything, you don't know which lane to put it in. To solve this problem, I created the second solution in this PR, where if A's dependency isn't known yet, create a lane, and put all dependencies in the same lane even before the task is created. However, after giving this test case more thought, I concluded that the test case makes no sense, because "A requires B, but A must be started before B" is illogical:
So, saying that "A requires B" and "A must start before B" is illogical, the two parts counter each other. I tried but failed to think of any use case where "A requires B", but "A must be started before B". Some analogies:
Using three keywords requires/before/after to handle dependency is an overkill, which brings more problems than solves. I think we can drop "before/after", and rename "requires" to something like "depend-on" (depend-on means requires + after, in my book). I prefer renaming "requires" to "depends-on" because "requires" in many cases isn't right:
If we drop "before/after" and leave only "depends-on", the layer in test case 3 can be fixed to: services:
a:
override: replace
command: /bin/sh -c "echo test1; sleep 10"
startup: enabled
b:
override: replace
command: /bin/sh -c "echo test2; sleep 10"
depends-on:
- a And this is more than enough to solve any dependency requirements. Proof: Terraform does the same thing, even the name "depends-on" is borrowed from Terraform. If we can do this, after sorting, the order is (A, B) and when handling B, A's lane is already decided. In this case, the feature in this PR can be much simpler:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's discuss this when you're back. But per my comment, I think this would fit better in plan.StartOrder
(and have that return a list of independent lanes).
I think you're right -- it's overkill. And I and others have been confused by requires/before/after several times. There's more in the README here. "Requires" basically means depends-on, but doesn't specify any order of starting. "Before" and "after" deal with the ordering.
That said, I think that ship has sailed -- in other words, it's too late to change it now. We can probably refine before/after/requires, but not remove or change them significantly. Let's chat about all this further when you're back, and then you can do a second iteration. |
Refactored according to the above suggestion. Key changes:
Some explanation:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This work looks good overall! Just requesting some minor changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this! Looking good now, and I've done some basic manual testing too.
#17991 Per discussion with @hpidcock, update Juju 3.6 to use the just-released Pebble v1.16.0 ([changelog](https://github.com/canonical/pebble/releases/tag/v1.16.0)). Summary of main changes: * Improvements to how services with dependencies are started and stopped (using "lanes"), so that independent services are started in parallel, and dependent services start up serially. Error handling is also improved. Fix in canonical/pebble#437. * A fix for a tricky bug in pebble exec, so it doesn't lose output in interactive mode. Fix in canonical/pebble#466! prdesc Reduce the size of exec tasks by not storing the execSetup (which includes all environment variables) in state. This speeds up (and shrinks the data) when serialising state to disk during state.Unlock. Fix is in canonical/pebble#478. prdesc Ensure Pebble doesn't hang (with no error message) when the state file directory is read-only or otherwise inaccessible. Fix in canonical/pebble#481. * Re-implement warnings using notices. This was always the intention since the notices feature was added (it was designed as a superset of warnings), but we'd never gotten to it. Lots of nice code deletion in canonical/pebble#486. ## QA steps Deploy a K8s charm, like `snappass-test`, and ensure `pebble version --client` on the workload reports v1.16.0.
When starting services, put services that depend on each other (before/after/requires) in the same lane. If there is no dependency, put it in a stand-alone lane. Tasks only wait for tasks defined in the same lane.
Fixes #231.
Logic
The logic is as follows:
Tests
I mainly ran three types of test. The first is the one mentioned in #231:
With this feature, service C won't be in "hold" status, but rather, started. Logs:
As a second test, if C depends on B, C is still in "hold" because they are in the same lane:
Logs:
As a third test, I tested the "requires + before" combination:
Logs: