@onlineapps/service-wrapper 2.3.0 → 2.3.2
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 +5 -5
- package/src/ConfigLoader.js +1 -1
- package/src/ServiceWrapper.js +102 -107
- package/src/createTenantContextMiddleware.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/service-wrapper",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"@onlineapps/conn-infra-error-handler": "1.0.9",
|
|
31
31
|
"@onlineapps/conn-infra-mq": "1.1.69",
|
|
32
32
|
"@onlineapps/conn-orch-api-mapper": "1.0.30",
|
|
33
|
-
"@onlineapps/conn-orch-cookbook": "2.0.
|
|
34
|
-
"@onlineapps/conn-orch-orchestrator": "1.0.
|
|
35
|
-
"@onlineapps/conn-orch-registry": "1.1.
|
|
36
|
-
"@onlineapps/conn-orch-validator": "2.0.
|
|
33
|
+
"@onlineapps/conn-orch-cookbook": "2.0.38",
|
|
34
|
+
"@onlineapps/conn-orch-orchestrator": "1.0.106",
|
|
35
|
+
"@onlineapps/conn-orch-registry": "1.1.55",
|
|
36
|
+
"@onlineapps/conn-orch-validator": "2.0.32",
|
|
37
37
|
"@onlineapps/monitoring-core": "1.0.21",
|
|
38
38
|
"@onlineapps/service-common": "1.0.18",
|
|
39
39
|
"@onlineapps/runtime-config": "1.0.2"
|
package/src/ConfigLoader.js
CHANGED
|
@@ -44,7 +44,7 @@ const CONFIG_REGISTRY = {
|
|
|
44
44
|
description: 'Operations specification (API contract)'
|
|
45
45
|
},
|
|
46
46
|
VALIDATION_PROOF: {
|
|
47
|
-
paths: ['
|
|
47
|
+
paths: ['conn-runtime/validation-proof.json'],
|
|
48
48
|
required: false,
|
|
49
49
|
description: 'Pre-validation proof from cookbook tests'
|
|
50
50
|
}
|
package/src/ServiceWrapper.js
CHANGED
|
@@ -25,9 +25,20 @@ const ErrorHandlerConnector = require('@onlineapps/conn-infra-error-handler');
|
|
|
25
25
|
const { ValidationOrchestrator } = require('@onlineapps/conn-orch-validator');
|
|
26
26
|
const runtimeCfg = require('./config');
|
|
27
27
|
|
|
28
|
+
const RUNTIME_DIR = 'conn-runtime';
|
|
29
|
+
const PROOF_RELATIVE_PATH = `${RUNTIME_DIR}/validation-proof.json`;
|
|
30
|
+
|
|
28
31
|
const REVALIDATION_FAST_BACKOFF_MS = [30000, 60000, 120000, 300000, 600000, 1800000];
|
|
29
|
-
const
|
|
30
|
-
const
|
|
32
|
+
const REVALIDATION_HOURLY_MS = 3600000;
|
|
33
|
+
const REVALIDATION_HOURLY_MAX = 24;
|
|
34
|
+
const REVALIDATION_SLOW_MS = 21600000;
|
|
35
|
+
const REVALIDATION_STATE = {
|
|
36
|
+
IDLE: 'idle',
|
|
37
|
+
FAST_BACKOFF: 'fast_backoff',
|
|
38
|
+
HOURLY_BACKOFF: 'hourly_backoff',
|
|
39
|
+
SLOW_BACKOFF: 'slow_backoff',
|
|
40
|
+
SUCCEEDED: 'succeeded'
|
|
41
|
+
};
|
|
31
42
|
|
|
32
43
|
const INFRA_QUEUE_OWNERS = {
|
|
33
44
|
'workflow.init': 'Gateway (api_gateway)',
|
|
@@ -82,7 +93,6 @@ class ServiceWrapper {
|
|
|
82
93
|
this._revalidationState = REVALIDATION_STATE.IDLE;
|
|
83
94
|
this._revalidationAttempt = 0;
|
|
84
95
|
this._revalidationTimer = null;
|
|
85
|
-
this._infraInvalidationConsumerTag = null;
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
/**
|
|
@@ -163,14 +173,8 @@ class ServiceWrapper {
|
|
|
163
173
|
throw new Error(`Failed to start workflow listeners for ${serviceName}: ${listenerError.message}`);
|
|
164
174
|
}
|
|
165
175
|
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
await this._subscribeToInfraInvalidation(serviceName);
|
|
169
|
-
} catch (subErr) {
|
|
170
|
-
this.logger?.warn('[ServiceWrapper][infra.invalidation] Failed to subscribe to invalidation events (non-critical)', {
|
|
171
|
-
error: subErr.message
|
|
172
|
-
});
|
|
173
|
-
}
|
|
176
|
+
// Revalidation requests from Registry are handled via RegistryClient 'revalidate' event
|
|
177
|
+
// (subscribed in _initializeRegistry after successful registration)
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
/**
|
|
@@ -439,7 +443,6 @@ class ServiceWrapper {
|
|
|
439
443
|
this.workflowInitConsumerTag = null;
|
|
440
444
|
this.workflowControlConsumerTag = null;
|
|
441
445
|
this.serviceWorkflowConsumerTag = null;
|
|
442
|
-
this._infraInvalidationConsumerTag = null;
|
|
443
446
|
if (this._revalidationTimer) {
|
|
444
447
|
clearTimeout(this._revalidationTimer);
|
|
445
448
|
this._revalidationTimer = null;
|
|
@@ -997,6 +1000,15 @@ class ServiceWrapper {
|
|
|
997
1000
|
throw new Error(`Failed to register service ${serviceName}: ${error.message}`);
|
|
998
1001
|
}
|
|
999
1002
|
|
|
1003
|
+
// Listen for revalidation requests from Registry (infra version changes)
|
|
1004
|
+
this.registryClient.on('revalidate', (payload) => {
|
|
1005
|
+
this.logger?.info('[ServiceWrapper][revalidate] Revalidation requested by Registry', {
|
|
1006
|
+
reason: payload.reason,
|
|
1007
|
+
infraFingerprint: payload.infraFingerprint?.substring(0, 16)
|
|
1008
|
+
});
|
|
1009
|
+
this._deleteProofAndRevalidate();
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1000
1012
|
// Start heartbeat with fail-fast on persistent MQ failure
|
|
1001
1013
|
const heartbeatInterval = this.config.wrapper?.registry?.heartbeatInterval;
|
|
1002
1014
|
if (typeof heartbeatInterval !== 'number' || Number.isNaN(heartbeatInterval) || heartbeatInterval <= 0) {
|
|
@@ -1271,7 +1283,7 @@ class ServiceWrapper {
|
|
|
1271
1283
|
return { status: 'no_service_root' };
|
|
1272
1284
|
}
|
|
1273
1285
|
|
|
1274
|
-
const proofPath = path.join(this.serviceRoot,
|
|
1286
|
+
const proofPath = path.join(this.serviceRoot, PROOF_RELATIVE_PATH);
|
|
1275
1287
|
|
|
1276
1288
|
if (!fs.existsSync(proofPath)) {
|
|
1277
1289
|
return { status: 'no_proof' };
|
|
@@ -1845,67 +1857,23 @@ class ServiceWrapper {
|
|
|
1845
1857
|
}
|
|
1846
1858
|
|
|
1847
1859
|
/**
|
|
1848
|
-
*
|
|
1849
|
-
*
|
|
1860
|
+
* Delete local validation proof and trigger re-validation.
|
|
1861
|
+
* Called when Registry sends a revalidation request (infra version change).
|
|
1850
1862
|
* @private
|
|
1851
1863
|
*/
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
const channel = this.mqClient._transport.channel;
|
|
1859
|
-
const exchangeName = 'infrastructure.health.events';
|
|
1860
|
-
|
|
1861
|
-
await channel.assertExchange(exchangeName, 'fanout', { durable: true });
|
|
1862
|
-
|
|
1863
|
-
const q = await channel.assertQueue('', { exclusive: true, autoDelete: true });
|
|
1864
|
-
|
|
1865
|
-
await channel.bindQueue(q.queue, exchangeName, '');
|
|
1866
|
-
|
|
1867
|
-
const { consumerTag } = await channel.consume(q.queue, async (msg) => {
|
|
1868
|
-
if (!msg) return;
|
|
1869
|
-
|
|
1870
|
-
try {
|
|
1871
|
-
const event = JSON.parse(msg.content.toString());
|
|
1872
|
-
|
|
1873
|
-
if (event.type !== 'infra.invalidation') {
|
|
1874
|
-
channel.ack(msg);
|
|
1875
|
-
return;
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
this.logger?.info('[ServiceWrapper][infra.invalidation] Infrastructure change detected, scheduling re-validation', {
|
|
1879
|
-
fingerprint: event.fingerprint?.substring(0, 16) + '...',
|
|
1880
|
-
timestamp: event.timestamp
|
|
1881
|
-
});
|
|
1882
|
-
|
|
1883
|
-
// Delete local validation proof
|
|
1884
|
-
const fs = require('fs');
|
|
1885
|
-
const path = require('path');
|
|
1886
|
-
if (this.serviceRoot) {
|
|
1887
|
-
const proofPath = path.join(this.serviceRoot, 'conn-runtime', 'validation-proof.json');
|
|
1888
|
-
if (fs.existsSync(proofPath)) {
|
|
1889
|
-
fs.unlinkSync(proofPath);
|
|
1890
|
-
this.logger?.info('[ServiceWrapper][infra.invalidation] Deleted local validation proof', { proofPath });
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
this._revalidateWithBackoff();
|
|
1864
|
+
_deleteProofAndRevalidate() {
|
|
1865
|
+
const fs = require('fs');
|
|
1866
|
+
const path = require('path');
|
|
1895
1867
|
|
|
1896
|
-
|
|
1897
|
-
|
|
1868
|
+
if (this.serviceRoot) {
|
|
1869
|
+
const proofPath = path.join(this.serviceRoot, PROOF_RELATIVE_PATH);
|
|
1870
|
+
if (fs.existsSync(proofPath)) {
|
|
1871
|
+
fs.unlinkSync(proofPath);
|
|
1872
|
+
this.logger?.info('[ServiceWrapper][revalidate] Deleted local validation proof', { proofPath });
|
|
1898
1873
|
}
|
|
1874
|
+
}
|
|
1899
1875
|
|
|
1900
|
-
|
|
1901
|
-
});
|
|
1902
|
-
|
|
1903
|
-
this._infraInvalidationConsumerTag = consumerTag;
|
|
1904
|
-
this.logger?.info('[ServiceWrapper][infra.invalidation] Subscribed to infrastructure invalidation events', {
|
|
1905
|
-
exchange: exchangeName,
|
|
1906
|
-
queue: q.queue,
|
|
1907
|
-
consumerTag
|
|
1908
|
-
});
|
|
1876
|
+
this._revalidateWithBackoff();
|
|
1909
1877
|
}
|
|
1910
1878
|
|
|
1911
1879
|
/**
|
|
@@ -1915,7 +1883,8 @@ class ServiceWrapper {
|
|
|
1915
1883
|
*/
|
|
1916
1884
|
_revalidateWithBackoff() {
|
|
1917
1885
|
if (this._revalidationState === REVALIDATION_STATE.FAST_BACKOFF ||
|
|
1918
|
-
this._revalidationState === REVALIDATION_STATE.
|
|
1886
|
+
this._revalidationState === REVALIDATION_STATE.HOURLY_BACKOFF ||
|
|
1887
|
+
this._revalidationState === REVALIDATION_STATE.SLOW_BACKOFF) {
|
|
1919
1888
|
this.logger?.info('[ServiceWrapper][revalidation] Ignoring duplicate invalidation — re-validation cycle already active', {
|
|
1920
1889
|
state: this._revalidationState,
|
|
1921
1890
|
attempt: this._revalidationAttempt
|
|
@@ -1925,27 +1894,44 @@ class ServiceWrapper {
|
|
|
1925
1894
|
|
|
1926
1895
|
this._revalidationState = REVALIDATION_STATE.FAST_BACKOFF;
|
|
1927
1896
|
this._revalidationAttempt = 0;
|
|
1897
|
+
this._hourlyAttempts = 0;
|
|
1928
1898
|
this._scheduleRevalidationAttempt();
|
|
1929
1899
|
}
|
|
1930
1900
|
|
|
1931
1901
|
/**
|
|
1932
|
-
* Schedule the next re-validation attempt
|
|
1902
|
+
* Schedule the next re-validation attempt.
|
|
1903
|
+
* Backoff phases: 6 fast (30s-30min) -> hourly (max 24h) -> every 6h (forever).
|
|
1904
|
+
* At transition from hourly to slow: deregister + shutdown.
|
|
1933
1905
|
* @private
|
|
1934
1906
|
*/
|
|
1935
1907
|
_scheduleRevalidationAttempt() {
|
|
1936
1908
|
let delayMs;
|
|
1937
1909
|
|
|
1938
|
-
if (this._revalidationState === REVALIDATION_STATE.
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1910
|
+
if (this._revalidationState === REVALIDATION_STATE.FAST_BACKOFF) {
|
|
1911
|
+
if (this._revalidationAttempt < REVALIDATION_FAST_BACKOFF_MS.length) {
|
|
1912
|
+
delayMs = REVALIDATION_FAST_BACKOFF_MS[this._revalidationAttempt];
|
|
1913
|
+
} else {
|
|
1914
|
+
this._revalidationState = REVALIDATION_STATE.HOURLY_BACKOFF;
|
|
1915
|
+
this._hourlyAttempts = 0;
|
|
1916
|
+
delayMs = REVALIDATION_HOURLY_MS;
|
|
1917
|
+
this.logger?.warn(`[ServiceWrapper][revalidation] Fast retries exhausted (${this._revalidationAttempt} attempts) — switching to hourly retry`);
|
|
1918
|
+
}
|
|
1919
|
+
} else if (this._revalidationState === REVALIDATION_STATE.HOURLY_BACKOFF) {
|
|
1920
|
+
if (this._hourlyAttempts < REVALIDATION_HOURLY_MAX) {
|
|
1921
|
+
delayMs = REVALIDATION_HOURLY_MS;
|
|
1922
|
+
} else {
|
|
1923
|
+
this.logger?.error(
|
|
1924
|
+
`[ServiceWrapper][revalidation] All revalidation attempts exhausted ` +
|
|
1925
|
+
`(${REVALIDATION_FAST_BACKOFF_MS.length} fast + ${REVALIDATION_HOURLY_MAX} hourly) — deregistering and shutting down`
|
|
1926
|
+
);
|
|
1927
|
+
this._deregisterAndShutdown();
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1942
1930
|
} else {
|
|
1943
|
-
|
|
1944
|
-
delayMs = REVALIDATION_DAILY_MS;
|
|
1945
|
-
this.logger?.warn(`[ServiceWrapper][revalidation] Fast retries exhausted (${this._revalidationAttempt} attempts) - switching to daily retry`);
|
|
1931
|
+
delayMs = REVALIDATION_SLOW_MS;
|
|
1946
1932
|
}
|
|
1947
1933
|
|
|
1948
|
-
this.logger?.info(`[ServiceWrapper][revalidation] Next attempt in ${delayMs}
|
|
1934
|
+
this.logger?.info(`[ServiceWrapper][revalidation] Next attempt in ${Math.round(delayMs / 1000)}s`, {
|
|
1949
1935
|
attempt: this._revalidationAttempt + 1,
|
|
1950
1936
|
state: this._revalidationState,
|
|
1951
1937
|
delayMs
|
|
@@ -1954,16 +1940,37 @@ class ServiceWrapper {
|
|
|
1954
1940
|
this._revalidationTimer = setTimeout(() => this._executeRevalidation(), delayMs);
|
|
1955
1941
|
}
|
|
1956
1942
|
|
|
1943
|
+
/**
|
|
1944
|
+
* Deregister from Registry and exit. Container orchestrator will restart.
|
|
1945
|
+
* @private
|
|
1946
|
+
*/
|
|
1947
|
+
async _deregisterAndShutdown() {
|
|
1948
|
+
try {
|
|
1949
|
+
if (this.registryClient) {
|
|
1950
|
+
await this.registryClient.deregister();
|
|
1951
|
+
this.logger?.info('[ServiceWrapper][revalidation] Deregistered from registry');
|
|
1952
|
+
}
|
|
1953
|
+
} catch (err) {
|
|
1954
|
+
this.logger?.error('[ServiceWrapper][revalidation] Failed to deregister before shutdown', { error: err.message });
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
process.exit(1);
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1957
1960
|
/**
|
|
1958
1961
|
* Execute a single re-validation attempt.
|
|
1959
1962
|
* @private
|
|
1960
1963
|
*/
|
|
1961
1964
|
async _executeRevalidation() {
|
|
1962
1965
|
this._revalidationAttempt++;
|
|
1963
|
-
|
|
1966
|
+
if (this._revalidationState === REVALIDATION_STATE.HOURLY_BACKOFF) {
|
|
1967
|
+
this._hourlyAttempts++;
|
|
1968
|
+
}
|
|
1964
1969
|
const startTime = Date.now();
|
|
1965
1970
|
|
|
1966
|
-
this.logger?.info(`[ServiceWrapper][revalidation] Attempt ${this._revalidationAttempt}
|
|
1971
|
+
this.logger?.info(`[ServiceWrapper][revalidation] Attempt ${this._revalidationAttempt} starting`, {
|
|
1972
|
+
state: this._revalidationState
|
|
1973
|
+
});
|
|
1967
1974
|
|
|
1968
1975
|
try {
|
|
1969
1976
|
const serviceName = this.config.service?.name;
|
|
@@ -1986,7 +1993,6 @@ class ServiceWrapper {
|
|
|
1986
1993
|
|
|
1987
1994
|
this.validationProof = result.proof;
|
|
1988
1995
|
|
|
1989
|
-
// Re-register with fresh proof
|
|
1990
1996
|
if (this.registryClient) {
|
|
1991
1997
|
const serviceInfo = {
|
|
1992
1998
|
name: serviceName,
|
|
@@ -2005,26 +2011,15 @@ class ServiceWrapper {
|
|
|
2005
2011
|
this._revalidationTimer = null;
|
|
2006
2012
|
this.logger?.info(`[ServiceWrapper][revalidation] Success after ${this._revalidationAttempt} attempts (${elapsed}ms)`);
|
|
2007
2013
|
|
|
2008
|
-
// Clean up failure tracking file
|
|
2009
2014
|
this._clearValidationFailureFile();
|
|
2010
2015
|
|
|
2011
2016
|
} catch (error) {
|
|
2012
2017
|
const elapsed = Date.now() - startTime;
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
:
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
if (this._revalidationState === REVALIDATION_STATE.DAILY_BACKOFF) {
|
|
2020
|
-
this.logger?.warn(`[ServiceWrapper][revalidation] Daily retry attempt ${this._revalidationAttempt} - ${error.message}`);
|
|
2021
|
-
} else {
|
|
2022
|
-
this.logger?.warn(`[ServiceWrapper][revalidation] Attempt ${this._revalidationAttempt} failed: ${error.message} - next retry in ${nextDelay}ms`, {
|
|
2023
|
-
attempt: this._revalidationAttempt,
|
|
2024
|
-
elapsed,
|
|
2025
|
-
error: error.message
|
|
2026
|
-
});
|
|
2027
|
-
}
|
|
2018
|
+
this.logger?.warn(`[ServiceWrapper][revalidation] Attempt ${this._revalidationAttempt} failed (${elapsed}ms): ${error.message}`, {
|
|
2019
|
+
attempt: this._revalidationAttempt,
|
|
2020
|
+
state: this._revalidationState,
|
|
2021
|
+
error: error.message
|
|
2022
|
+
});
|
|
2028
2023
|
|
|
2029
2024
|
this._scheduleRevalidationAttempt();
|
|
2030
2025
|
}
|
|
@@ -2042,7 +2037,7 @@ class ServiceWrapper {
|
|
|
2042
2037
|
|
|
2043
2038
|
if (!this.serviceRoot) return null;
|
|
2044
2039
|
|
|
2045
|
-
const failurePath = path.join(this.serviceRoot,
|
|
2040
|
+
const failurePath = path.join(this.serviceRoot, RUNTIME_DIR, 'validation-failure.json');
|
|
2046
2041
|
try {
|
|
2047
2042
|
if (!fs.existsSync(failurePath)) return null;
|
|
2048
2043
|
return JSON.parse(fs.readFileSync(failurePath, 'utf8'));
|
|
@@ -2064,12 +2059,12 @@ class ServiceWrapper {
|
|
|
2064
2059
|
|
|
2065
2060
|
if (!this.serviceRoot) return;
|
|
2066
2061
|
|
|
2067
|
-
const
|
|
2068
|
-
if (!fs.existsSync(
|
|
2069
|
-
fs.mkdirSync(
|
|
2062
|
+
const rtDir = path.join(this.serviceRoot, RUNTIME_DIR);
|
|
2063
|
+
if (!fs.existsSync(rtDir)) {
|
|
2064
|
+
fs.mkdirSync(rtDir, { recursive: true });
|
|
2070
2065
|
}
|
|
2071
2066
|
|
|
2072
|
-
const failurePath = path.join(
|
|
2067
|
+
const failurePath = path.join(rtDir, 'validation-failure.json');
|
|
2073
2068
|
const data = {
|
|
2074
2069
|
lastAttempt: new Date().toISOString(),
|
|
2075
2070
|
attemptCount,
|
|
@@ -2090,7 +2085,7 @@ class ServiceWrapper {
|
|
|
2090
2085
|
|
|
2091
2086
|
if (!this.serviceRoot) return;
|
|
2092
2087
|
|
|
2093
|
-
const failurePath = path.join(this.serviceRoot,
|
|
2088
|
+
const failurePath = path.join(this.serviceRoot, RUNTIME_DIR, 'validation-failure.json');
|
|
2094
2089
|
try {
|
|
2095
2090
|
if (fs.existsSync(failurePath)) {
|
|
2096
2091
|
fs.unlinkSync(failurePath);
|
|
@@ -2214,15 +2209,15 @@ class ServiceWrapper {
|
|
|
2214
2209
|
const lastAttempt = new Date(failureData.lastAttempt).getTime();
|
|
2215
2210
|
const sinceLastAttempt = Date.now() - lastAttempt;
|
|
2216
2211
|
|
|
2217
|
-
if (sinceLastAttempt <
|
|
2218
|
-
const remainingMs =
|
|
2212
|
+
if (sinceLastAttempt < REVALIDATION_SLOW_MS) {
|
|
2213
|
+
const remainingMs = REVALIDATION_SLOW_MS - sinceLastAttempt;
|
|
2219
2214
|
const remainingMin = Math.round(remainingMs / 60000);
|
|
2220
2215
|
this.logger?.info(
|
|
2221
2216
|
`[ServiceWrapper][startup] Previous validation failures detected (${failureData.attemptCount} attempts since ${failureData.firstFailure}) - waiting ${remainingMin}min before retry`
|
|
2222
2217
|
);
|
|
2223
2218
|
await new Promise(resolve => setTimeout(resolve, Math.min(remainingMs, 300000)));
|
|
2224
2219
|
} else {
|
|
2225
|
-
this.logger?.info('[ServiceWrapper][startup] Previous failures detected, but
|
|
2220
|
+
this.logger?.info('[ServiceWrapper][startup] Previous failures detected, but cooldown elapsed - retrying fresh');
|
|
2226
2221
|
}
|
|
2227
2222
|
}
|
|
2228
2223
|
|