rollbridge 0.1.40 → 0.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/docs/config.md +4 -3
- package/examples/tensorbuzz.com.js +5 -2
- package/package.json +1 -1
- package/src/cli.js +25 -0
- package/src/config.js +32 -5
- package/src/daemon.js +180 -3
- package/src/guardian-client.js +55 -2
- package/src/managed-process.js +71 -11
- package/src/process-guardian.js +4 -1
- package/src/release-group.js +22 -0
- package/test/completion.test.js +4 -2
- package/test/config-examples.test.js +1 -0
- package/test/config-validation.test.js +23 -3
- package/test/guardian-client.test.js +82 -1
- package/test/managed-process.test.js +29 -2
- package/test/owner-recovery.test.js +11 -18
- package/test/owner-replacement.test.js +13 -19
- package/test/rollbridge.test.js +242 -13
package/test/rollbridge.test.js
CHANGED
|
@@ -660,25 +660,248 @@ test("retirement failure retains the exact transition, blocks other deploys, and
|
|
|
660
660
|
}
|
|
661
661
|
})
|
|
662
662
|
|
|
663
|
-
test("candidate activation failure
|
|
663
|
+
test("candidate activation failure reports restoration failure and exact recovery clears the fence", async () => {
|
|
664
664
|
const fixture = await createFixture({handoffService: true, handoffServiceActivate: true, handoffServiceActivateFailure: true, nonBlockingDrainWorker: true, webDependsOnService: true})
|
|
665
665
|
const daemon = await startDaemon(fixture.config)
|
|
666
666
|
|
|
667
667
|
try {
|
|
668
668
|
await daemon.deploy({releaseId: "v1", releasePath: fixture.root, revision: "v1"})
|
|
669
|
-
|
|
669
|
+
const incumbentCoordinator = daemon.releases.get("v1")?.getProcess("beacon")
|
|
670
|
+
const reactivate = incumbentCoordinator?.reactivateStrict.bind(incumbentCoordinator)
|
|
671
|
+
|
|
672
|
+
assert.ok(incumbentCoordinator && reactivate)
|
|
673
|
+
incumbentCoordinator.reactivateStrict = async () => { throw new Error("incumbent restoration rejected") }
|
|
674
|
+
await assert.rejects(
|
|
675
|
+
() => daemon.deploy({releaseId: "v2", releasePath: fixture.root, revision: "v2"}),
|
|
676
|
+
error => {
|
|
677
|
+
assert.ok(error instanceof AggregateError)
|
|
678
|
+
assert.match(error.message, /activate command exited non-zero/)
|
|
679
|
+
assert.match(error.message, /incumbent v1 restoration failed: incumbent restoration rejected/i)
|
|
680
|
+
return true
|
|
681
|
+
}
|
|
682
|
+
)
|
|
670
683
|
|
|
671
684
|
const failed = daemon.status()
|
|
672
685
|
|
|
673
686
|
assert.equal(failed.activeReleaseId, "v1")
|
|
674
|
-
assert.equal(failed.generationTransition?.phase, "
|
|
675
|
-
assert.
|
|
687
|
+
assert.equal(failed.generationTransition?.phase, "restoring_previous")
|
|
688
|
+
assert.match(String(failed.generationTransition?.activationError), /activate command exited non-zero/)
|
|
689
|
+
assert.match(String(failed.generationTransition?.compensationError), /incumbent restoration rejected/)
|
|
690
|
+
const failedEvents = daemon.eventLog.recent()
|
|
691
|
+
const activationEvent = failedEvents.find((event) => event.message === "release generation activation failed")
|
|
692
|
+
const restorationEvent = failedEvents.find((event) => event.message === "release generation compensation restoration failed")
|
|
693
|
+
|
|
694
|
+
assert.match(String(activationEvent?.data.error), /activate command exited non-zero/)
|
|
695
|
+
assert.match(String(restorationEvent?.data.activationError), /activate command exited non-zero/)
|
|
696
|
+
assert.match(String(restorationEvent?.data.error), /incumbent restoration rejected/)
|
|
697
|
+
assert.deepEqual(await lifecycleEvents(fixture.lifecycleLogPath), ["activate:v1", "retire:v1", "retire:v2"])
|
|
698
|
+
await assert.rejects(() => daemon.deploy({releaseId: "v3", releasePath: fixture.root, revision: "v3"}), /transition.*v2.*unresolved/i)
|
|
676
699
|
|
|
677
|
-
|
|
678
|
-
await
|
|
700
|
+
incumbentCoordinator.reactivateStrict = reactivate
|
|
701
|
+
const recovery = await sendControlCommand({
|
|
702
|
+
command: {
|
|
703
|
+
command: "recover-generation-transition",
|
|
704
|
+
previousReleaseId: "v1",
|
|
705
|
+
releaseId: "v2",
|
|
706
|
+
releasePath: fixture.root,
|
|
707
|
+
revision: "v2"
|
|
708
|
+
},
|
|
709
|
+
path: fixture.config.control.path
|
|
710
|
+
})
|
|
679
711
|
|
|
680
|
-
assert.equal(
|
|
681
|
-
assert.
|
|
712
|
+
assert.equal(recovery.recoveryStatus, "recovered")
|
|
713
|
+
assert.equal(daemon.status().activeReleaseId, "v1")
|
|
714
|
+
assert.equal(daemon.status().generationTransition, undefined)
|
|
715
|
+
const persisted = /** @type {{generationTransition?: import("../src/json.js").JsonValue} | undefined} */ (await readState(fixture.statePath))
|
|
716
|
+
|
|
717
|
+
assert.equal(persisted?.generationTransition, undefined)
|
|
718
|
+
assert.deepEqual(await lifecycleEvents(fixture.lifecycleLogPath), ["activate:v1", "retire:v1", "retire:v2", "activate:v1"])
|
|
719
|
+
|
|
720
|
+
const idempotent = await sendControlCommand({
|
|
721
|
+
command: {
|
|
722
|
+
command: "recover-generation-transition",
|
|
723
|
+
previousReleaseId: "v1",
|
|
724
|
+
releaseId: "v2",
|
|
725
|
+
releasePath: fixture.root,
|
|
726
|
+
revision: "v2"
|
|
727
|
+
},
|
|
728
|
+
path: fixture.config.control.path
|
|
729
|
+
})
|
|
730
|
+
|
|
731
|
+
assert.equal(idempotent.recoveryStatus, "already_recovered")
|
|
732
|
+
await daemon.deploy({releaseId: "v3", releasePath: fixture.root, revision: "v3"})
|
|
733
|
+
await assert.rejects(
|
|
734
|
+
() => sendControlCommand({
|
|
735
|
+
command: {
|
|
736
|
+
command: "recover-generation-transition",
|
|
737
|
+
previousReleaseId: "v1",
|
|
738
|
+
releaseId: "v3",
|
|
739
|
+
releasePath: fixture.root,
|
|
740
|
+
revision: "v3"
|
|
741
|
+
},
|
|
742
|
+
path: fixture.config.control.path
|
|
743
|
+
}),
|
|
744
|
+
/not a safe failed pre-commit transition/i
|
|
745
|
+
)
|
|
746
|
+
} finally {
|
|
747
|
+
await daemon.shutdown()
|
|
748
|
+
await fs.rm(fixture.root, {force: true, recursive: true})
|
|
749
|
+
}
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
test("candidate activation failure compensates to the incumbent and admits a different later release", async () => {
|
|
753
|
+
const fixture = await createFixture({handoffService: true, handoffServiceActivate: true, handoffServiceActivateFailure: true, nonBlockingDrainWorker: true, webDependsOnService: true})
|
|
754
|
+
const daemon = await startDaemon(fixture.config)
|
|
755
|
+
|
|
756
|
+
try {
|
|
757
|
+
await daemon.deploy({releaseId: "v1", releasePath: fixture.root, revision: "v1"})
|
|
758
|
+
await assert.rejects(
|
|
759
|
+
() => daemon.deploy({releaseId: "v2", releasePath: fixture.root, revision: "v2"}),
|
|
760
|
+
/activate command exited non-zero.*compensation restored incumbent v1 as authoritative and retired failed candidate v2/i
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
const compensated = daemon.status()
|
|
764
|
+
|
|
765
|
+
assert.equal(compensated.activeReleaseId, "v1")
|
|
766
|
+
assert.equal(compensated.generationTransition, undefined)
|
|
767
|
+
assert.equal(await fetchText(daemon, "/release"), "v1")
|
|
768
|
+
assert.equal(statusRelease(daemon, "v1").processes.find((processStatus) => processStatus.id === "worker")?.state, "running")
|
|
769
|
+
assert.deepEqual(await lifecycleEvents(fixture.lifecycleLogPath), ["activate:v1", "retire:v1", "retire:v2", "activate:v1"])
|
|
770
|
+
|
|
771
|
+
await daemon.deploy({releaseId: "v3", releasePath: fixture.root, revision: "v3"})
|
|
772
|
+
|
|
773
|
+
assert.equal(daemon.status().activeReleaseId, "v3")
|
|
774
|
+
assert.equal(await fetchText(daemon, "/release"), "v3")
|
|
775
|
+
} finally {
|
|
776
|
+
await daemon.shutdown()
|
|
777
|
+
await fs.rm(fixture.root, {force: true, recursive: true})
|
|
778
|
+
}
|
|
779
|
+
})
|
|
780
|
+
|
|
781
|
+
test("ambiguous candidate activation retires the candidate before reactivating the incumbent", async () => {
|
|
782
|
+
const fixture = await createFixture({handoffService: true, handoffServiceActivate: true, handoffServiceActivateAmbiguousFailure: true, nonBlockingDrainWorker: true, webDependsOnService: true})
|
|
783
|
+
const daemon = await startDaemon(fixture.config)
|
|
784
|
+
|
|
785
|
+
try {
|
|
786
|
+
await daemon.deploy({releaseId: "v1", releasePath: fixture.root, revision: "v1"})
|
|
787
|
+
await assert.rejects(
|
|
788
|
+
() => daemon.deploy({releaseId: "v2", releasePath: fixture.root, revision: "v2"}),
|
|
789
|
+
/activate command exited non-zero.*compensation restored incumbent v1 as authoritative and retired failed candidate v2/i
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
assert.deepEqual(await lifecycleEvents(fixture.lifecycleLogPath), [
|
|
793
|
+
"activate:v1",
|
|
794
|
+
"retire:v1",
|
|
795
|
+
"activate:v2",
|
|
796
|
+
"retire:v2",
|
|
797
|
+
"activate:v1"
|
|
798
|
+
])
|
|
799
|
+
assert.equal(daemon.status().activeReleaseId, "v1")
|
|
800
|
+
assert.equal(daemon.status().generationTransition, undefined)
|
|
801
|
+
} finally {
|
|
802
|
+
await daemon.shutdown()
|
|
803
|
+
await fs.rm(fixture.root, {force: true, recursive: true})
|
|
804
|
+
}
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
test("candidate activation recovery reverses a worker-specific quiet hook before reporting active", async () => {
|
|
808
|
+
const fixture = await createFixture({handoffService: true, handoffServiceActivate: true, handoffServiceActivateFailure: true, nonBlockingDrainWorker: true, webDependsOnService: true, workerReactivationLifecycle: true})
|
|
809
|
+
const daemon = await startDaemon(fixture.config)
|
|
810
|
+
|
|
811
|
+
try {
|
|
812
|
+
await daemon.deploy({releaseId: "v1", releasePath: fixture.root, revision: "v1"})
|
|
813
|
+
await assert.rejects(
|
|
814
|
+
() => daemon.deploy({releaseId: "v2", releasePath: fixture.root, revision: "v2"}),
|
|
815
|
+
/compensation restored incumbent v1 as authoritative/i
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
const events = await lifecycleEvents(fixture.lifecycleLogPath)
|
|
819
|
+
const candidateRetired = events.indexOf("worker-retire:v2")
|
|
820
|
+
const workerReactivated = events.indexOf("worker-reactivate:v1")
|
|
821
|
+
|
|
822
|
+
assert.ok(candidateRetired >= 0, JSON.stringify(events))
|
|
823
|
+
assert.ok(workerReactivated > candidateRetired, JSON.stringify(events))
|
|
824
|
+
assert.equal(statusRelease(daemon, "v1").processes.find((processStatus) => processStatus.id === "worker")?.state, "running")
|
|
825
|
+
assert.equal(daemon.status().activeReleaseId, "v1")
|
|
826
|
+
assert.equal(daemon.status().generationTransition, undefined)
|
|
827
|
+
} finally {
|
|
828
|
+
await daemon.shutdown()
|
|
829
|
+
await fs.rm(fixture.root, {force: true, recursive: true})
|
|
830
|
+
}
|
|
831
|
+
})
|
|
832
|
+
|
|
833
|
+
test("candidate activation recovery keeps the fence when a worker-specific resume hook fails", async () => {
|
|
834
|
+
const fixture = await createFixture({handoffService: true, handoffServiceActivate: true, handoffServiceActivateFailure: true, nonBlockingDrainWorker: true, webDependsOnService: true, workerReactivationFailure: true, workerReactivationLifecycle: true})
|
|
835
|
+
const daemon = await startDaemon(fixture.config)
|
|
836
|
+
|
|
837
|
+
try {
|
|
838
|
+
await daemon.deploy({releaseId: "v1", releasePath: fixture.root, revision: "v1"})
|
|
839
|
+
await assert.rejects(
|
|
840
|
+
() => daemon.deploy({releaseId: "v2", releasePath: fixture.root, revision: "v2"}),
|
|
841
|
+
error => {
|
|
842
|
+
const failure = /** @type {Error} */ (error)
|
|
843
|
+
|
|
844
|
+
assert.match(failure.message, /activate command exited non-zero/)
|
|
845
|
+
assert.match(failure.message, /reactivate command exited non-zero/)
|
|
846
|
+
return true
|
|
847
|
+
}
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
const status = daemon.status()
|
|
851
|
+
const restorationEvent = daemon.eventLog.recent().find((event) => event.message === "release generation compensation restoration failed")
|
|
852
|
+
|
|
853
|
+
assert.equal(status.activeReleaseId, "v1")
|
|
854
|
+
assert.equal(status.generationTransition?.phase, "restoring_previous")
|
|
855
|
+
assert.match(String(status.generationTransition?.activationError), /activate command exited non-zero/)
|
|
856
|
+
assert.match(String(status.generationTransition?.compensationError), /reactivate command exited non-zero/)
|
|
857
|
+
assert.equal(statusRelease(daemon, "v1").processes.find((processStatus) => processStatus.id === "worker")?.state, "quiesced")
|
|
858
|
+
assert.match(String(restorationEvent?.data.activationError), /activate command exited non-zero/)
|
|
859
|
+
assert.match(String(restorationEvent?.data.error), /reactivate command exited non-zero/)
|
|
860
|
+
await assert.rejects(() => daemon.deploy({releaseId: "v3", releasePath: fixture.root, revision: "v3"}), /transition.*v2.*unresolved/i)
|
|
861
|
+
} finally {
|
|
862
|
+
await daemon.shutdown()
|
|
863
|
+
await fs.rm(fixture.root, {force: true, recursive: true})
|
|
864
|
+
}
|
|
865
|
+
})
|
|
866
|
+
|
|
867
|
+
test("compensation keeps the fence when the cleared checkpoint cannot be persisted", async () => {
|
|
868
|
+
const fixture = await createFixture({handoffService: true, handoffServiceActivate: true, handoffServiceActivateFailure: true, nonBlockingDrainWorker: true, webDependsOnService: true})
|
|
869
|
+
const daemon = await startDaemon(fixture.config)
|
|
870
|
+
|
|
871
|
+
try {
|
|
872
|
+
await daemon.deploy({releaseId: "v1", releasePath: fixture.root, revision: "v1"})
|
|
873
|
+
const checkpoint = daemon.checkpointGenerationTransition.bind(daemon)
|
|
874
|
+
|
|
875
|
+
daemon.checkpointGenerationTransition = async () => {
|
|
876
|
+
if (!daemon.generationTransition) throw new Error("cleared checkpoint unavailable")
|
|
877
|
+
await checkpoint()
|
|
878
|
+
}
|
|
879
|
+
await assert.rejects(
|
|
880
|
+
() => daemon.deploy({releaseId: "v2", releasePath: fixture.root, revision: "v2"}),
|
|
881
|
+
/activate command exited non-zero.*compensation checkpoint clear failed: cleared checkpoint unavailable/i
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
assert.equal(daemon.status().activeReleaseId, "v1")
|
|
885
|
+
assert.equal(daemon.status().generationTransition?.phase, "restoring_previous")
|
|
886
|
+
const persisted = /** @type {{generationTransition?: {phase?: string}} | undefined} */ (await readState(fixture.statePath))
|
|
887
|
+
|
|
888
|
+
assert.equal(persisted?.generationTransition?.phase, "restoring_previous")
|
|
889
|
+
await assert.rejects(() => daemon.deploy({releaseId: "v3", releasePath: fixture.root, revision: "v3"}), /transition.*v2.*unresolved/i)
|
|
890
|
+
|
|
891
|
+
daemon.checkpointGenerationTransition = checkpoint
|
|
892
|
+
const recovery = await sendControlCommand({
|
|
893
|
+
command: {
|
|
894
|
+
command: "recover-generation-transition",
|
|
895
|
+
previousReleaseId: "v1",
|
|
896
|
+
releaseId: "v2",
|
|
897
|
+
releasePath: fixture.root,
|
|
898
|
+
revision: "v2"
|
|
899
|
+
},
|
|
900
|
+
path: fixture.config.control.path
|
|
901
|
+
})
|
|
902
|
+
|
|
903
|
+
assert.equal(recovery.recoveryStatus, "recovered")
|
|
904
|
+
assert.equal(daemon.status().generationTransition, undefined)
|
|
682
905
|
} finally {
|
|
683
906
|
await daemon.shutdown()
|
|
684
907
|
await fs.rm(fixture.root, {force: true, recursive: true})
|
|
@@ -691,6 +914,10 @@ test("unresolved generation transition fences stop, restart, and rollback mutati
|
|
|
691
914
|
|
|
692
915
|
try {
|
|
693
916
|
await daemon.deploy({releaseId: "v1", releasePath: fixture.root, revision: "v1"})
|
|
917
|
+
const incumbentCoordinator = daemon.releases.get("v1")?.getProcess("beacon")
|
|
918
|
+
|
|
919
|
+
assert.ok(incumbentCoordinator)
|
|
920
|
+
incumbentCoordinator.reactivateStrict = async () => { throw new Error("incumbent restoration rejected") }
|
|
694
921
|
await assert.rejects(() => daemon.deploy({releaseId: "v2", releasePath: fixture.root, revision: "v2"}), /activate command exited non-zero/)
|
|
695
922
|
|
|
696
923
|
await assert.rejects(() => daemon.stopRelease("v2"), /cannot stop.*generation transition.*unresolved/i)
|
|
@@ -698,9 +925,7 @@ test("unresolved generation transition fences stop, restart, and rollback mutati
|
|
|
698
925
|
await assert.rejects(() => daemon.rollback({releaseId: "v2"}), /cannot rollback.*generation transition.*unresolved/i)
|
|
699
926
|
|
|
700
927
|
assert.notEqual(statusRelease(daemon, "v2").processes.find((entry) => entry.id === "web")?.state, "stopped")
|
|
701
|
-
await
|
|
702
|
-
await daemon.deploy({releaseId: "v2", releasePath: fixture.root, revision: "v2"})
|
|
703
|
-
assert.equal(await fetchText(daemon, "/release"), "v2")
|
|
928
|
+
assert.equal(await fetchText(daemon, "/release"), "v1")
|
|
704
929
|
} finally {
|
|
705
930
|
await daemon.shutdown()
|
|
706
931
|
await fs.rm(fixture.root, {force: true, recursive: true})
|
|
@@ -1654,7 +1879,7 @@ test("deploy can ensure the daemon before sending the release command", async ()
|
|
|
1654
1879
|
})
|
|
1655
1880
|
|
|
1656
1881
|
/**
|
|
1657
|
-
* @param {{companionReplicas?: number, handoffService?: boolean, handoffServiceActivate?: boolean, handoffServiceActivateFailure?: boolean | string, handoffServiceQuiet?: boolean, handoffServiceQuietFailure?: boolean, includeCompanion?: boolean, includeService?: boolean, includeSingleton?: boolean, memoryLimitBytes?: number, nonBlockingDrainWorker?: boolean, persistState?: boolean, proxyHost?: string, singletonCwd?: string, webCommand?: string, webDependsOnService?: boolean, webHealthTimeoutMs?: number, workerStopDelayMs?: number}} [options] - Fixture options.
|
|
1882
|
+
* @param {{companionReplicas?: number, handoffService?: boolean, handoffServiceActivate?: boolean, handoffServiceActivateAmbiguousFailure?: boolean, handoffServiceActivateFailure?: boolean | string, handoffServiceQuiet?: boolean, handoffServiceQuietFailure?: boolean, includeCompanion?: boolean, includeService?: boolean, includeSingleton?: boolean, memoryLimitBytes?: number, nonBlockingDrainWorker?: boolean, persistState?: boolean, proxyHost?: string, singletonCwd?: string, webCommand?: string, webDependsOnService?: boolean, webHealthTimeoutMs?: number, workerReactivationFailure?: boolean, workerReactivationLifecycle?: boolean, workerStopDelayMs?: number}} [options] - Fixture options.
|
|
1658
1883
|
* @returns {Promise<{activationGatePath: string, config: import("../src/config.js").RollbridgeConfig, lifecycleLogPath: string, retirementGatePath: string, root: string, serviceLogPath: string, serviceQuietPath: string, singletonLogPath: string, statePath: string}>} Fixture data.
|
|
1659
1884
|
*/
|
|
1660
1885
|
async function createFixture(options = {}) {
|
|
@@ -1672,7 +1897,7 @@ async function createFixture(options = {}) {
|
|
|
1672
1897
|
if (options.includeService || options.handoffService) {
|
|
1673
1898
|
const activationFailureRelease = typeof options.handoffServiceActivateFailure === "string" ? options.handoffServiceActivateFailure : "v2"
|
|
1674
1899
|
const lifecycle = options.handoffServiceActivate ? {
|
|
1675
|
-
activateCommand: `${options.handoffServiceActivateFailure ? `[ "$ROLLBRIDGE_RELEASE_ID" != ${JSON.stringify(activationFailureRelease)} ] || [ -f ${JSON.stringify(activationGatePath)} ] || exit 24; ` : ""}printf 'activate:%s\\n' "$ROLLBRIDGE_RELEASE_ID" >> ${JSON.stringify(lifecycleLogPath)}`,
|
|
1900
|
+
activateCommand: `${options.handoffServiceActivateFailure && !options.handoffServiceActivateAmbiguousFailure ? `[ "$ROLLBRIDGE_RELEASE_ID" != ${JSON.stringify(activationFailureRelease)} ] || [ -f ${JSON.stringify(activationGatePath)} ] || exit 24; ` : ""}printf 'activate:%s\\n' "$ROLLBRIDGE_RELEASE_ID" >> ${JSON.stringify(lifecycleLogPath)}${options.handoffServiceActivateAmbiguousFailure ? `; [ "$ROLLBRIDGE_RELEASE_ID" != ${JSON.stringify(activationFailureRelease)} ] || [ -f ${JSON.stringify(activationGatePath)} ] || exit 24` : ""}`,
|
|
1676
1901
|
quietCommand: `${options.handoffServiceQuietFailure ? `[ -f ${JSON.stringify(retirementGatePath)} ] || exit 23; ` : ""}printf 'retire:%s\\n' "$ROLLBRIDGE_RELEASE_ID" >> ${JSON.stringify(lifecycleLogPath)}`
|
|
1677
1902
|
} : options.handoffServiceQuiet || options.handoffServiceQuietFailure ? {
|
|
1678
1903
|
quietCommand: options.handoffServiceQuietFailure ? "exit 23" : `printf '%s\\n' "$ROLLBRIDGE_RELEASE_ID" >> ${JSON.stringify(serviceQuietPath)}`
|
|
@@ -1713,6 +1938,10 @@ async function createFixture(options = {}) {
|
|
|
1713
1938
|
processes.push({
|
|
1714
1939
|
command: `${JSON.stringify(process.execPath)} -e ${JSON.stringify(`process.on('SIGTERM', () => setTimeout(() => process.exit(0), ${options.workerStopDelayMs || 0})); setInterval(() => {}, 1000)`)}`,
|
|
1715
1940
|
id: "worker",
|
|
1941
|
+
...(options.workerReactivationLifecycle ? {lifecycle: {
|
|
1942
|
+
quietCommand: `printf 'worker-retire:%s\\n' "$ROLLBRIDGE_RELEASE_ID" >> ${JSON.stringify(lifecycleLogPath)}`,
|
|
1943
|
+
reactivateCommand: `${options.workerReactivationFailure ? "exit 25; " : ""}printf 'worker-reactivate:%s\\n' "$ROLLBRIDGE_RELEASE_ID" >> ${JSON.stringify(lifecycleLogPath)}`
|
|
1944
|
+
}} : {}),
|
|
1716
1945
|
nonBlockingDrain: true,
|
|
1717
1946
|
policy: "companion"
|
|
1718
1947
|
})
|