@crawlee/core 4.0.0-beta.104 → 4.0.0-beta.106
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 +85 -85
- package/autoscaling/client_load_signal.d.ts +1 -5
- package/autoscaling/client_load_signal.js +20 -20
- package/autoscaling/concurrency_system.d.ts +5 -20
- package/autoscaling/concurrency_system.js +81 -80
- package/autoscaling/cpu_load_signal.d.ts +1 -2
- package/autoscaling/cpu_load_signal.js +10 -10
- package/autoscaling/event_loop_load_signal.d.ts +1 -4
- package/autoscaling/event_loop_load_signal.js +18 -18
- package/autoscaling/load_signal.d.ts +1 -1
- package/autoscaling/load_signal.js +12 -11
- package/autoscaling/memory_load_signal.d.ts +3 -12
- package/autoscaling/memory_load_signal.js +40 -41
- package/autoscaling/snapshotter.d.ts +1 -4
- package/autoscaling/snapshotter.js +12 -12
- package/autoscaling/system_status.d.ts +1 -3
- package/autoscaling/system_status.js +11 -11
- package/configuration.d.ts +1 -1
- package/configuration.js +3 -3
- package/crawlers/context_pipeline.js +6 -6
- package/crawlers/crawler_commons.d.ts +6 -56
- package/crawlers/crawler_commons.js +1 -107
- package/crawlers/index.d.ts +1 -1
- package/crawlers/index.js +0 -1
- package/crawlers/statistics.d.ts +1 -8
- package/crawlers/statistics.js +45 -44
- package/events/event_manager.d.ts +1 -1
- package/events/event_manager.js +3 -3
- package/events/local_event_manager.d.ts +1 -1
- package/events/local_event_manager.js +3 -3
- package/log.js +5 -1
- package/memory-storage/memory-storage.d.ts +1 -5
- package/memory-storage/memory-storage.js +2 -2
- package/memory-storage/resource-clients/dataset.d.ts +1 -1
- package/memory-storage/resource-clients/dataset.js +6 -5
- package/memory-storage/resource-clients/key-value-store.d.ts +1 -1
- package/memory-storage/resource-clients/key-value-store.js +13 -12
- package/memory-storage/resource-clients/request-queue.d.ts +4 -23
- package/memory-storage/resource-clients/request-queue.js +59 -58
- package/owned_or_injected.d.ts +1 -3
- package/owned_or_injected.js +17 -17
- package/package.json +5 -5
- package/proxy_configuration.d.ts +1 -3
- package/proxy_configuration.js +8 -8
- package/recoverable_state.d.ts +1 -10
- package/recoverable_state.js +41 -41
- package/request.d.ts +1 -2
- package/request.js +10 -13
- package/router.d.ts +1 -4
- package/router.js +23 -23
- package/serialization.js +8 -9
- package/service_locator.d.ts +1 -10
- package/service_locator.js +48 -48
- package/session_pool/session.d.ts +1 -12
- package/session_pool/session.js +50 -50
- package/session_pool/session_pool.d.ts +2 -11
- package/session_pool/session_pool.js +59 -58
- package/storages/dataset.d.ts +12 -1
- package/storages/dataset.js +121 -22
- package/storages/index.d.ts +1 -1
- package/storages/index.js +1 -1
- package/storages/key_value_store.d.ts +19 -4
- package/storages/key_value_store.js +174 -48
- package/storages/request_dedup_cache.d.ts +1 -2
- package/storages/request_dedup_cache.js +9 -9
- package/storages/request_list.d.ts +2 -22
- package/storages/request_list.js +74 -73
- package/storages/request_manager_tandem.d.ts +1 -10
- package/storages/request_manager_tandem.js +27 -27
- package/storages/request_queue.d.ts +21 -18
- package/storages/request_queue.js +256 -53
- package/storages/sitemap_request_loader.d.ts +1 -44
- package/storages/sitemap_request_loader.js +87 -87
- package/storages/storage_instance_manager.d.ts +1 -2
- package/storages/storage_instance_manager.js +17 -17
- package/storages/storage_stats.d.ts +1 -1
- package/storages/storage_stats.js +4 -4
- package/storages/transaction.d.ts +252 -0
- package/storages/transaction.js +251 -0
- package/system-info/runtime.js +7 -7
- 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.
|
|
@@ -56,27 +56,27 @@ import { serviceLocator } from '../service_locator.js';
|
|
|
56
56
|
* @internal
|
|
57
57
|
*/
|
|
58
58
|
export class AutoscaledPool {
|
|
59
|
-
log;
|
|
59
|
+
#log;
|
|
60
60
|
// Configurable properties.
|
|
61
|
-
maybeRunIntervalMillis;
|
|
62
|
-
taskTimeoutMillis;
|
|
63
|
-
runTaskFunction;
|
|
64
|
-
isFinishedFunction;
|
|
65
|
-
isTaskReadyFunction;
|
|
66
|
-
concurrencySystem;
|
|
67
|
-
consumer;
|
|
61
|
+
#maybeRunIntervalMillis;
|
|
62
|
+
#taskTimeoutMillis;
|
|
63
|
+
#runTaskFunction;
|
|
64
|
+
#isFinishedFunction;
|
|
65
|
+
#isTaskReadyFunction;
|
|
66
|
+
#concurrencySystem;
|
|
67
|
+
#consumer;
|
|
68
68
|
// Internal properties.
|
|
69
|
-
isStopped = false;
|
|
70
|
-
resolve = null;
|
|
71
|
-
reject = null;
|
|
72
|
-
maybeRunInterval;
|
|
73
|
-
queryingIsTaskReady;
|
|
74
|
-
queryingIsFinished;
|
|
69
|
+
#isStopped = false;
|
|
70
|
+
#resolve = null;
|
|
71
|
+
#reject = null;
|
|
72
|
+
#maybeRunInterval;
|
|
73
|
+
#queryingIsTaskReady;
|
|
74
|
+
#queryingIsFinished;
|
|
75
75
|
/**
|
|
76
76
|
* This pool's *own* in-flight task count, as opposed to {@link AutoscaledPool.currentConcurrency}, which is the
|
|
77
77
|
* (possibly shared) governor's total. `pause()` and `maybeFinish()` care only about this pool draining.
|
|
78
78
|
*/
|
|
79
|
-
ownConcurrency = 0;
|
|
79
|
+
#ownConcurrency = 0;
|
|
80
80
|
constructor(options) {
|
|
81
81
|
ow(options, ow.object.exactShape({
|
|
82
82
|
runTaskFunction: ow.function,
|
|
@@ -89,19 +89,19 @@ export class AutoscaledPool {
|
|
|
89
89
|
consumer: ow.object.partialShape({ id: ow.string.nonEmpty }),
|
|
90
90
|
}));
|
|
91
91
|
const { runTaskFunction, isFinishedFunction, isTaskReadyFunction, maybeRunIntervalSecs = 0.5, taskTimeoutSecs = 0, log = serviceLocator.getLogger(), concurrencySystem, consumer, } = options;
|
|
92
|
-
this
|
|
92
|
+
this.#log = log.child({ prefix: 'AutoscaledPool' });
|
|
93
93
|
// Configurable properties.
|
|
94
|
-
this
|
|
95
|
-
this
|
|
96
|
-
this
|
|
97
|
-
this
|
|
98
|
-
this
|
|
99
|
-
this
|
|
100
|
-
this
|
|
94
|
+
this.#maybeRunIntervalMillis = maybeRunIntervalSecs * 1000;
|
|
95
|
+
this.#taskTimeoutMillis = taskTimeoutSecs * 1000;
|
|
96
|
+
this.#runTaskFunction = runTaskFunction;
|
|
97
|
+
this.#isFinishedFunction = isFinishedFunction;
|
|
98
|
+
this.#isTaskReadyFunction = isTaskReadyFunction;
|
|
99
|
+
this.#concurrencySystem = concurrencySystem;
|
|
100
|
+
this.#consumer = consumer;
|
|
101
101
|
// Internal properties.
|
|
102
|
-
this
|
|
103
|
-
this
|
|
104
|
-
this
|
|
102
|
+
this.#isStopped = false;
|
|
103
|
+
this.#resolve = null;
|
|
104
|
+
this.#reject = null;
|
|
105
105
|
this.maybeRunTask = this.maybeRunTask.bind(this);
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
@@ -112,18 +112,18 @@ export class AutoscaledPool {
|
|
|
112
112
|
* {@link ConcurrencySystem} its owner holds, never through the pool.
|
|
113
113
|
*/
|
|
114
114
|
get system() {
|
|
115
|
-
return this
|
|
115
|
+
return this.#concurrencySystem;
|
|
116
116
|
}
|
|
117
117
|
/** The estimated number of parallel tasks the governor can currently support. */
|
|
118
118
|
get desiredConcurrency() {
|
|
119
|
-
return this
|
|
119
|
+
return this.#concurrencySystem.desiredConcurrency;
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
122
|
* The number of parallel tasks currently booked against the governor. When it is shared, this counts every
|
|
123
123
|
* borrowing pool's tasks, not just this one's.
|
|
124
124
|
*/
|
|
125
125
|
get currentConcurrency() {
|
|
126
|
-
return this
|
|
126
|
+
return this.#concurrencySystem.currentConcurrency;
|
|
127
127
|
}
|
|
128
128
|
/**
|
|
129
129
|
* Runs the auto-scaled pool. Returns a promise that gets resolved or rejected once
|
|
@@ -135,26 +135,26 @@ export class AutoscaledPool {
|
|
|
135
135
|
async run() {
|
|
136
136
|
// Checked here, on an awaited path — the capacity queries inside the task loop run from intervals and
|
|
137
137
|
// `setImmediate`, where a throw would become an unhandled rejection and hang `run()` forever.
|
|
138
|
-
if (!this
|
|
138
|
+
if (!this.#concurrencySystem.isRunning) {
|
|
139
139
|
throw new CriticalError('The ConcurrencySystem this AutoscaledPool borrows has not been started, so system load would not be ' +
|
|
140
140
|
'monitored and the concurrency would never be adjusted. Whoever creates a ConcurrencySystem owns ' +
|
|
141
141
|
'its lifecycle: call `await concurrencySystem.start()` before running the pools or crawlers that ' +
|
|
142
142
|
'use it, and `await concurrencySystem.stop()` once they are all done.');
|
|
143
143
|
}
|
|
144
144
|
const poolPromise = new Promise((resolve, reject) => {
|
|
145
|
-
this
|
|
146
|
-
this
|
|
145
|
+
this.#resolve = resolve;
|
|
146
|
+
this.#reject = reject;
|
|
147
147
|
});
|
|
148
148
|
// This is here because if we scale down to let's say 1, then after each promise is finished
|
|
149
149
|
// this.maybeRunTask() doesn't trigger another one. So if that 1 instance gets stuck it results
|
|
150
150
|
// in the crawler getting stuck and even after scaling up it never triggers another promise.
|
|
151
|
-
this
|
|
151
|
+
this.#maybeRunInterval = betterSetInterval(this.maybeRunTask, this.#maybeRunIntervalMillis);
|
|
152
152
|
try {
|
|
153
153
|
await poolPromise;
|
|
154
154
|
}
|
|
155
155
|
finally {
|
|
156
156
|
// If resolve is null, the pool is already destroyed.
|
|
157
|
-
if (this
|
|
157
|
+
if (this.#resolve)
|
|
158
158
|
await this.destroy();
|
|
159
159
|
}
|
|
160
160
|
}
|
|
@@ -170,9 +170,9 @@ export class AutoscaledPool {
|
|
|
170
170
|
* parts of their asynchronous chains of commands will not execute.
|
|
171
171
|
*/
|
|
172
172
|
async abort() {
|
|
173
|
-
this
|
|
174
|
-
if (this
|
|
175
|
-
this
|
|
173
|
+
this.#isStopped = true;
|
|
174
|
+
if (this.#resolve) {
|
|
175
|
+
this.#resolve();
|
|
176
176
|
await this.destroy();
|
|
177
177
|
}
|
|
178
178
|
}
|
|
@@ -192,9 +192,9 @@ export class AutoscaledPool {
|
|
|
192
192
|
* it during a long pause, its owner can `stop()` and `start()` it again.
|
|
193
193
|
*/
|
|
194
194
|
async pause(timeoutSecs) {
|
|
195
|
-
if (this
|
|
195
|
+
if (this.#isStopped)
|
|
196
196
|
return;
|
|
197
|
-
this
|
|
197
|
+
this.#isStopped = true;
|
|
198
198
|
await new Promise((resolve, reject) => {
|
|
199
199
|
let timeout;
|
|
200
200
|
let interval;
|
|
@@ -208,14 +208,14 @@ export class AutoscaledPool {
|
|
|
208
208
|
}, timeoutSecs);
|
|
209
209
|
}
|
|
210
210
|
interval = setInterval(() => {
|
|
211
|
-
if (this
|
|
211
|
+
if (this.#ownConcurrency <= 0) {
|
|
212
212
|
// Clean up timeout and interval to prevent process hanging.
|
|
213
213
|
if (timeout)
|
|
214
214
|
clearTimeout(timeout);
|
|
215
215
|
clearInterval(interval);
|
|
216
216
|
resolve();
|
|
217
217
|
}
|
|
218
|
-
}, this
|
|
218
|
+
}, this.#maybeRunIntervalMillis);
|
|
219
219
|
});
|
|
220
220
|
}
|
|
221
221
|
/**
|
|
@@ -225,7 +225,7 @@ export class AutoscaledPool {
|
|
|
225
225
|
* Tasks will automatically start running again in `options.maybeRunIntervalSecs`.
|
|
226
226
|
*/
|
|
227
227
|
resume() {
|
|
228
|
-
this
|
|
228
|
+
this.#isStopped = false;
|
|
229
229
|
}
|
|
230
230
|
/**
|
|
231
231
|
* Explicitly check the queue for new tasks. The AutoscaledPool checks the queue for new tasks periodically,
|
|
@@ -238,27 +238,27 @@ export class AutoscaledPool {
|
|
|
238
238
|
* Starts a new task
|
|
239
239
|
* if the number of running tasks (current concurrency) is lower than desired concurrency
|
|
240
240
|
* and the system is not currently overloaded
|
|
241
|
-
* and
|
|
241
|
+
* and `isTaskReadyFunction()` returns true.
|
|
242
242
|
*
|
|
243
243
|
* It doesn't allow multiple concurrent runs of this method.
|
|
244
244
|
*/
|
|
245
245
|
async maybeRunTask(intervalCallback) {
|
|
246
|
-
this
|
|
246
|
+
this.#log.perf('Attempting to run a task.');
|
|
247
247
|
// Check if the function was invoked by the maybeRunInterval and use an empty function if not.
|
|
248
248
|
const done = intervalCallback || (() => { });
|
|
249
249
|
// Prevent starting a new task if:
|
|
250
250
|
// - the pool is paused or aborted
|
|
251
|
-
if (this
|
|
252
|
-
this
|
|
251
|
+
if (this.#isStopped) {
|
|
252
|
+
this.#log.perf('Task will not run. AutoscaledPool is stopped.');
|
|
253
253
|
return done();
|
|
254
254
|
}
|
|
255
255
|
// - we are already querying for a task.
|
|
256
|
-
if (this
|
|
257
|
-
this
|
|
256
|
+
if (this.#queryingIsTaskReady) {
|
|
257
|
+
this.#log.perf('Task will not run. Waiting for a ready task.');
|
|
258
258
|
return done();
|
|
259
259
|
}
|
|
260
260
|
// - the budget has room for us.
|
|
261
|
-
if (!this
|
|
261
|
+
if (!this.#concurrencySystem.hasCapacityForTask(this.#consumer)) {
|
|
262
262
|
done();
|
|
263
263
|
// A shared governor's budget can stay saturated by another pool indefinitely, so we still have to be able
|
|
264
264
|
// to notice that *this* pool has run out of work — `maybeFinish()` is the only thing that ever resolves
|
|
@@ -267,37 +267,37 @@ export class AutoscaledPool {
|
|
|
267
267
|
return this.maybeFinish();
|
|
268
268
|
}
|
|
269
269
|
// - a task is ready.
|
|
270
|
-
this
|
|
270
|
+
this.#queryingIsTaskReady = true;
|
|
271
271
|
let isTaskReady;
|
|
272
272
|
try {
|
|
273
|
-
this
|
|
274
|
-
isTaskReady = await this
|
|
273
|
+
this.#log.perf('Checking for ready tasks.');
|
|
274
|
+
isTaskReady = await this.#isTaskReadyFunction();
|
|
275
275
|
}
|
|
276
276
|
catch (e) {
|
|
277
277
|
const err = e;
|
|
278
|
-
this
|
|
278
|
+
this.#log.perf('Checking for ready tasks failed.');
|
|
279
279
|
// We might have already rejected this promise.
|
|
280
|
-
if (this
|
|
280
|
+
if (this.#reject) {
|
|
281
281
|
// No need to log all concurrent errors.
|
|
282
|
-
this
|
|
283
|
-
this
|
|
282
|
+
this.#log.exception(err, 'isTaskReadyFunction failed');
|
|
283
|
+
this.#reject(err);
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
finally {
|
|
287
|
-
this
|
|
287
|
+
this.#queryingIsTaskReady = false;
|
|
288
288
|
}
|
|
289
289
|
if (!isTaskReady) {
|
|
290
|
-
this
|
|
290
|
+
this.#log.perf('Task will not run. No tasks are ready.');
|
|
291
291
|
done();
|
|
292
292
|
// No tasks could mean that we're finished with all tasks.
|
|
293
293
|
return this.maybeFinish();
|
|
294
294
|
}
|
|
295
295
|
// - the budget still has room. Re-checked atomically, because another pool sharing the governor may have taken
|
|
296
296
|
// the last free slot while we awaited `isTaskReadyFunction` above.
|
|
297
|
-
if (!this
|
|
297
|
+
if (!this.#concurrencySystem.tryRegisterTaskStart(this.#consumer)) {
|
|
298
298
|
return done();
|
|
299
299
|
}
|
|
300
|
-
this
|
|
300
|
+
this.#ownConcurrency++;
|
|
301
301
|
try {
|
|
302
302
|
// Everything's fine. Run task.
|
|
303
303
|
// Try to run next task to build up concurrency,
|
|
@@ -306,73 +306,73 @@ export class AutoscaledPool {
|
|
|
306
306
|
// We need to restart interval here, so that it doesn't get blocked by a stalled task.
|
|
307
307
|
done();
|
|
308
308
|
// Execute the current task.
|
|
309
|
-
this
|
|
310
|
-
if (this
|
|
311
|
-
await addTimeoutToPromise(async () => this
|
|
309
|
+
this.#log.perf('Running a task.');
|
|
310
|
+
if (this.#taskTimeoutMillis > 0) {
|
|
311
|
+
await addTimeoutToPromise(async () => this.#runTaskFunction(), this.#taskTimeoutMillis, `runTaskFunction timed out after ${this.#taskTimeoutMillis / 1000} seconds.`);
|
|
312
312
|
}
|
|
313
313
|
else {
|
|
314
|
-
await this
|
|
314
|
+
await this.#runTaskFunction();
|
|
315
315
|
}
|
|
316
|
-
this
|
|
316
|
+
this.#log.perf('Task finished.');
|
|
317
317
|
// Run task after the previous one finished. Only on success: a failed task rejects the pool, and
|
|
318
318
|
// nudging the loop afterwards could start work on an already destroyed pool.
|
|
319
319
|
setImmediate(this.maybeRunTask);
|
|
320
320
|
}
|
|
321
321
|
catch (e) {
|
|
322
322
|
const err = e;
|
|
323
|
-
this
|
|
323
|
+
this.#log.perf('Running a task failed.');
|
|
324
324
|
// We might have already rejected this promise.
|
|
325
|
-
if (this
|
|
325
|
+
if (this.#reject) {
|
|
326
326
|
// No need to log all concurrent errors.
|
|
327
327
|
if (
|
|
328
328
|
// avoid reprinting the same critical error multiple times, as it will be printed by Nodejs at the end anyway
|
|
329
329
|
!(e instanceof CriticalError)) {
|
|
330
|
-
this
|
|
330
|
+
this.#log.exception(err, 'runTaskFunction failed.');
|
|
331
331
|
}
|
|
332
|
-
this
|
|
332
|
+
this.#reject(err);
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
finally {
|
|
336
|
-
this
|
|
337
|
-
this
|
|
336
|
+
this.#concurrencySystem.registerTaskEnd(this.#consumer);
|
|
337
|
+
this.#ownConcurrency--;
|
|
338
338
|
}
|
|
339
339
|
return undefined;
|
|
340
340
|
}
|
|
341
341
|
/**
|
|
342
|
-
* If there are no running tasks and
|
|
342
|
+
* If there are no running tasks and `isFinishedFunction()` returns true then closes
|
|
343
343
|
* the pool and resolves the pool's promise returned by the run() method.
|
|
344
344
|
*
|
|
345
345
|
* It doesn't allow multiple concurrent runs of this method.
|
|
346
346
|
*/
|
|
347
347
|
async maybeFinish() {
|
|
348
|
-
if (this
|
|
348
|
+
if (this.#queryingIsFinished)
|
|
349
349
|
return;
|
|
350
|
-
if (this
|
|
350
|
+
if (this.#ownConcurrency > 0)
|
|
351
351
|
return;
|
|
352
|
-
this
|
|
352
|
+
this.#queryingIsFinished = true;
|
|
353
353
|
try {
|
|
354
|
-
const isFinished = await this
|
|
355
|
-
if (isFinished && this
|
|
356
|
-
this
|
|
354
|
+
const isFinished = await this.#isFinishedFunction();
|
|
355
|
+
if (isFinished && this.#resolve)
|
|
356
|
+
this.#resolve();
|
|
357
357
|
}
|
|
358
358
|
catch (e) {
|
|
359
359
|
const err = e;
|
|
360
|
-
if (this
|
|
360
|
+
if (this.#reject) {
|
|
361
361
|
// No need to log all concurrent errors.
|
|
362
|
-
this
|
|
363
|
-
this
|
|
362
|
+
this.#log.exception(err, 'isFinishedFunction failed.');
|
|
363
|
+
this.#reject(err);
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
366
|
finally {
|
|
367
|
-
this
|
|
367
|
+
this.#queryingIsFinished = false;
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
370
|
/**
|
|
371
371
|
* Cleans up resources.
|
|
372
372
|
*/
|
|
373
373
|
async destroy() {
|
|
374
|
-
this
|
|
375
|
-
this
|
|
376
|
-
betterClearInterval(this
|
|
374
|
+
this.#resolve = null;
|
|
375
|
+
this.#reject = null;
|
|
376
|
+
betterClearInterval(this.#maybeRunInterval);
|
|
377
377
|
}
|
|
378
378
|
}
|
|
@@ -39,13 +39,9 @@ export interface ClientLoadSignalOptions {
|
|
|
39
39
|
* @category Scaling
|
|
40
40
|
*/
|
|
41
41
|
export declare class ClientLoadSignal implements LoadSignal {
|
|
42
|
+
#private;
|
|
42
43
|
readonly name = "clientInfo";
|
|
43
44
|
readonly overloadedRatio: number;
|
|
44
|
-
private readonly store;
|
|
45
|
-
private readonly intervalMillis;
|
|
46
|
-
private readonly maxErrors;
|
|
47
|
-
private interval?;
|
|
48
|
-
private client?;
|
|
49
45
|
constructor(options?: ClientLoadSignalOptions);
|
|
50
46
|
start(context: LoadSignalStartContext): Promise<void>;
|
|
51
47
|
stop(): Promise<void>;
|
|
@@ -16,35 +16,35 @@ const CLIENT_RATE_LIMIT_ERROR_RETRY_COUNT = 2;
|
|
|
16
16
|
export class ClientLoadSignal {
|
|
17
17
|
name = 'clientInfo';
|
|
18
18
|
overloadedRatio;
|
|
19
|
-
store = new SnapshotStore();
|
|
20
|
-
intervalMillis;
|
|
21
|
-
maxErrors;
|
|
22
|
-
interval;
|
|
23
|
-
client;
|
|
19
|
+
#store = new SnapshotStore();
|
|
20
|
+
#intervalMillis;
|
|
21
|
+
#maxErrors;
|
|
22
|
+
#interval;
|
|
23
|
+
#client;
|
|
24
24
|
constructor(options = {}) {
|
|
25
25
|
this.overloadedRatio = options.overloadedRatio ?? 0.3;
|
|
26
|
-
this
|
|
27
|
-
this
|
|
26
|
+
this.#intervalMillis = (options.snapshotIntervalSecs ?? 1) * 1000;
|
|
27
|
+
this.#maxErrors = options.maxErrors ?? 3;
|
|
28
28
|
this.handle = this.handle.bind(this);
|
|
29
29
|
}
|
|
30
30
|
async start(context) {
|
|
31
|
-
this
|
|
31
|
+
this.#store.useSampleWindow(context.maxSampleWindowMillis);
|
|
32
32
|
// A new session starts from a clean slate, or its first measurement diffs the error count against the previous
|
|
33
33
|
// session's — possibly against a different backend, since the client is resolved afresh just below.
|
|
34
|
-
this
|
|
34
|
+
this.#store.clear();
|
|
35
35
|
// Resolved here rather than in the constructor, where asking for the backend would instantiate a default one
|
|
36
36
|
// as a side effect - long before the crawler that owns the run has had a chance to register its own.
|
|
37
|
-
this
|
|
38
|
-
this
|
|
37
|
+
this.#client = serviceLocator.getStorageBackend();
|
|
38
|
+
this.#interval = betterSetInterval(this.handle, this.#intervalMillis);
|
|
39
39
|
}
|
|
40
40
|
async stop() {
|
|
41
|
-
if (this
|
|
42
|
-
betterClearInterval(this
|
|
43
|
-
this
|
|
44
|
-
this
|
|
41
|
+
if (this.#interval)
|
|
42
|
+
betterClearInterval(this.#interval);
|
|
43
|
+
this.#interval = undefined;
|
|
44
|
+
this.#client = undefined;
|
|
45
45
|
}
|
|
46
46
|
getSample(sampleDurationMillis) {
|
|
47
|
-
return this
|
|
47
|
+
return this.#store.getSample(sampleDurationMillis);
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* Records one snapshot, overloaded when rate-limit errors grew by more than the configured limit since the
|
|
@@ -53,21 +53,21 @@ export class ClientLoadSignal {
|
|
|
53
53
|
*/
|
|
54
54
|
handle(intervalCallback) {
|
|
55
55
|
const now = new Date();
|
|
56
|
-
const allErrorCounts = this
|
|
56
|
+
const allErrorCounts = this.#client?.stats?.rateLimitErrors ?? [];
|
|
57
57
|
const currentErrCount = allErrorCounts[CLIENT_RATE_LIMIT_ERROR_RETRY_COUNT] || 0;
|
|
58
58
|
const snapshot = {
|
|
59
59
|
createdAt: now,
|
|
60
60
|
isOverloaded: false,
|
|
61
61
|
rateLimitErrorCount: currentErrCount,
|
|
62
62
|
};
|
|
63
|
-
const all = this
|
|
63
|
+
const all = this.#store.getAll();
|
|
64
64
|
const previousSnapshot = all[all.length - 1];
|
|
65
65
|
if (previousSnapshot) {
|
|
66
66
|
const delta = currentErrCount - previousSnapshot.rateLimitErrorCount;
|
|
67
|
-
if (delta > this
|
|
67
|
+
if (delta > this.#maxErrors)
|
|
68
68
|
snapshot.isOverloaded = true;
|
|
69
69
|
}
|
|
70
|
-
this
|
|
70
|
+
this.#store.push(snapshot, now);
|
|
71
71
|
intervalCallback();
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -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
|
}
|