querysub 0.510.0 → 0.511.0
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/package.json +1 -1
- package/spec.txt +1 -4
- package/src/4-querysub/Querysub.ts +2 -0
- package/src/deployManager/components/MachineDetailPage.tsx +1 -1
- package/src/deployManager/components/ServiceDetailPage.tsx +265 -214
- package/src/deployManager/components/ServicesListPage.tsx +5 -4
- package/src/deployManager/components/Tools.tsx +6 -3
- package/src/deployManager/components/deployButtons.tsx +7 -1
- package/src/deployManager/machineApplyMainCode.ts +82 -75
- package/src/deployManager/machineController.ts +2 -2
- package/src/deployManager/machineSchema.ts +107 -105
- package/src/diagnostics/logs/errorNotifications2/ErrorNotificationPage.tsx +1 -1
- package/src/library-components/TypedConfigEditor.tsx +16 -17
- package/src/misc.ts +30 -0
|
@@ -48,9 +48,9 @@ export class ServicesListPage extends qreact.Component {
|
|
|
48
48
|
const newServiceId = `service-${Math.round(Date.now())}`;
|
|
49
49
|
const newConfig: ServiceConfig = {
|
|
50
50
|
serviceId: newServiceId,
|
|
51
|
-
machineIds: [],
|
|
52
51
|
parameters: {
|
|
53
52
|
key: newServiceId,
|
|
53
|
+
machineIds: [],
|
|
54
54
|
repoUrl: gitInfo.repoUrl,
|
|
55
55
|
gitRef: gitInfo.latestRef,
|
|
56
56
|
command: "ping 1.1.1.1",
|
|
@@ -80,11 +80,12 @@ export class ServicesListPage extends qreact.Component {
|
|
|
80
80
|
{services.map(([serviceId, config]) => {
|
|
81
81
|
if (!config) return <div key={serviceId}>Config is broken? Missing value for service? Is the file corrupted?</div>;
|
|
82
82
|
|
|
83
|
-
let
|
|
83
|
+
let machineIds = config.parameters.machineIds || [];
|
|
84
|
+
let disabledMachines = machineIds.filter(machineId => {
|
|
84
85
|
let machineConfig = getMachineConfig(machineId);
|
|
85
86
|
return machineConfig?.disabled;
|
|
86
87
|
});
|
|
87
|
-
let enabledMachineIds =
|
|
88
|
+
let enabledMachineIds = machineIds.filter(machineId => {
|
|
88
89
|
let machineConfig = getMachineConfig(machineId);
|
|
89
90
|
return !machineConfig?.disabled;
|
|
90
91
|
});
|
|
@@ -138,7 +139,7 @@ export class ServicesListPage extends qreact.Component {
|
|
|
138
139
|
</div>
|
|
139
140
|
<div className={css.vbox(4)}>
|
|
140
141
|
<div>Updated {formatDateJSX(config.info.lastUpdatedTime)} AGO</div>
|
|
141
|
-
{config.parameters.overlapTime && <div className={css.colorhsl(210, 50, 50).fontSize(14).fontWeight("bold")}>
|
|
142
|
+
{config.parameters.overlapTime && <div className={css.colorhsl(210, 50, 50).fontSize(14).fontWeight("bold")}>Deploy overlap: {formatTime(config.parameters.overlapTime)}</div> || undefined}
|
|
142
143
|
</div>
|
|
143
144
|
</div>
|
|
144
145
|
</Anchor>
|
|
@@ -125,11 +125,14 @@ class RenameMachine extends qreact.Component {
|
|
|
125
125
|
);
|
|
126
126
|
let toUpdate: ServiceConfig[] = [];
|
|
127
127
|
for (let config of configs.filter(isDefined)) {
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
let machineIds = config.parameters.machineIds || [];
|
|
129
|
+
if (!machineIds.includes(from)) continue;
|
|
130
130
|
toUpdate.push({
|
|
131
131
|
...config,
|
|
132
|
-
|
|
132
|
+
parameters: {
|
|
133
|
+
...config.parameters,
|
|
134
|
+
machineIds: machineIds.map(m => m === from ? to : m),
|
|
135
|
+
},
|
|
133
136
|
});
|
|
134
137
|
}
|
|
135
138
|
if (toUpdate.length === 0) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
|
-
import { MachineServiceController, MachineInfo, ServiceConfig } from "../machineSchema";
|
|
3
|
+
import { DEFAULT_OVERLAP_TIME, MachineServiceController, MachineInfo, ServiceConfig } from "../machineSchema";
|
|
4
4
|
import { showFullscreenModal } from "../../5-diagnostics/FullscreenModal";
|
|
5
5
|
import { css } from "../../4-dom/css";
|
|
6
6
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
@@ -97,6 +97,9 @@ export class UpdateButtons extends qreact.Component<{
|
|
|
97
97
|
onClick={async () => {
|
|
98
98
|
for (let service of outdatedServices) {
|
|
99
99
|
service.parameters.gitRef = gitInfo.latestRef;
|
|
100
|
+
// Release with the configured overlap, so the new instances are running before the old ones shut down
|
|
101
|
+
let overlap = service.parameters.overlapTime || DEFAULT_OVERLAP_TIME;
|
|
102
|
+
service.parameters.releaseTime = Date.now() + overlap;
|
|
100
103
|
}
|
|
101
104
|
await controller.setServiceConfigs.promise(outdatedServices);
|
|
102
105
|
}}
|
|
@@ -133,6 +136,9 @@ export class UpdateServiceButtons extends qreact.Component<{
|
|
|
133
136
|
className={buttonStyle.hsl(120, 70, 90).alignSelf("stretch")}
|
|
134
137
|
onClick={async () => {
|
|
135
138
|
this.props.service.parameters.gitRef = gitInfo.latestRef;
|
|
139
|
+
// Release with the configured overlap, so the new instances are running before the old ones shut down
|
|
140
|
+
let overlap = this.props.service.parameters.overlapTime || DEFAULT_OVERLAP_TIME;
|
|
141
|
+
this.props.service.parameters.releaseTime = Date.now() + overlap;
|
|
136
142
|
await controller.setServiceConfigs.promise([this.props.service]);
|
|
137
143
|
}}
|
|
138
144
|
>
|
|
@@ -4,14 +4,14 @@ import { measureWrap } from "socket-function/src/profiling/measure";
|
|
|
4
4
|
import { getOwnMachineId } from "sliftutils/misc/https/certs";
|
|
5
5
|
import { forceRemoveNode, getOurNodeId, getOurNodeIdAssert } from "../-f-node-discovery/NodeDiscovery";
|
|
6
6
|
import { Querysub } from "../4-querysub/Querysub";
|
|
7
|
-
import { MACHINE_RESYNC_INTERVAL, MachineServiceControllerBase, MachineInfo, ServiceConfig,
|
|
7
|
+
import { MACHINE_RESYNC_INTERVAL, MachineServiceControllerBase, MachineInfo, ServiceConfig, ServiceParameters, SERVICE_FOLDER, machineInfos, SERVICE_NODE_FILE_NAME, getEffectiveServiceConfigs, getLiveServiceParameters, restartParametersKey, recordLaunch, DEFAULT_OVERLAP_TIME } from "./machineSchema";
|
|
8
8
|
import { runPromise } from "../functional/runCommand";
|
|
9
9
|
import { getExternalIP } from "socket-function/src/networking";
|
|
10
10
|
import { errorToUndefined, errorToUndefinedSilent } from "../errors";
|
|
11
11
|
import { getDomain } from "../config";
|
|
12
12
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
13
13
|
import { sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
14
|
-
import { isDefined } from "../misc";
|
|
14
|
+
import { isDefined, setPreciseTimeout } from "../misc";
|
|
15
15
|
import { logLoadTime } from "../logModuleLoadTimes";
|
|
16
16
|
import { delay, retryFunctional, runInSerial, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
17
17
|
import os from "os";
|
|
@@ -132,7 +132,7 @@ const SCREEN_SUFFIX = "-dply";
|
|
|
132
132
|
export function getScreenName(config: { serviceKey: string; index: number }): string {
|
|
133
133
|
return `${config.serviceKey}-${config.index}${SCREEN_SUFFIX}`.replace(/[^a-zA-Z0-9\-_]/g, "_");
|
|
134
134
|
}
|
|
135
|
-
// The new instances started during a
|
|
135
|
+
// The new instances started during a release overlap. They cover the service while the canonical screens are restarted with the new config at release time, and are killed shortly after.
|
|
136
136
|
export function getNextScreenName(config: { serviceKey: string; index: number }): string {
|
|
137
137
|
return getScreenName({ serviceKey: config.serviceKey + "-next", index: config.index });
|
|
138
138
|
}
|
|
@@ -585,56 +585,30 @@ const ensureGitSynced = measureWrap(async function ensureGitSynced(config: {
|
|
|
585
585
|
let launchesPerService = new Map<string, number>();
|
|
586
586
|
let lastLaunchedTimePerService = new Map<string, number>();
|
|
587
587
|
|
|
588
|
-
//
|
|
589
|
-
async function processSwitchovers(): Promise<Map<string, { switchover: ServiceSwitchover; pending: ServiceConfig }>> {
|
|
590
|
-
let active = new Map<string, { switchover: ServiceSwitchover; pending: ServiceConfig }>();
|
|
591
|
-
let switchovers = await serviceSwitchovers.values();
|
|
592
|
-
for (let switchover of switchovers) {
|
|
593
|
-
let pending = await pendingServiceConfigs.get(switchover.serviceId);
|
|
594
|
-
if (!pending) {
|
|
595
|
-
console.warn(`Switchover for ${switchover.serviceId} has no pending config, removing it`);
|
|
596
|
-
await serviceSwitchovers.delete(switchover.serviceId);
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
if (Date.now() >= switchover.switchTime) {
|
|
600
|
-
// Multiple apply nodes may race to promote, which is fine, as they all write the same pending config.
|
|
601
|
-
console.log(green(`Promoting pending config for ${switchover.serviceId} (switchover scheduled for ${new Date(switchover.switchTime).toLocaleString()})`));
|
|
602
|
-
await serviceConfigs.set(switchover.serviceId, pending);
|
|
603
|
-
await pendingServiceConfigs.delete(switchover.serviceId);
|
|
604
|
-
await serviceSwitchovers.delete(switchover.serviceId);
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
let overlapStart = switchover.switchTime - switchover.overlapTime;
|
|
608
|
-
console.log(magenta(`Switchover in flight for ${switchover.serviceId} (${pending.parameters.key}): new instances start at ${new Date(overlapStart).toLocaleString()}${Date.now() >= overlapStart && " (overlap running)" || ""}, old instances shut down at ${new Date(switchover.switchTime).toLocaleString()}`));
|
|
609
|
-
active.set(switchover.serviceId, { switchover, pending });
|
|
610
|
-
}
|
|
611
|
-
return active;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// Starts (or verifies) the "-next" instance of a service during a switchover overlap, running the pending config. It covers the service while the canonical screens restart with the new config at switch time, and is killed by the unused-screen cleanup once the switchover promotes.
|
|
588
|
+
// Starts (or verifies) the "-next" instance of a service during a release overlap, running the new (not yet live) parameters. It covers the service while the canonical screens restart with the new parameters at release time, and is killed by the unused-screen cleanup afterwards (once the release passes we stop ensuring it).
|
|
615
589
|
async function ensureNextInstance(config: {
|
|
616
|
-
|
|
590
|
+
next: ServiceParameters;
|
|
617
591
|
index: number;
|
|
618
592
|
screenNamesUsed: Set<string>;
|
|
619
593
|
screenStateMap: Map<string, { screenName: string; isProcessRunning: boolean; pid: string }>;
|
|
620
594
|
}) {
|
|
621
|
-
let {
|
|
622
|
-
let nextScreenName = getNextScreenName({ serviceKey:
|
|
595
|
+
let { next, index } = config;
|
|
596
|
+
let nextScreenName = getNextScreenName({ serviceKey: next.key, index });
|
|
623
597
|
config.screenNamesUsed.add(nextScreenName);
|
|
624
598
|
|
|
625
599
|
let root = os.homedir() + "/" + SERVICE_FOLDER;
|
|
626
600
|
let folder = root + nextScreenName + "/";
|
|
627
601
|
let gitFolder = folder + "git/";
|
|
628
602
|
await fs.promises.mkdir(gitFolder, { recursive: true });
|
|
629
|
-
if (
|
|
603
|
+
if (next.gitRef) {
|
|
630
604
|
let prevGitRef = "";
|
|
631
605
|
try {
|
|
632
606
|
prevGitRef = await getGitRefLive(gitFolder);
|
|
633
607
|
} catch { }
|
|
634
608
|
await ensureGitSynced({
|
|
635
609
|
gitFolder,
|
|
636
|
-
repoUrl:
|
|
637
|
-
gitRef:
|
|
610
|
+
repoUrl: next.repoUrl,
|
|
611
|
+
gitRef: next.gitRef,
|
|
638
612
|
});
|
|
639
613
|
let afterGitRef = await getGitRefLive(gitFolder);
|
|
640
614
|
let nodeModulesMissing = !await fsExistsAsync(gitFolder + "node_modules");
|
|
@@ -649,37 +623,76 @@ async function ensureNextInstance(config: {
|
|
|
649
623
|
if (await fsExistsAsync(parameterPath)) {
|
|
650
624
|
prevParameters = await fs.promises.readFile(parameterPath, "utf8");
|
|
651
625
|
}
|
|
652
|
-
let newParametersString = JSON.stringify(
|
|
653
|
-
let sameParameters = prevParameters
|
|
626
|
+
let newParametersString = JSON.stringify(next);
|
|
627
|
+
let sameParameters = sameRestartParameters(prevParameters, next);
|
|
654
628
|
let screenIsRunning = config.screenStateMap.get(nextScreenName)?.isProcessRunning;
|
|
655
629
|
if (sameParameters && screenIsRunning) {
|
|
656
|
-
|
|
630
|
+
if (prevParameters !== newParametersString) {
|
|
631
|
+
await fs.promises.writeFile(parameterPath, newParametersString);
|
|
632
|
+
}
|
|
633
|
+
console.log(green(`Verified release-overlap instance ${magenta(nextScreenName)} is running`));
|
|
657
634
|
return;
|
|
658
635
|
}
|
|
659
636
|
|
|
660
|
-
console.log(`Starting
|
|
637
|
+
console.log(`Starting release-overlap instance ${magenta(nextScreenName)}, with ${newParametersString}, isRunning = ${screenIsRunning}, sameParameters = ${sameParameters}`);
|
|
661
638
|
await fs.promises.writeFile(parameterPath, newParametersString);
|
|
662
639
|
await runScreenCommand({
|
|
663
640
|
screenName: nextScreenName,
|
|
664
|
-
command:
|
|
641
|
+
command: next.command,
|
|
665
642
|
});
|
|
666
643
|
}
|
|
667
644
|
|
|
645
|
+
function sameRestartParameters(prevParametersJSON: string, next: ServiceParameters): boolean {
|
|
646
|
+
if (!prevParametersJSON) return false;
|
|
647
|
+
try {
|
|
648
|
+
return restartParametersKey(JSON.parse(prevParametersJSON)) === restartParametersKey(next);
|
|
649
|
+
} catch {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
668
654
|
const resyncServicesBase = runInSerial(measureWrap(async function resyncServices() {
|
|
669
655
|
console.log(magenta("Resyncing services"));
|
|
670
656
|
let machineId = getOwnMachineId(getDomain());
|
|
671
|
-
let
|
|
672
|
-
let
|
|
673
|
-
|
|
657
|
+
let allConfigs = await getEffectiveServiceConfigs();
|
|
658
|
+
let relevantConfigs = allConfigs
|
|
659
|
+
.filter(config => (getLiveServiceParameters(config).machineIds || []).includes(machineId) || (config.parameters.machineIds || []).includes(machineId))
|
|
660
|
+
.filter(x => getLiveServiceParameters(x).deploy || x.parameters.deploy);
|
|
674
661
|
|
|
675
662
|
let machineInfo = await getLiveMachineInfo();
|
|
676
663
|
|
|
677
664
|
let screenState = await getScreenState();
|
|
678
665
|
let screenStateMap = new Map(screenState.map(s => [s.screenName, s]));
|
|
679
666
|
let screenNamesUsed = new Set<string>();
|
|
667
|
+
let upcomingReleases: { releaseTime: number; overlapTime: number }[] = [];
|
|
680
668
|
let root = os.homedir() + "/" + SERVICE_FOLDER;
|
|
681
|
-
for (let
|
|
682
|
-
let
|
|
669
|
+
for (let record of relevantConfigs) {
|
|
670
|
+
let config = { ...record, parameters: getLiveServiceParameters(record) };
|
|
671
|
+
let releaseTime = record.parameters.releaseTime || 0;
|
|
672
|
+
let isUpcomingRelease = !!releaseTime && Date.now() < releaseTime && !!record.oldParameters;
|
|
673
|
+
let overlapTime = record.parameters.overlapTime || DEFAULT_OVERLAP_TIME;
|
|
674
|
+
if (isUpcomingRelease) {
|
|
675
|
+
upcomingReleases.push({ releaseTime, overlapTime });
|
|
676
|
+
console.log(magenta(`Release in flight for ${record.serviceId} (${record.parameters.key}): new instances start at ${new Date(releaseTime - overlapTime).toLocaleString()}${Date.now() >= releaseTime - overlapTime && " (overlap running)" || ""}, old instances shut down at ${new Date(releaseTime).toLocaleString()}`));
|
|
677
|
+
// The release-overlap instances follow the NEW parameters' machineIds, so machines that are only in the new parameters still start their instances at overlap time (their canonical instances start once the release passes)
|
|
678
|
+
if (Date.now() >= releaseTime - overlapTime && record.parameters.deploy) {
|
|
679
|
+
let nextMatchedCount = (record.parameters.machineIds || []).filter(id => id === machineId).length;
|
|
680
|
+
for (let i = 0; i < nextMatchedCount; i++) {
|
|
681
|
+
try {
|
|
682
|
+
await ensureNextInstance({
|
|
683
|
+
next: record.parameters,
|
|
684
|
+
index: i,
|
|
685
|
+
screenNamesUsed,
|
|
686
|
+
screenStateMap,
|
|
687
|
+
});
|
|
688
|
+
} catch (e: any) {
|
|
689
|
+
console.error(`Error starting release-overlap instance for ${record.serviceId} (${record.parameters.key}): ${e.stack}`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (!config.parameters.deploy) continue;
|
|
695
|
+
let matchedCount = (config.parameters.machineIds || []).filter(id => id === machineId).length;
|
|
683
696
|
for (let i = 0; i < matchedCount; i++) {
|
|
684
697
|
let screenName = getScreenName({
|
|
685
698
|
serviceKey: config.parameters.key,
|
|
@@ -698,20 +711,9 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
|
|
|
698
711
|
let folder = root + screenName + "/";
|
|
699
712
|
screenNamesUsed.add(screenName);
|
|
700
713
|
|
|
701
|
-
|
|
702
|
-
if (switchoverObj) {
|
|
714
|
+
if (isUpcomingRelease) {
|
|
703
715
|
// Tell the running instance when it will be shut down, so it (and everything talking to it) can wind down gracefully.
|
|
704
|
-
await notifyServiceShutdown(screenName,
|
|
705
|
-
let overlapStart = switchoverObj.switchover.switchTime - switchoverObj.switchover.overlapTime;
|
|
706
|
-
let pendingMatchedCount = switchoverObj.pending.machineIds.filter(id => id === machineId).length;
|
|
707
|
-
if (Date.now() >= overlapStart && switchoverObj.pending.parameters.deploy && i < pendingMatchedCount) {
|
|
708
|
-
await ensureNextInstance({
|
|
709
|
-
pending: switchoverObj.pending,
|
|
710
|
-
index: i,
|
|
711
|
-
screenNamesUsed,
|
|
712
|
-
screenStateMap,
|
|
713
|
-
});
|
|
714
|
-
}
|
|
716
|
+
await notifyServiceShutdown(screenName, releaseTime);
|
|
715
717
|
}
|
|
716
718
|
|
|
717
719
|
let gitFolder = folder + "git/";
|
|
@@ -741,8 +743,7 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
|
|
|
741
743
|
}
|
|
742
744
|
let newParametersString = JSON.stringify(config.parameters);
|
|
743
745
|
|
|
744
|
-
|
|
745
|
-
let sameParameters = prevParameters === newParametersString;
|
|
746
|
+
let sameParameters = sameRestartParameters(prevParameters, config.parameters);
|
|
746
747
|
let screenIsRunning = screenStateMap.get(screenName)?.isProcessRunning;
|
|
747
748
|
|
|
748
749
|
let nodePathId = folder + SERVICE_NODE_FILE_NAME;
|
|
@@ -752,6 +753,9 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
|
|
|
752
753
|
}
|
|
753
754
|
|
|
754
755
|
if (sameParameters && screenIsRunning) {
|
|
756
|
+
if (prevParameters !== newParametersString) {
|
|
757
|
+
await fs.promises.writeFile(parameterPath, newParametersString);
|
|
758
|
+
}
|
|
755
759
|
console.log(green(`Verified ${magenta(screenName)} is running`));
|
|
756
760
|
continue;
|
|
757
761
|
}
|
|
@@ -816,7 +820,7 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
|
|
|
816
820
|
});
|
|
817
821
|
}
|
|
818
822
|
|
|
819
|
-
|
|
823
|
+
scheduleReleaseResync(upcomingReleases);
|
|
820
824
|
|
|
821
825
|
await machineInfos.set(machineId, machineInfo);
|
|
822
826
|
console.log(`${magenta(`Resynced ${relevantConfigs.length} services`)}:\n${JSON.stringify(machineInfo, null, 2)}`);
|
|
@@ -832,29 +836,32 @@ async function resyncServices() {
|
|
|
832
836
|
}
|
|
833
837
|
}
|
|
834
838
|
|
|
835
|
-
// The regular resync poll is too coarse for
|
|
836
|
-
let
|
|
837
|
-
function
|
|
839
|
+
// The regular resync poll is too coarse for release boundaries (the overlap start and the release itself), so schedule an extra resync at the next boundary.
|
|
840
|
+
let cancelReleaseResync: (() => void) | undefined;
|
|
841
|
+
function scheduleReleaseResync(upcomingReleases: { releaseTime: number; overlapTime: number }[]) {
|
|
838
842
|
let now = Date.now();
|
|
839
843
|
let next: number | undefined;
|
|
840
|
-
for (let
|
|
841
|
-
for (let boundary of [
|
|
844
|
+
for (let release of upcomingReleases) {
|
|
845
|
+
for (let boundary of [release.releaseTime - release.overlapTime, release.releaseTime]) {
|
|
842
846
|
if (boundary <= now) continue;
|
|
843
847
|
if (next === undefined || boundary < next) {
|
|
844
848
|
next = boundary;
|
|
845
849
|
}
|
|
846
850
|
}
|
|
847
851
|
}
|
|
848
|
-
if (
|
|
849
|
-
|
|
850
|
-
|
|
852
|
+
if (cancelReleaseResync) {
|
|
853
|
+
cancelReleaseResync();
|
|
854
|
+
cancelReleaseResync = undefined;
|
|
851
855
|
}
|
|
852
856
|
if (next === undefined) return;
|
|
853
|
-
console.log(`Scheduling a resync for the next
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
857
|
+
console.log(`Scheduling a resync for the next release boundary at ${new Date(next).toLocaleString()}`);
|
|
858
|
+
cancelReleaseResync = setPreciseTimeout({
|
|
859
|
+
time: next,
|
|
860
|
+
callback: () => {
|
|
861
|
+
cancelReleaseResync = undefined;
|
|
862
|
+
void resyncServices();
|
|
863
|
+
},
|
|
864
|
+
});
|
|
858
865
|
}
|
|
859
866
|
|
|
860
867
|
|
|
@@ -38,7 +38,7 @@ export function onServiceConfigChange(callback: () => Promise<void>): () => void
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
/** Tells a service's node that it will be shut down at the given time (due to a scheduled
|
|
41
|
+
/** Tells a service's node that it will be shut down at the given time (due to a scheduled deploy). */
|
|
42
42
|
export async function scheduleNodeShutdown(config: {
|
|
43
43
|
nodeId: string;
|
|
44
44
|
time: number;
|
|
@@ -51,7 +51,7 @@ class OnServiceChangeBase {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
public async scheduleShutdown(time: number) {
|
|
54
|
-
console.log(`Received scheduled shutdown notification (service
|
|
54
|
+
console.log(`Received scheduled shutdown notification (service deploy): shutting down at ${new Date(time).toLocaleString()}`);
|
|
55
55
|
setScheduledShutdownTime(time);
|
|
56
56
|
}
|
|
57
57
|
}
|