Wait for Stopping services to stop before target apply success

This mitigates an edge case bug introduced in v13.1.3 where services that
are slow to exit may get stuck in a state of Downloaded if a service var is
changed then reverted rapidly. More detailed description in linked issue.

Change-type: patch
Closes: #1991
Signed-off-by: Christina Wang <christina@balena.io>
This commit is contained in:
Christina Wang 2022-08-02 14:34:25 -07:00
parent 936ada7f64
commit 12b67742c8
2 changed files with 31 additions and 1 deletions

View File

@ -360,6 +360,18 @@ export class App {
);
};
/**
* Checks if Supervisor should keep the state loop alive while waiting on a service to stop
* @param serviceCurrent
* @param serviceTarget
*/
const shouldWaitForStop = (serviceCurrent: Service) => {
return (
serviceCurrent.config.running === true &&
serviceCurrent.status === 'Stopping'
);
};
/**
* Filter all the services which should be updated due to run state change, or config mismatch.
*/
@ -372,7 +384,8 @@ export class App {
({ current: c, target: t }) =>
!isEqualExceptForRunningState(c, t) ||
shouldBeStarted(c, t) ||
shouldBeStopped(c, t),
shouldBeStopped(c, t) ||
shouldWaitForStop(c),
);
return {

View File

@ -657,6 +657,23 @@ describe('compose/app', () => {
expectNoStep('kill', steps);
});
it('should emit a noop while waiting on a stopping service', async () => {
const current = createApp({
services: [
await createService(
{ serviceName: 'main', running: true },
{ state: { status: 'Stopping' } },
),
],
});
const target = createApp({
services: [await createService({ serviceName: 'main', running: true })],
});
const steps = current.nextStepsForAppUpdate(defaultContext, target);
expectSteps('noop', steps);
});
it('should remove a dead container that is still referenced in the target state', async () => {
const current = createApp({
services: [