@outputai/core 0.7.1-next.db8ddd7.0 → 0.7.1-next.ed233ce.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/bin/worker.sh +6 -0
- package/package.json +1 -1
- package/src/consts.js +0 -4
- package/src/errors.js +6 -2
- package/src/interface/evaluator.js +7 -20
- package/src/interface/evaluator.spec.js +117 -1
- package/src/interface/step.js +8 -9
- package/src/interface/step.spec.js +124 -0
- package/src/interface/validations/index.js +108 -0
- package/src/interface/validations/index.spec.js +182 -0
- package/src/interface/validations/schemas.js +113 -0
- package/src/interface/validations/schemas.spec.js +209 -0
- package/src/interface/webhook.js +1 -1
- package/src/interface/webhook.spec.js +1 -1
- package/src/interface/workflow.d.ts +10 -9
- package/src/interface/workflow.js +76 -164
- package/src/interface/workflow.spec.js +637 -521
- package/src/interface/workflow_activity_options.js +16 -0
- package/src/interface/workflow_utils.js +1 -1
- package/src/interface/zod_integration.spec.js +2 -2
- package/src/internal_utils/aggregations.js +0 -10
- package/src/internal_utils/aggregations.spec.js +1 -48
- package/src/internal_utils/errors.js +14 -8
- package/src/internal_utils/errors.spec.js +73 -27
- package/src/utils/index.d.ts +19 -0
- package/src/utils/utils.js +53 -0
- package/src/utils/utils.spec.js +105 -1
- package/src/worker/bundle.js +26 -0
- package/src/worker/bundle.spec.js +53 -0
- package/src/worker/bundler_options.js +1 -1
- package/src/worker/bundler_options.spec.js +1 -1
- package/src/worker/catalog_workflow/catalog_job.js +148 -0
- package/src/worker/catalog_workflow/catalog_job.spec.js +232 -0
- package/src/worker/check.js +24 -0
- package/src/worker/connection_monitor.js +112 -0
- package/src/worker/connection_monitor.spec.js +199 -0
- package/src/worker/index.js +146 -41
- package/src/worker/index.spec.js +281 -109
- package/src/worker/interceptors/activity.js +7 -24
- package/src/worker/interceptors/activity.spec.js +97 -66
- package/src/worker/interceptors/index.js +4 -7
- package/src/worker/interceptors/modules.js +15 -0
- package/src/worker/interceptors/workflow.js +6 -8
- package/src/worker/interceptors/workflow.spec.js +49 -42
- package/src/worker/interruption.js +33 -0
- package/src/worker/interruption.spec.js +86 -0
- package/src/worker/loader/activities.js +75 -0
- package/src/worker/loader/activities.spec.js +213 -0
- package/src/worker/loader/hooks.js +28 -0
- package/src/worker/loader/hooks.spec.js +64 -0
- package/src/worker/loader/matchers.js +46 -0
- package/src/worker/loader/matchers.spec.js +140 -0
- package/src/worker/{loader_tools.js → loader/tools.js} +19 -67
- package/src/worker/{loader_tools.spec.js → loader/tools.spec.js} +53 -85
- package/src/worker/loader/workflows.js +82 -0
- package/src/worker/loader/workflows.spec.js +256 -0
- package/src/worker/{setup_telemetry.js → telemetry.js} +9 -4
- package/src/worker/{setup_telemetry.spec.js → telemetry.spec.js} +3 -3
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +5 -109
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +31 -103
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +5 -6
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +11 -83
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +8 -11
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +9 -9
- package/src/interface/validations/runtime.js +0 -20
- package/src/interface/validations/runtime.spec.js +0 -29
- package/src/interface/validations/schema_utils.js +0 -8
- package/src/interface/validations/schema_utils.spec.js +0 -67
- package/src/interface/validations/static.js +0 -137
- package/src/interface/validations/static.spec.js +0 -397
- package/src/interface/workflow.replay_compatibility.spec.js +0 -254
- package/src/worker/loader.js +0 -202
- package/src/worker/loader.spec.js +0 -498
- package/src/worker/shutdown.js +0 -26
- package/src/worker/shutdown.spec.js +0 -82
- package/src/worker/start_catalog.js +0 -96
- package/src/worker/start_catalog.spec.js +0 -179
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { TemporalConnectionMonitor } from './connection_monitor.js';
|
|
3
|
+
|
|
4
|
+
const SERVING = 1;
|
|
5
|
+
const NOT_SERVING = 2;
|
|
6
|
+
const CHECK_TIMEOUT_MS = 50;
|
|
7
|
+
const CHECK_INTERVAL_MS = 100;
|
|
8
|
+
|
|
9
|
+
const { scheduledDelays, delayMock, mockLogger } = vi.hoisted( () => {
|
|
10
|
+
const scheduledDelays = [];
|
|
11
|
+
const delayMock = vi.fn( ( ms, value, options ) => new Promise( resolve => {
|
|
12
|
+
scheduledDelays.push( { ms, value, options, resolve } );
|
|
13
|
+
} ) );
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
scheduledDelays,
|
|
17
|
+
delayMock,
|
|
18
|
+
mockLogger: {
|
|
19
|
+
info: vi.fn(),
|
|
20
|
+
warn: vi.fn()
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
} );
|
|
24
|
+
|
|
25
|
+
vi.mock( 'node:timers/promises', () => ( { setTimeout: delayMock } ) );
|
|
26
|
+
vi.mock( '#logger', () => ( { createChildLogger: vi.fn( () => mockLogger ) } ) );
|
|
27
|
+
|
|
28
|
+
const createConnection = check => ( {
|
|
29
|
+
healthService: { check }
|
|
30
|
+
} );
|
|
31
|
+
|
|
32
|
+
const createMonitor = ( check, overrides = {} ) => new TemporalConnectionMonitor( createConnection( check ), {
|
|
33
|
+
checkIntervalMs: CHECK_INTERVAL_MS,
|
|
34
|
+
checkTimeoutMs: CHECK_TIMEOUT_MS,
|
|
35
|
+
...overrides
|
|
36
|
+
} );
|
|
37
|
+
|
|
38
|
+
const flushPromises = async () => Array
|
|
39
|
+
.from( { length: 10 } )
|
|
40
|
+
.reduce( promise => promise.then( () => Promise.resolve() ), Promise.resolve() );
|
|
41
|
+
|
|
42
|
+
const resolveNextDelay = ms => {
|
|
43
|
+
const index = scheduledDelays.findIndex( delay => delay.ms === ms );
|
|
44
|
+
expect( index ).not.toBe( -1 );
|
|
45
|
+
const [ scheduled ] = scheduledDelays.splice( index, 1 );
|
|
46
|
+
scheduled.resolve( scheduled.value );
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
describe( 'TemporalConnectionMonitor', () => {
|
|
50
|
+
beforeEach( () => {
|
|
51
|
+
vi.clearAllMocks();
|
|
52
|
+
scheduledDelays.length = 0;
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
it( 'logs healthy when the connection is serving', async () => {
|
|
56
|
+
const check = vi.fn().mockResolvedValue( { status: SERVING } );
|
|
57
|
+
const monitor = createMonitor( check );
|
|
58
|
+
|
|
59
|
+
const run = monitor.start();
|
|
60
|
+
await flushPromises();
|
|
61
|
+
|
|
62
|
+
expect( check ).toHaveBeenCalledWith( {} );
|
|
63
|
+
expect( mockLogger.info ).toHaveBeenCalledWith( 'Healthy' );
|
|
64
|
+
expect( delayMock ).toHaveBeenCalledWith( CHECK_TIMEOUT_MS, 0, { ref: false } );
|
|
65
|
+
expect( delayMock ).toHaveBeenCalledWith( CHECK_INTERVAL_MS, 0, { ref: false } );
|
|
66
|
+
expect( monitor.running ).toBe( true );
|
|
67
|
+
|
|
68
|
+
await monitor.stop();
|
|
69
|
+
await run;
|
|
70
|
+
|
|
71
|
+
expect( monitor.running ).toBe( false );
|
|
72
|
+
} );
|
|
73
|
+
|
|
74
|
+
it( 'logs transient timeout failures before retrying', async () => {
|
|
75
|
+
const check = vi.fn().mockReturnValue( new Promise( () => {} ) );
|
|
76
|
+
const monitor = createMonitor( check );
|
|
77
|
+
|
|
78
|
+
monitor.start();
|
|
79
|
+
resolveNextDelay( CHECK_TIMEOUT_MS );
|
|
80
|
+
await flushPromises();
|
|
81
|
+
|
|
82
|
+
expect( mockLogger.warn ).toHaveBeenCalledWith( 'Connection unhealthy', {
|
|
83
|
+
error: 'Connection health check timed out',
|
|
84
|
+
failures: 1
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
await monitor.stop();
|
|
88
|
+
} );
|
|
89
|
+
|
|
90
|
+
it( 'logs recovered after a transient failure succeeds', async () => {
|
|
91
|
+
const check = vi.fn()
|
|
92
|
+
.mockRejectedValueOnce( new Error( 'temporary outage' ) )
|
|
93
|
+
.mockResolvedValueOnce( { status: SERVING } );
|
|
94
|
+
const monitor = createMonitor( check );
|
|
95
|
+
|
|
96
|
+
monitor.start();
|
|
97
|
+
await flushPromises();
|
|
98
|
+
|
|
99
|
+
expect( mockLogger.warn ).toHaveBeenCalledWith( 'Connection unhealthy', {
|
|
100
|
+
error: 'temporary outage',
|
|
101
|
+
failures: 1
|
|
102
|
+
} );
|
|
103
|
+
|
|
104
|
+
resolveNextDelay( CHECK_INTERVAL_MS );
|
|
105
|
+
await flushPromises();
|
|
106
|
+
|
|
107
|
+
expect( mockLogger.info ).toHaveBeenCalledWith( 'Recovered' );
|
|
108
|
+
|
|
109
|
+
await monitor.stop();
|
|
110
|
+
} );
|
|
111
|
+
|
|
112
|
+
it( 'stores connection loss error and calls callback after max consecutive failures', async () => {
|
|
113
|
+
const error = new Error( 'connection refused' );
|
|
114
|
+
const check = vi.fn().mockRejectedValue( error );
|
|
115
|
+
const connectionLost = vi.fn();
|
|
116
|
+
const monitor = createMonitor( check );
|
|
117
|
+
|
|
118
|
+
monitor.onConnectionLost( connectionLost );
|
|
119
|
+
monitor.start();
|
|
120
|
+
|
|
121
|
+
await flushPromises();
|
|
122
|
+
resolveNextDelay( CHECK_INTERVAL_MS );
|
|
123
|
+
await flushPromises();
|
|
124
|
+
resolveNextDelay( CHECK_INTERVAL_MS );
|
|
125
|
+
await flushPromises();
|
|
126
|
+
|
|
127
|
+
expect( mockLogger.warn ).toHaveBeenCalledTimes( 3 );
|
|
128
|
+
expect( mockLogger.warn ).toHaveBeenCalledWith( 'Connection lost', {
|
|
129
|
+
error: 'connection refused',
|
|
130
|
+
failures: 3
|
|
131
|
+
} );
|
|
132
|
+
expect( connectionLost ).toHaveBeenCalledWith( error );
|
|
133
|
+
expect( monitor.connectionLossError ).toBe( error );
|
|
134
|
+
expect( monitor.running ).toBe( false );
|
|
135
|
+
} );
|
|
136
|
+
|
|
137
|
+
it( 'treats non-serving health status as a failure', async () => {
|
|
138
|
+
const check = vi.fn().mockResolvedValue( { status: NOT_SERVING } );
|
|
139
|
+
const monitor = createMonitor( check );
|
|
140
|
+
|
|
141
|
+
monitor.start();
|
|
142
|
+
await flushPromises();
|
|
143
|
+
|
|
144
|
+
expect( mockLogger.warn ).toHaveBeenCalledWith( 'Connection unhealthy', {
|
|
145
|
+
error: `Connection not serving (status ${NOT_SERVING})`,
|
|
146
|
+
failures: 1
|
|
147
|
+
} );
|
|
148
|
+
|
|
149
|
+
await monitor.stop();
|
|
150
|
+
} );
|
|
151
|
+
|
|
152
|
+
it( 'returns the same lifecycle promise when started more than once', async () => {
|
|
153
|
+
const check = vi.fn().mockReturnValue( new Promise( () => {} ) );
|
|
154
|
+
const monitor = createMonitor( check );
|
|
155
|
+
|
|
156
|
+
const firstRun = monitor.start();
|
|
157
|
+
const secondRun = monitor.start();
|
|
158
|
+
|
|
159
|
+
expect( secondRun ).toBe( firstRun );
|
|
160
|
+
expect( check ).toHaveBeenCalledOnce();
|
|
161
|
+
|
|
162
|
+
await monitor.stop();
|
|
163
|
+
} );
|
|
164
|
+
|
|
165
|
+
it( 'stops without calling connection lost callback for in-flight health checks', async () => {
|
|
166
|
+
const check = vi.fn().mockReturnValue( new Promise( () => {} ) );
|
|
167
|
+
const connectionLost = vi.fn();
|
|
168
|
+
const monitor = createMonitor( check, { maxFailures: 1 } );
|
|
169
|
+
|
|
170
|
+
monitor.onConnectionLost( connectionLost );
|
|
171
|
+
monitor.start();
|
|
172
|
+
|
|
173
|
+
expect( monitor.running ).toBe( true );
|
|
174
|
+
|
|
175
|
+
await monitor.stop();
|
|
176
|
+
|
|
177
|
+
expect( connectionLost ).not.toHaveBeenCalled();
|
|
178
|
+
expect( monitor.connectionLossError ).toBeNull();
|
|
179
|
+
expect( monitor.running ).toBe( false );
|
|
180
|
+
} );
|
|
181
|
+
|
|
182
|
+
it( 'applies timing and failure threshold overrides', async () => {
|
|
183
|
+
const error = new Error( 'fast failure' );
|
|
184
|
+
const check = vi.fn().mockRejectedValue( error );
|
|
185
|
+
const connectionLost = vi.fn();
|
|
186
|
+
const monitor = createMonitor( check, {
|
|
187
|
+
maxFailures: 1,
|
|
188
|
+
checkIntervalMs: 7,
|
|
189
|
+
checkTimeoutMs: 3
|
|
190
|
+
} );
|
|
191
|
+
|
|
192
|
+
monitor.onConnectionLost( connectionLost );
|
|
193
|
+
await monitor.start();
|
|
194
|
+
|
|
195
|
+
expect( delayMock ).toHaveBeenCalledWith( 3, 0, { ref: false } );
|
|
196
|
+
expect( delayMock ).not.toHaveBeenCalledWith( 7, 0, { ref: false } );
|
|
197
|
+
expect( connectionLost ).toHaveBeenCalledWith( error );
|
|
198
|
+
} );
|
|
199
|
+
} );
|
package/src/worker/index.js
CHANGED
|
@@ -1,55 +1,64 @@
|
|
|
1
1
|
import { Worker, NativeConnection } from '@temporalio/worker';
|
|
2
2
|
import * as configs from './configs.js';
|
|
3
|
-
import { loadActivities
|
|
3
|
+
import { loadActivities } from './loader/activities.js';
|
|
4
|
+
import { loadWorkflows } from './loader/workflows.js';
|
|
5
|
+
import { loadHooks } from './loader/hooks.js';
|
|
6
|
+
import { hashSourceCode } from './loader/tools.js';
|
|
4
7
|
import { sinks } from './sinks.js';
|
|
5
8
|
import { createCatalog } from './catalog_workflow/index.js';
|
|
6
9
|
import { init as initTracing } from '#tracing';
|
|
7
10
|
import { webpackConfigHook } from './bundler_options.js';
|
|
8
11
|
import { initInterceptors } from './interceptors/index.js';
|
|
9
12
|
import { createChildLogger } from '#logger';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
13
|
+
import { setupInterruptionHandler } from './interruption.js';
|
|
14
|
+
import { CatalogJob } from './catalog_workflow/catalog_job.js';
|
|
12
15
|
import { bootstrapFetchProxy } from './proxy.js';
|
|
13
16
|
import { messageBus } from '#bus';
|
|
14
17
|
import { BusEventType } from '#consts';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
18
|
+
import { setupTelemetry } from './telemetry.js';
|
|
19
|
+
import { TemporalConnectionMonitor } from './connection_monitor.js';
|
|
20
|
+
import { runOnce } from '#utils';
|
|
21
|
+
|
|
17
22
|
import './log_hooks.js';
|
|
18
23
|
|
|
19
24
|
const log = createChildLogger( 'Worker' );
|
|
20
25
|
|
|
26
|
+
const {
|
|
27
|
+
address,
|
|
28
|
+
apiKey,
|
|
29
|
+
namespace,
|
|
30
|
+
taskQueue,
|
|
31
|
+
grpcProxy,
|
|
32
|
+
maxConcurrentWorkflowTaskExecutions,
|
|
33
|
+
maxConcurrentActivityTaskExecutions,
|
|
34
|
+
maxCachedWorkflows,
|
|
35
|
+
maxConcurrentActivityTaskPolls,
|
|
36
|
+
maxConcurrentWorkflowTaskPolls
|
|
37
|
+
} = configs;
|
|
38
|
+
|
|
39
|
+
const state = {
|
|
40
|
+
connection: null,
|
|
41
|
+
connectionMonitor: null,
|
|
42
|
+
catalogJob: null,
|
|
43
|
+
workerError: null
|
|
44
|
+
};
|
|
45
|
+
|
|
21
46
|
// Get caller directory from command line arguments
|
|
22
47
|
const callerDir = process.argv[2];
|
|
23
48
|
|
|
24
|
-
|
|
25
|
-
const {
|
|
26
|
-
address,
|
|
27
|
-
apiKey,
|
|
28
|
-
namespace,
|
|
29
|
-
taskQueue,
|
|
30
|
-
grpcProxy,
|
|
31
|
-
maxConcurrentWorkflowTaskExecutions,
|
|
32
|
-
maxConcurrentActivityTaskExecutions,
|
|
33
|
-
maxCachedWorkflows,
|
|
34
|
-
maxConcurrentActivityTaskPolls,
|
|
35
|
-
maxConcurrentWorkflowTaskPolls
|
|
36
|
-
} = configs;
|
|
37
|
-
|
|
49
|
+
const execute = async () => {
|
|
38
50
|
log.info( 'Loading config...', { callerDir } );
|
|
39
51
|
await loadHooks( callerDir );
|
|
40
52
|
|
|
41
53
|
log.info( 'Loading workflows...', { callerDir } );
|
|
42
|
-
const workflows = await loadWorkflows( callerDir );
|
|
54
|
+
const { workflows, entrypoint: workflowsPath } = await loadWorkflows( callerDir );
|
|
43
55
|
|
|
44
56
|
log.info( 'Loading activities...', { callerDir } );
|
|
45
|
-
const activities = await loadActivities( callerDir, workflows );
|
|
57
|
+
const { activities } = await loadActivities( callerDir, workflows );
|
|
46
58
|
|
|
47
59
|
messageBus.emit( BusEventType.WORKER_BEFORE_START );
|
|
48
60
|
bootstrapFetchProxy();
|
|
49
61
|
|
|
50
|
-
log.info( 'Creating worker entry point...' );
|
|
51
|
-
const workflowsPath = createWorkflowsEntryPoint( workflows );
|
|
52
|
-
|
|
53
62
|
log.info( 'Initializing tracing...' );
|
|
54
63
|
await initTracing();
|
|
55
64
|
|
|
@@ -64,17 +73,23 @@ const callerDir = process.argv[2];
|
|
|
64
73
|
if ( proxy ) {
|
|
65
74
|
log.info( 'Using gRPC proxy', { targetHost: grpcProxy } );
|
|
66
75
|
}
|
|
67
|
-
|
|
76
|
+
state.connection = await NativeConnection.connect( { address, tls: Boolean( apiKey ), apiKey, proxy } );
|
|
77
|
+
|
|
78
|
+
log.info( 'Creating connection monitor...' );
|
|
79
|
+
state.connectionMonitor = new TemporalConnectionMonitor( state.connection );
|
|
80
|
+
|
|
81
|
+
log.info( 'Creating catalog job manager...' );
|
|
82
|
+
state.catalogJob = new CatalogJob( { connection: state.connection, namespace, catalog, catalogHash } );
|
|
68
83
|
|
|
69
84
|
log.info( 'Creating worker...' );
|
|
70
85
|
const worker = await Worker.create( {
|
|
71
|
-
connection,
|
|
86
|
+
connection: state.connection,
|
|
72
87
|
namespace,
|
|
73
88
|
taskQueue,
|
|
74
89
|
workflowsPath,
|
|
75
90
|
activities,
|
|
76
91
|
sinks,
|
|
77
|
-
interceptors: initInterceptors( { activities, workflows
|
|
92
|
+
interceptors: initInterceptors( { activities, workflows } ),
|
|
78
93
|
maxConcurrentWorkflowTaskExecutions,
|
|
79
94
|
maxConcurrentActivityTaskExecutions,
|
|
80
95
|
maxCachedWorkflows,
|
|
@@ -83,22 +98,112 @@ const callerDir = process.argv[2];
|
|
|
83
98
|
bundlerOptions: { webpackConfigHook }
|
|
84
99
|
} );
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
log.info( 'Setting up telemetry...' );
|
|
88
102
|
setupTelemetry( { worker } );
|
|
89
103
|
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
/**
|
|
105
|
+
* NOTE
|
|
106
|
+
* Temporal worker shutdown is a bit odd.
|
|
107
|
+
* worker.run() is an async job that only resolves when calling worker.shutdown().
|
|
108
|
+
* But worker.shutdown() is not async and returns nothing, so there is no way to await it.
|
|
109
|
+
* All code that needs to run after shutdown needs to be after `await worker.run()`.
|
|
110
|
+
*
|
|
111
|
+
* The following code needs to cover these scenarios:
|
|
112
|
+
* 1. Connection monitor detects connection loss
|
|
113
|
+
* 2. Catalog.run() has a failure
|
|
114
|
+
* 3. Interruption is received
|
|
115
|
+
* 4. Worker throws an error
|
|
116
|
+
*
|
|
117
|
+
* For each scenario all promises in the Promise.all() need to be completed via functions:
|
|
118
|
+
* connectionMonitor.stop(), catalogJob.interrupt(), worker.shutdown()
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Graceful shutdown
|
|
123
|
+
* Triggers the actions that will resolve all promises in the Promise.all(), so the code can resume
|
|
124
|
+
*/
|
|
125
|
+
const shutdown = runOnce( () => {
|
|
126
|
+
log.info( 'Shutdown started...' );
|
|
127
|
+
if ( worker.getStatus().runState === 'RUNNING' ) {
|
|
128
|
+
worker.shutdown();
|
|
129
|
+
}
|
|
130
|
+
state.connectionMonitor.stop();
|
|
131
|
+
state.catalogJob.interrupt();
|
|
132
|
+
} );
|
|
92
133
|
|
|
93
|
-
|
|
94
|
-
|
|
134
|
+
/** When receiving an interruption, call shutdown */
|
|
135
|
+
setupInterruptionHandler( shutdown );
|
|
95
136
|
|
|
96
|
-
|
|
137
|
+
/** When the connection is lost, call shutdown */
|
|
138
|
+
state.connectionMonitor.onConnectionLost( shutdown );
|
|
97
139
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
140
|
+
/** If the catalog job manager fails, call shutdown */
|
|
141
|
+
state.catalogJob.onError( shutdown );
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Runs the worker, connection monitor and catalogJob (ephemeral)
|
|
145
|
+
* None of these will reject in normal conditions. Errors need to be inspected later
|
|
146
|
+
* They will resolve only when calling the actions in shutdown(),
|
|
147
|
+
* except catalogJob, which can resolve by itself given a bit of time
|
|
148
|
+
*/
|
|
149
|
+
log.info( 'Running worker...' );
|
|
150
|
+
await Promise.all( [
|
|
151
|
+
// When the worker fails, store the error and call shutdown
|
|
152
|
+
worker.run().catch( error => {
|
|
153
|
+
state.workerError = error;
|
|
154
|
+
shutdown();
|
|
155
|
+
} ),
|
|
156
|
+
state.connectionMonitor.start(),
|
|
157
|
+
state.catalogJob.run()
|
|
158
|
+
] );
|
|
159
|
+
|
|
160
|
+
log.info( 'Worker terminated' );
|
|
161
|
+
|
|
162
|
+
/** After the Promise.all() is resolved, check which services had an error, since none rejects the promise */
|
|
163
|
+
const error =
|
|
164
|
+
state.connectionMonitor.connectionLossError ??
|
|
165
|
+
state.catalogJob.error ??
|
|
166
|
+
state.workerError;
|
|
167
|
+
|
|
168
|
+
/** If any error is found, throws it, so this process can exit with code=1 (failure) */
|
|
169
|
+
if ( error ) {
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
execute()
|
|
175
|
+
.finally( async () => {
|
|
176
|
+
/**
|
|
177
|
+
* This will make sure that if we had any uncaught failures, everything is tore down.
|
|
178
|
+
* Ignore any errors here in order to not mask actually errors from before and because at this point
|
|
179
|
+
* the code is already shutting down, so no need to throw anyway.
|
|
180
|
+
*
|
|
181
|
+
* worker.shutdown() is not tried here, because it cannot be awaited. By this point is safe to say
|
|
182
|
+
* that worker never started, already crashed or was stopped anyway.
|
|
183
|
+
*/
|
|
184
|
+
if ( state.connectionMonitor?.running ) {
|
|
185
|
+
log.info( 'Stopping connection monitor...' );
|
|
186
|
+
await state.connectionMonitor.stop()
|
|
187
|
+
.catch( e => log.warn( 'Connection monitor stop error', { error: e.message } ) );
|
|
188
|
+
}
|
|
189
|
+
if ( state.catalogJob?.running ) {
|
|
190
|
+
log.info( 'Interrupting catalog job...' );
|
|
191
|
+
await state.catalogJob.interrupt()
|
|
192
|
+
.catch( e => log.warn( 'Catalog job interruption error', { error: e.message } ) );
|
|
193
|
+
}
|
|
194
|
+
if ( state.connection ) {
|
|
195
|
+
log.info( 'Closing connection...' );
|
|
196
|
+
await state.connection.close()
|
|
197
|
+
.catch( e => log.warn( 'Connection close error', { error: e.message } ) );
|
|
198
|
+
}
|
|
199
|
+
} )
|
|
200
|
+
.then( () => log.info( 'Bye' ) )
|
|
201
|
+
.catch( error => {
|
|
202
|
+
log.error( 'Fatal error', { error: error.message, stack: error.stack } );
|
|
203
|
+
|
|
204
|
+
messageBus.emit( BusEventType.RUNTIME_ERROR, { error } );
|
|
205
|
+
|
|
206
|
+
const timeToFlushEvent = configs.processFailureShutdownDelay;
|
|
207
|
+
log.info( `Exiting in ${timeToFlushEvent}ms` );
|
|
208
|
+
setTimeout( () => process.exit( 1 ), timeToFlushEvent );
|
|
209
|
+
} );
|