@crawlee/core 4.0.0-beta.98 → 4.0.0-rc.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/autoscaling/autoscaled_pool.d.ts +3 -21
- package/autoscaling/autoscaled_pool.js +105 -97
- package/autoscaling/concurrency_system.d.ts +7 -22
- package/autoscaling/concurrency_system.js +112 -100
- package/autoscaling/cpu_load_signal.d.ts +1 -2
- package/autoscaling/cpu_load_signal.js +11 -10
- package/autoscaling/event_loop_load_signal.d.ts +1 -4
- package/autoscaling/event_loop_load_signal.js +18 -18
- package/autoscaling/index.d.ts +1 -1
- package/autoscaling/index.js +1 -1
- package/autoscaling/load_signal.d.ts +8 -7
- package/autoscaling/load_signal.js +14 -12
- package/autoscaling/memory_load_signal.d.ts +3 -12
- package/autoscaling/memory_load_signal.js +41 -41
- package/autoscaling/snapshotter.d.ts +7 -10
- package/autoscaling/snapshotter.js +18 -18
- package/autoscaling/{client_load_signal.d.ts → storage_backend_load_signal.d.ts} +14 -17
- package/autoscaling/{client_load_signal.js → storage_backend_load_signal.js} +27 -27
- package/autoscaling/system_status.d.ts +9 -11
- package/autoscaling/system_status.js +13 -13
- package/configuration.d.ts +16 -16
- package/configuration.js +6 -6
- package/crawlers/context_pipeline.js +6 -6
- package/crawlers/crawler_commons.d.ts +14 -116
- package/crawlers/crawler_commons.js +1 -107
- package/crawlers/error_snapshotter.d.ts +2 -4
- package/crawlers/error_snapshotter.js +5 -5
- package/crawlers/index.d.ts +1 -1
- package/crawlers/index.js +0 -1
- package/crawlers/statistics.d.ts +164 -45
- package/crawlers/statistics.js +339 -153
- package/debug.js +4 -4
- package/enqueue_links/enqueue_links.d.ts +51 -159
- package/enqueue_links/enqueue_links.js +38 -231
- package/enqueue_links/shared.d.ts +34 -24
- package/enqueue_links/shared.js +54 -36
- package/errors.d.ts +43 -12
- package/errors.js +48 -9
- package/events/event_manager.d.ts +2 -2
- package/events/event_manager.js +7 -6
- package/events/local_event_manager.d.ts +1 -1
- package/events/local_event_manager.js +5 -5
- package/http.d.ts +9 -0
- package/http.js +28 -0
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/log.js +5 -1
- package/memory-storage/memory-storage.d.ts +3 -11
- package/memory-storage/memory-storage.js +42 -48
- package/memory-storage/resource-clients/dataset.d.ts +1 -1
- package/memory-storage/resource-clients/dataset.js +8 -13
- package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
- package/memory-storage/resource-clients/key-value-store.js +36 -38
- package/memory-storage/resource-clients/request-queue.d.ts +4 -23
- package/memory-storage/resource-clients/request-queue.js +68 -80
- package/owned_or_injected.d.ts +1 -3
- package/owned_or_injected.js +17 -17
- package/package.json +8 -9
- package/proxy_configuration.d.ts +1 -3
- package/proxy_configuration.js +18 -14
- package/recoverable_state.d.ts +80 -45
- package/recoverable_state.js +163 -75
- package/request.d.ts +3 -4
- package/request.js +57 -44
- package/router.d.ts +6 -10
- package/router.js +23 -23
- package/serialization.js +14 -13
- package/service_locator.d.ts +19 -10
- package/service_locator.js +57 -48
- package/session_pool/session.d.ts +3 -14
- package/session_pool/session.js +76 -74
- package/session_pool/session_pool.d.ts +6 -12
- package/session_pool/session_pool.js +85 -77
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +12 -1
- package/storages/dataset.js +133 -32
- package/storages/index.d.ts +2 -1
- package/storages/index.js +2 -1
- package/storages/key_value_store.d.ts +19 -4
- package/storages/key_value_store.js +204 -75
- package/storages/request_dedup_cache.d.ts +1 -2
- package/storages/request_dedup_cache.js +9 -9
- package/storages/request_list.d.ts +4 -23
- package/storages/request_list.js +103 -96
- package/storages/request_loader.d.ts +5 -0
- package/storages/request_manager_tandem.d.ts +1 -10
- package/storages/request_manager_tandem.js +27 -27
- package/storages/request_queue.d.ts +21 -21
- package/storages/request_queue.js +318 -177
- package/storages/sitemap_request_loader.d.ts +22 -70
- package/storages/sitemap_request_loader.js +122 -116
- package/storages/storage_instance_manager.d.ts +1 -4
- package/storages/storage_instance_manager.js +17 -19
- package/storages/storage_stats.d.ts +1 -1
- package/storages/storage_stats.js +4 -4
- package/storages/throttling_request_manager.d.ts +239 -0
- package/storages/throttling_request_manager.js +646 -0
- package/storages/transaction.d.ts +252 -0
- package/storages/transaction.js +251 -0
- package/storages/utils.d.ts +6 -3
- package/storages/utils.js +11 -2
- package/system-info/runtime.js +7 -7
- package/url.d.ts +9 -0
- package/url.js +11 -0
- package/validators.d.ts +22 -25
- package/validators.js +13 -25
- package/storages/access_checking.d.ts +0 -12
- package/storages/access_checking.js +0 -17
|
@@ -107,25 +107,7 @@ export interface AutoscaledPoolOptions extends TaskLoopPredicates {
|
|
|
107
107
|
* @internal
|
|
108
108
|
*/
|
|
109
109
|
export declare class AutoscaledPool {
|
|
110
|
-
private
|
|
111
|
-
private readonly maybeRunIntervalMillis;
|
|
112
|
-
private readonly taskTimeoutMillis;
|
|
113
|
-
private readonly runTaskFunction;
|
|
114
|
-
private readonly isFinishedFunction;
|
|
115
|
-
private readonly isTaskReadyFunction;
|
|
116
|
-
private readonly concurrencySystem;
|
|
117
|
-
private readonly consumer;
|
|
118
|
-
private isStopped;
|
|
119
|
-
private resolve;
|
|
120
|
-
private reject;
|
|
121
|
-
private maybeRunInterval;
|
|
122
|
-
private queryingIsTaskReady;
|
|
123
|
-
private queryingIsFinished;
|
|
124
|
-
/**
|
|
125
|
-
* This pool's *own* in-flight task count, as opposed to {@link AutoscaledPool.currentConcurrency}, which is the
|
|
126
|
-
* (possibly shared) governor's total. `pause()` and `maybeFinish()` care only about this pool draining.
|
|
127
|
-
*/
|
|
128
|
-
private ownConcurrency;
|
|
110
|
+
#private;
|
|
129
111
|
constructor(options: AutoscaledPoolOptions);
|
|
130
112
|
/**
|
|
131
113
|
* The governor backing this pool, as supplied to the constructor — exposed as the read-only
|
|
@@ -194,13 +176,13 @@ export declare class AutoscaledPool {
|
|
|
194
176
|
* Starts a new task
|
|
195
177
|
* if the number of running tasks (current concurrency) is lower than desired concurrency
|
|
196
178
|
* and the system is not currently overloaded
|
|
197
|
-
* and
|
|
179
|
+
* and `isTaskReadyFunction()` returns true.
|
|
198
180
|
*
|
|
199
181
|
* It doesn't allow multiple concurrent runs of this method.
|
|
200
182
|
*/
|
|
201
183
|
private maybeRunTask;
|
|
202
184
|
/**
|
|
203
|
-
* If there are no running tasks and
|
|
185
|
+
* If there are no running tasks and `isFinishedFunction()` returns true then closes
|
|
204
186
|
* the pool and resolves the pool's promise returned by the run() method.
|
|
205
187
|
*
|
|
206
188
|
* It doesn't allow multiple concurrent runs of this method.
|
|
@@ -1,8 +1,26 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { z } from 'zod';
|
|
2
2
|
import { addTimeoutToPromise } from '@apify/timeout';
|
|
3
3
|
import { betterClearInterval, betterSetInterval } from '@apify/utilities';
|
|
4
4
|
import { CriticalError } from '../errors.js';
|
|
5
5
|
import { serviceLocator } from '../service_locator.js';
|
|
6
|
+
import { parseArgument, schemas, validators } from '../validators.js';
|
|
7
|
+
// `schemas.anyObject` and `objectWithKeys`-based validators pass values through by reference
|
|
8
|
+
// (object schemas return a pruned plain copy), so class instances like loggers and the
|
|
9
|
+
// concurrency system keep their prototype.
|
|
10
|
+
const autoscaledPoolOptionsSchema = z.strictObject({
|
|
11
|
+
runTaskFunction: schemas.anyFunction,
|
|
12
|
+
isFinishedFunction: schemas.anyFunction,
|
|
13
|
+
isTaskReadyFunction: schemas.anyFunction,
|
|
14
|
+
maybeRunIntervalSecs: schemas.anyNumber
|
|
15
|
+
.refine((value) => value > 0, 'Expected a number greater than 0')
|
|
16
|
+
.default(0.5),
|
|
17
|
+
taskTimeoutSecs: schemas.anyNumber
|
|
18
|
+
.refine((value) => value >= 0, 'Expected a number greater than or equal to 0')
|
|
19
|
+
.default(0),
|
|
20
|
+
log: validators.logger.default(() => serviceLocator.getLogger()),
|
|
21
|
+
concurrencySystem: schemas.anyObject,
|
|
22
|
+
consumer: schemas.anyObject.refine((value) => typeof value.id === 'string' && value.id.length > 0, "Expected an object with a non-empty string 'id'"),
|
|
23
|
+
});
|
|
6
24
|
/**
|
|
7
25
|
* Manages a pool of asynchronous resource-intensive tasks that are executed in parallel.
|
|
8
26
|
* The pool only starts new tasks while its {@link IConcurrencySystem|concurrency system} reports free capacity —
|
|
@@ -56,52 +74,42 @@ import { serviceLocator } from '../service_locator.js';
|
|
|
56
74
|
* @internal
|
|
57
75
|
*/
|
|
58
76
|
export class AutoscaledPool {
|
|
59
|
-
log;
|
|
77
|
+
#log;
|
|
60
78
|
// Configurable properties.
|
|
61
|
-
maybeRunIntervalMillis;
|
|
62
|
-
taskTimeoutMillis;
|
|
63
|
-
runTaskFunction;
|
|
64
|
-
isFinishedFunction;
|
|
65
|
-
isTaskReadyFunction;
|
|
66
|
-
concurrencySystem;
|
|
67
|
-
consumer;
|
|
79
|
+
#maybeRunIntervalMillis;
|
|
80
|
+
#taskTimeoutMillis;
|
|
81
|
+
#runTaskFunction;
|
|
82
|
+
#isFinishedFunction;
|
|
83
|
+
#isTaskReadyFunction;
|
|
84
|
+
#concurrencySystem;
|
|
85
|
+
#consumer;
|
|
68
86
|
// Internal properties.
|
|
69
|
-
isStopped = false;
|
|
70
|
-
resolve = null;
|
|
71
|
-
reject = null;
|
|
72
|
-
maybeRunInterval;
|
|
73
|
-
queryingIsTaskReady;
|
|
74
|
-
queryingIsFinished;
|
|
87
|
+
#isStopped = false;
|
|
88
|
+
#resolve = null;
|
|
89
|
+
#reject = null;
|
|
90
|
+
#maybeRunInterval;
|
|
91
|
+
#queryingIsTaskReady;
|
|
92
|
+
#queryingIsFinished;
|
|
75
93
|
/**
|
|
76
94
|
* This pool's *own* in-flight task count, as opposed to {@link AutoscaledPool.currentConcurrency}, which is the
|
|
77
95
|
* (possibly shared) governor's total. `pause()` and `maybeFinish()` care only about this pool draining.
|
|
78
96
|
*/
|
|
79
|
-
ownConcurrency = 0;
|
|
97
|
+
#ownConcurrency = 0;
|
|
80
98
|
constructor(options) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
isFinishedFunction: ow.function,
|
|
84
|
-
isTaskReadyFunction: ow.function,
|
|
85
|
-
maybeRunIntervalSecs: ow.optional.number.greaterThan(0),
|
|
86
|
-
taskTimeoutSecs: ow.optional.number.greaterThanOrEqual(0),
|
|
87
|
-
log: ow.optional.object,
|
|
88
|
-
concurrencySystem: ow.object,
|
|
89
|
-
consumer: ow.object.partialShape({ id: ow.string.nonEmpty }),
|
|
90
|
-
}));
|
|
91
|
-
const { runTaskFunction, isFinishedFunction, isTaskReadyFunction, maybeRunIntervalSecs = 0.5, taskTimeoutSecs = 0, log = serviceLocator.getLogger(), concurrencySystem, consumer, } = options;
|
|
92
|
-
this.log = log.child({ prefix: 'AutoscaledPool' });
|
|
99
|
+
const { runTaskFunction, isFinishedFunction, isTaskReadyFunction, maybeRunIntervalSecs, taskTimeoutSecs, log, concurrencySystem, consumer, } = parseArgument(options, autoscaledPoolOptionsSchema, 'AutoscaledPoolOptions');
|
|
100
|
+
this.#log = log.child({ prefix: 'AutoscaledPool' });
|
|
93
101
|
// Configurable properties.
|
|
94
|
-
this
|
|
95
|
-
this
|
|
96
|
-
this
|
|
97
|
-
this
|
|
98
|
-
this
|
|
99
|
-
this
|
|
100
|
-
this
|
|
102
|
+
this.#maybeRunIntervalMillis = maybeRunIntervalSecs * 1000;
|
|
103
|
+
this.#taskTimeoutMillis = taskTimeoutSecs * 1000;
|
|
104
|
+
this.#runTaskFunction = runTaskFunction;
|
|
105
|
+
this.#isFinishedFunction = isFinishedFunction;
|
|
106
|
+
this.#isTaskReadyFunction = isTaskReadyFunction;
|
|
107
|
+
this.#concurrencySystem = concurrencySystem;
|
|
108
|
+
this.#consumer = consumer;
|
|
101
109
|
// Internal properties.
|
|
102
|
-
this
|
|
103
|
-
this
|
|
104
|
-
this
|
|
110
|
+
this.#isStopped = false;
|
|
111
|
+
this.#resolve = null;
|
|
112
|
+
this.#reject = null;
|
|
105
113
|
this.maybeRunTask = this.maybeRunTask.bind(this);
|
|
106
114
|
}
|
|
107
115
|
/**
|
|
@@ -112,18 +120,18 @@ export class AutoscaledPool {
|
|
|
112
120
|
* {@link ConcurrencySystem} its owner holds, never through the pool.
|
|
113
121
|
*/
|
|
114
122
|
get system() {
|
|
115
|
-
return this
|
|
123
|
+
return this.#concurrencySystem;
|
|
116
124
|
}
|
|
117
125
|
/** The estimated number of parallel tasks the governor can currently support. */
|
|
118
126
|
get desiredConcurrency() {
|
|
119
|
-
return this
|
|
127
|
+
return this.#concurrencySystem.desiredConcurrency;
|
|
120
128
|
}
|
|
121
129
|
/**
|
|
122
130
|
* The number of parallel tasks currently booked against the governor. When it is shared, this counts every
|
|
123
131
|
* borrowing pool's tasks, not just this one's.
|
|
124
132
|
*/
|
|
125
133
|
get currentConcurrency() {
|
|
126
|
-
return this
|
|
134
|
+
return this.#concurrencySystem.currentConcurrency;
|
|
127
135
|
}
|
|
128
136
|
/**
|
|
129
137
|
* Runs the auto-scaled pool. Returns a promise that gets resolved or rejected once
|
|
@@ -135,26 +143,26 @@ export class AutoscaledPool {
|
|
|
135
143
|
async run() {
|
|
136
144
|
// Checked here, on an awaited path — the capacity queries inside the task loop run from intervals and
|
|
137
145
|
// `setImmediate`, where a throw would become an unhandled rejection and hang `run()` forever.
|
|
138
|
-
if (!this
|
|
146
|
+
if (!this.#concurrencySystem.isRunning) {
|
|
139
147
|
throw new CriticalError('The ConcurrencySystem this AutoscaledPool borrows has not been started, so system load would not be ' +
|
|
140
148
|
'monitored and the concurrency would never be adjusted. Whoever creates a ConcurrencySystem owns ' +
|
|
141
149
|
'its lifecycle: call `await concurrencySystem.start()` before running the pools or crawlers that ' +
|
|
142
150
|
'use it, and `await concurrencySystem.stop()` once they are all done.');
|
|
143
151
|
}
|
|
144
152
|
const poolPromise = new Promise((resolve, reject) => {
|
|
145
|
-
this
|
|
146
|
-
this
|
|
153
|
+
this.#resolve = resolve;
|
|
154
|
+
this.#reject = reject;
|
|
147
155
|
});
|
|
148
156
|
// This is here because if we scale down to let's say 1, then after each promise is finished
|
|
149
157
|
// this.maybeRunTask() doesn't trigger another one. So if that 1 instance gets stuck it results
|
|
150
158
|
// in the crawler getting stuck and even after scaling up it never triggers another promise.
|
|
151
|
-
this
|
|
159
|
+
this.#maybeRunInterval = betterSetInterval(this.maybeRunTask, this.#maybeRunIntervalMillis);
|
|
152
160
|
try {
|
|
153
161
|
await poolPromise;
|
|
154
162
|
}
|
|
155
163
|
finally {
|
|
156
164
|
// If resolve is null, the pool is already destroyed.
|
|
157
|
-
if (this
|
|
165
|
+
if (this.#resolve)
|
|
158
166
|
await this.destroy();
|
|
159
167
|
}
|
|
160
168
|
}
|
|
@@ -170,9 +178,9 @@ export class AutoscaledPool {
|
|
|
170
178
|
* parts of their asynchronous chains of commands will not execute.
|
|
171
179
|
*/
|
|
172
180
|
async abort() {
|
|
173
|
-
this
|
|
174
|
-
if (this
|
|
175
|
-
this
|
|
181
|
+
this.#isStopped = true;
|
|
182
|
+
if (this.#resolve) {
|
|
183
|
+
this.#resolve();
|
|
176
184
|
await this.destroy();
|
|
177
185
|
}
|
|
178
186
|
}
|
|
@@ -192,9 +200,9 @@ export class AutoscaledPool {
|
|
|
192
200
|
* it during a long pause, its owner can `stop()` and `start()` it again.
|
|
193
201
|
*/
|
|
194
202
|
async pause(timeoutSecs) {
|
|
195
|
-
if (this
|
|
203
|
+
if (this.#isStopped)
|
|
196
204
|
return;
|
|
197
|
-
this
|
|
205
|
+
this.#isStopped = true;
|
|
198
206
|
await new Promise((resolve, reject) => {
|
|
199
207
|
let timeout;
|
|
200
208
|
let interval;
|
|
@@ -208,14 +216,14 @@ export class AutoscaledPool {
|
|
|
208
216
|
}, timeoutSecs);
|
|
209
217
|
}
|
|
210
218
|
interval = setInterval(() => {
|
|
211
|
-
if (this
|
|
219
|
+
if (this.#ownConcurrency <= 0) {
|
|
212
220
|
// Clean up timeout and interval to prevent process hanging.
|
|
213
221
|
if (timeout)
|
|
214
222
|
clearTimeout(timeout);
|
|
215
223
|
clearInterval(interval);
|
|
216
224
|
resolve();
|
|
217
225
|
}
|
|
218
|
-
}, this
|
|
226
|
+
}, this.#maybeRunIntervalMillis);
|
|
219
227
|
});
|
|
220
228
|
}
|
|
221
229
|
/**
|
|
@@ -225,7 +233,7 @@ export class AutoscaledPool {
|
|
|
225
233
|
* Tasks will automatically start running again in `options.maybeRunIntervalSecs`.
|
|
226
234
|
*/
|
|
227
235
|
resume() {
|
|
228
|
-
this
|
|
236
|
+
this.#isStopped = false;
|
|
229
237
|
}
|
|
230
238
|
/**
|
|
231
239
|
* Explicitly check the queue for new tasks. The AutoscaledPool checks the queue for new tasks periodically,
|
|
@@ -238,27 +246,27 @@ export class AutoscaledPool {
|
|
|
238
246
|
* Starts a new task
|
|
239
247
|
* if the number of running tasks (current concurrency) is lower than desired concurrency
|
|
240
248
|
* and the system is not currently overloaded
|
|
241
|
-
* and
|
|
249
|
+
* and `isTaskReadyFunction()` returns true.
|
|
242
250
|
*
|
|
243
251
|
* It doesn't allow multiple concurrent runs of this method.
|
|
244
252
|
*/
|
|
245
253
|
async maybeRunTask(intervalCallback) {
|
|
246
|
-
this
|
|
254
|
+
this.#log.perf('Attempting to run a task.');
|
|
247
255
|
// Check if the function was invoked by the maybeRunInterval and use an empty function if not.
|
|
248
256
|
const done = intervalCallback || (() => { });
|
|
249
257
|
// Prevent starting a new task if:
|
|
250
258
|
// - the pool is paused or aborted
|
|
251
|
-
if (this
|
|
252
|
-
this
|
|
259
|
+
if (this.#isStopped) {
|
|
260
|
+
this.#log.perf('Task will not run. AutoscaledPool is stopped.');
|
|
253
261
|
return done();
|
|
254
262
|
}
|
|
255
263
|
// - we are already querying for a task.
|
|
256
|
-
if (this
|
|
257
|
-
this
|
|
264
|
+
if (this.#queryingIsTaskReady) {
|
|
265
|
+
this.#log.perf('Task will not run. Waiting for a ready task.');
|
|
258
266
|
return done();
|
|
259
267
|
}
|
|
260
268
|
// - the budget has room for us.
|
|
261
|
-
if (!this
|
|
269
|
+
if (!this.#concurrencySystem.hasCapacityForTask(this.#consumer)) {
|
|
262
270
|
done();
|
|
263
271
|
// A shared governor's budget can stay saturated by another pool indefinitely, so we still have to be able
|
|
264
272
|
// to notice that *this* pool has run out of work — `maybeFinish()` is the only thing that ever resolves
|
|
@@ -267,37 +275,37 @@ export class AutoscaledPool {
|
|
|
267
275
|
return this.maybeFinish();
|
|
268
276
|
}
|
|
269
277
|
// - a task is ready.
|
|
270
|
-
this
|
|
278
|
+
this.#queryingIsTaskReady = true;
|
|
271
279
|
let isTaskReady;
|
|
272
280
|
try {
|
|
273
|
-
this
|
|
274
|
-
isTaskReady = await this
|
|
281
|
+
this.#log.perf('Checking for ready tasks.');
|
|
282
|
+
isTaskReady = await this.#isTaskReadyFunction();
|
|
275
283
|
}
|
|
276
284
|
catch (e) {
|
|
277
285
|
const err = e;
|
|
278
|
-
this
|
|
286
|
+
this.#log.perf('Checking for ready tasks failed.');
|
|
279
287
|
// We might have already rejected this promise.
|
|
280
|
-
if (this
|
|
288
|
+
if (this.#reject) {
|
|
281
289
|
// No need to log all concurrent errors.
|
|
282
|
-
this
|
|
283
|
-
this
|
|
290
|
+
this.#log.exception(err, 'isTaskReadyFunction failed');
|
|
291
|
+
this.#reject(err);
|
|
284
292
|
}
|
|
285
293
|
}
|
|
286
294
|
finally {
|
|
287
|
-
this
|
|
295
|
+
this.#queryingIsTaskReady = false;
|
|
288
296
|
}
|
|
289
297
|
if (!isTaskReady) {
|
|
290
|
-
this
|
|
298
|
+
this.#log.perf('Task will not run. No tasks are ready.');
|
|
291
299
|
done();
|
|
292
300
|
// No tasks could mean that we're finished with all tasks.
|
|
293
301
|
return this.maybeFinish();
|
|
294
302
|
}
|
|
295
303
|
// - the budget still has room. Re-checked atomically, because another pool sharing the governor may have taken
|
|
296
304
|
// the last free slot while we awaited `isTaskReadyFunction` above.
|
|
297
|
-
if (!this
|
|
305
|
+
if (!this.#concurrencySystem.tryRegisterTaskStart(this.#consumer)) {
|
|
298
306
|
return done();
|
|
299
307
|
}
|
|
300
|
-
this
|
|
308
|
+
this.#ownConcurrency++;
|
|
301
309
|
try {
|
|
302
310
|
// Everything's fine. Run task.
|
|
303
311
|
// Try to run next task to build up concurrency,
|
|
@@ -306,73 +314,73 @@ export class AutoscaledPool {
|
|
|
306
314
|
// We need to restart interval here, so that it doesn't get blocked by a stalled task.
|
|
307
315
|
done();
|
|
308
316
|
// Execute the current task.
|
|
309
|
-
this
|
|
310
|
-
if (this
|
|
311
|
-
await addTimeoutToPromise(async () => this
|
|
317
|
+
this.#log.perf('Running a task.');
|
|
318
|
+
if (this.#taskTimeoutMillis > 0) {
|
|
319
|
+
await addTimeoutToPromise(async () => this.#runTaskFunction(), this.#taskTimeoutMillis, `runTaskFunction timed out after ${this.#taskTimeoutMillis / 1000} seconds.`);
|
|
312
320
|
}
|
|
313
321
|
else {
|
|
314
|
-
await this
|
|
322
|
+
await this.#runTaskFunction();
|
|
315
323
|
}
|
|
316
|
-
this
|
|
324
|
+
this.#log.perf('Task finished.');
|
|
317
325
|
// Run task after the previous one finished. Only on success: a failed task rejects the pool, and
|
|
318
326
|
// nudging the loop afterwards could start work on an already destroyed pool.
|
|
319
327
|
setImmediate(this.maybeRunTask);
|
|
320
328
|
}
|
|
321
329
|
catch (e) {
|
|
322
330
|
const err = e;
|
|
323
|
-
this
|
|
331
|
+
this.#log.perf('Running a task failed.');
|
|
324
332
|
// We might have already rejected this promise.
|
|
325
|
-
if (this
|
|
333
|
+
if (this.#reject) {
|
|
326
334
|
// No need to log all concurrent errors.
|
|
327
335
|
if (
|
|
328
336
|
// avoid reprinting the same critical error multiple times, as it will be printed by Nodejs at the end anyway
|
|
329
337
|
!(e instanceof CriticalError)) {
|
|
330
|
-
this
|
|
338
|
+
this.#log.exception(err, 'runTaskFunction failed.');
|
|
331
339
|
}
|
|
332
|
-
this
|
|
340
|
+
this.#reject(err);
|
|
333
341
|
}
|
|
334
342
|
}
|
|
335
343
|
finally {
|
|
336
|
-
this
|
|
337
|
-
this
|
|
344
|
+
this.#concurrencySystem.registerTaskEnd(this.#consumer);
|
|
345
|
+
this.#ownConcurrency--;
|
|
338
346
|
}
|
|
339
347
|
return undefined;
|
|
340
348
|
}
|
|
341
349
|
/**
|
|
342
|
-
* If there are no running tasks and
|
|
350
|
+
* If there are no running tasks and `isFinishedFunction()` returns true then closes
|
|
343
351
|
* the pool and resolves the pool's promise returned by the run() method.
|
|
344
352
|
*
|
|
345
353
|
* It doesn't allow multiple concurrent runs of this method.
|
|
346
354
|
*/
|
|
347
355
|
async maybeFinish() {
|
|
348
|
-
if (this
|
|
356
|
+
if (this.#queryingIsFinished)
|
|
349
357
|
return;
|
|
350
|
-
if (this
|
|
358
|
+
if (this.#ownConcurrency > 0)
|
|
351
359
|
return;
|
|
352
|
-
this
|
|
360
|
+
this.#queryingIsFinished = true;
|
|
353
361
|
try {
|
|
354
|
-
const isFinished = await this
|
|
355
|
-
if (isFinished && this
|
|
356
|
-
this
|
|
362
|
+
const isFinished = await this.#isFinishedFunction();
|
|
363
|
+
if (isFinished && this.#resolve)
|
|
364
|
+
this.#resolve();
|
|
357
365
|
}
|
|
358
366
|
catch (e) {
|
|
359
367
|
const err = e;
|
|
360
|
-
if (this
|
|
368
|
+
if (this.#reject) {
|
|
361
369
|
// No need to log all concurrent errors.
|
|
362
|
-
this
|
|
363
|
-
this
|
|
370
|
+
this.#log.exception(err, 'isFinishedFunction failed.');
|
|
371
|
+
this.#reject(err);
|
|
364
372
|
}
|
|
365
373
|
}
|
|
366
374
|
finally {
|
|
367
|
-
this
|
|
375
|
+
this.#queryingIsFinished = false;
|
|
368
376
|
}
|
|
369
377
|
}
|
|
370
378
|
/**
|
|
371
379
|
* Cleans up resources.
|
|
372
380
|
*/
|
|
373
381
|
async destroy() {
|
|
374
|
-
this
|
|
375
|
-
this
|
|
376
|
-
betterClearInterval(this
|
|
382
|
+
this.#resolve = null;
|
|
383
|
+
this.#reject = null;
|
|
384
|
+
betterClearInterval(this.#maybeRunInterval);
|
|
377
385
|
}
|
|
378
386
|
}
|
|
@@ -53,8 +53,8 @@ export interface ConcurrencySystemOptions {
|
|
|
53
53
|
autoscaleIntervalSecs?: number;
|
|
54
54
|
/**
|
|
55
55
|
* The signals that tell the system whether the machine is overloaded: per-resource tuning for the built-in four
|
|
56
|
-
* (memory, event loop, CPU,
|
|
57
|
-
* your own. See {@link LoadSignalsOptions}.
|
|
56
|
+
* (memory, event loop, CPU, storage backend) plus any {@link LoadSignalsOptions.custom|`custom`}
|
|
57
|
+
* implementations of your own. See {@link LoadSignalsOptions}.
|
|
58
58
|
*/
|
|
59
59
|
loadSignals?: LoadSignalsOptions;
|
|
60
60
|
/**
|
|
@@ -152,30 +152,15 @@ export interface IConcurrencySystem {
|
|
|
152
152
|
* @category Scaling
|
|
153
153
|
*/
|
|
154
154
|
export declare class ConcurrencySystem implements IConcurrencySystem {
|
|
155
|
+
#private;
|
|
155
156
|
private readonly log;
|
|
156
157
|
private readonly desiredConcurrencyRatio;
|
|
157
158
|
private readonly scaleUpStepRatio;
|
|
158
159
|
private readonly scaleDownStepRatio;
|
|
159
|
-
private readonly loggingIntervalMillis;
|
|
160
|
-
private readonly autoscaleIntervalMillis;
|
|
161
160
|
private readonly maxTasksPerMinute;
|
|
162
|
-
private _minConcurrency;
|
|
163
|
-
private _maxConcurrency;
|
|
164
|
-
private _desiredConcurrency;
|
|
165
161
|
private _currentConcurrency;
|
|
166
|
-
private lastLoggingTime?;
|
|
167
|
-
private _tasksPerMinute;
|
|
168
162
|
private readonly snapshotter;
|
|
169
|
-
private readonly loadSignals;
|
|
170
163
|
private readonly systemStatus;
|
|
171
|
-
private autoscaleInterval?;
|
|
172
|
-
private tasksDonePerSecondInterval?;
|
|
173
|
-
/** Whether the snapshotter and autoscaling intervals are currently running. */
|
|
174
|
-
private running;
|
|
175
|
-
/** The in-flight (or completed) startup, memoized so concurrent `start()` calls await one boot. */
|
|
176
|
-
private startPromise?;
|
|
177
|
-
/** Set once per session, so a pool outliving `stop()` is reported once rather than every half second. */
|
|
178
|
-
private warnedAboutQueryWhileStopped;
|
|
179
164
|
constructor(options?: ConcurrencySystemOptions);
|
|
180
165
|
/**
|
|
181
166
|
* Gets the minimum number of tasks running in parallel.
|
|
@@ -270,14 +255,14 @@ export declare class ConcurrencySystem implements IConcurrencySystem {
|
|
|
270
255
|
* Evaluates the historical system status and scales the shared desired concurrency up or down accordingly. Driven
|
|
271
256
|
* by the autoscaling interval started in {@link ConcurrencySystem.start|`start()`}.
|
|
272
257
|
*/
|
|
273
|
-
private
|
|
258
|
+
private autoscale;
|
|
274
259
|
/**
|
|
275
260
|
* Scales the system up by increasing the desired concurrency by the scaleUpStepRatio.
|
|
276
261
|
*/
|
|
277
|
-
private
|
|
262
|
+
private scaleUp;
|
|
278
263
|
/**
|
|
279
264
|
* Scales the system down by decreasing the desired concurrency by the scaleDownStepRatio.
|
|
280
265
|
*/
|
|
281
|
-
private
|
|
282
|
-
private
|
|
266
|
+
private scaleDown;
|
|
267
|
+
private incrementTasksDonePerSecond;
|
|
283
268
|
}
|