@crawlee/core 4.0.0-beta.13 → 4.0.0-beta.131
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/README.md +17 -13
- package/autoscaling/autoscaled_pool.d.ts +67 -172
- package/autoscaling/autoscaled_pool.js +182 -329
- package/autoscaling/concurrency_system.d.ts +268 -0
- package/autoscaling/concurrency_system.js +362 -0
- package/autoscaling/cpu_load_signal.d.ts +43 -0
- package/autoscaling/cpu_load_signal.js +47 -0
- package/autoscaling/event_loop_load_signal.d.ts +51 -0
- package/autoscaling/event_loop_load_signal.js +60 -0
- package/autoscaling/index.d.ts +6 -1
- package/autoscaling/index.js +6 -1
- package/autoscaling/load_signal.d.ts +100 -0
- package/autoscaling/load_signal.js +105 -0
- package/autoscaling/memory_load_signal.d.ts +47 -0
- package/autoscaling/memory_load_signal.js +106 -0
- package/autoscaling/snapshotter.d.ts +58 -163
- package/autoscaling/snapshotter.js +45 -263
- package/autoscaling/storage_backend_load_signal.d.ts +56 -0
- package/autoscaling/storage_backend_load_signal.js +73 -0
- package/autoscaling/system_status.d.ts +67 -89
- package/autoscaling/system_status.js +92 -122
- package/autoscaling/weighted_avg.d.ts +5 -0
- package/autoscaling/weighted_avg.js +14 -0
- package/byte_utils.d.ts +17 -0
- package/byte_utils.js +42 -0
- package/configuration.d.ts +96 -223
- package/configuration.js +170 -222
- package/cookie_utils.d.ts +3 -2
- package/cookie_utils.js +18 -7
- package/crawlers/context_pipeline.d.ts +10 -1
- package/crawlers/context_pipeline.js +31 -8
- package/crawlers/crawler_commons.d.ts +90 -129
- package/crawlers/crawler_commons.js +1 -116
- package/crawlers/error_snapshotter.d.ts +2 -5
- package/crawlers/error_snapshotter.js +7 -8
- package/crawlers/error_tracker.d.ts +0 -1
- package/crawlers/error_tracker.js +0 -1
- package/crawlers/index.d.ts +1 -3
- package/crawlers/index.js +0 -3
- package/crawlers/internals/types.d.ts +0 -1
- package/crawlers/internals/types.js +0 -1
- package/crawlers/statistics.d.ts +187 -63
- package/crawlers/statistics.js +354 -164
- package/debug.d.ts +36 -0
- package/debug.js +70 -0
- package/enqueue_links/enqueue_links.d.ts +68 -154
- package/enqueue_links/enqueue_links.js +38 -210
- package/enqueue_links/index.d.ts +0 -1
- package/enqueue_links/index.js +0 -1
- package/enqueue_links/shared.d.ts +52 -26
- package/enqueue_links/shared.js +115 -66
- package/errors.d.ts +72 -4
- package/errors.js +89 -5
- package/events/event_manager.d.ts +35 -9
- package/events/event_manager.js +12 -13
- package/events/index.d.ts +0 -1
- package/events/index.js +0 -1
- package/events/local_event_manager.d.ts +15 -3
- package/events/local_event_manager.js +39 -13
- package/http.d.ts +9 -0
- package/http.js +28 -0
- package/index.d.ts +7 -4
- package/index.js +6 -3
- package/iterables.d.ts +79 -0
- package/iterables.js +134 -0
- package/log.d.ts +82 -3
- package/log.js +106 -1
- package/memory-storage/consts.d.ts +4 -0
- package/memory-storage/consts.js +4 -0
- package/memory-storage/index.d.ts +1 -0
- package/memory-storage/index.js +1 -0
- package/memory-storage/memory-storage.d.ts +38 -0
- package/memory-storage/memory-storage.js +130 -0
- package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
- package/memory-storage/resource-clients/common/base-client.js +6 -0
- package/memory-storage/resource-clients/dataset.d.ts +40 -0
- package/memory-storage/resource-clients/dataset.js +108 -0
- package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
- package/memory-storage/resource-clients/key-value-store.js +201 -0
- package/memory-storage/resource-clients/request-queue.d.ts +77 -0
- package/memory-storage/resource-clients/request-queue.js +409 -0
- package/memory-storage/utils.d.ts +16 -0
- package/memory-storage/utils.js +41 -0
- package/owned_or_injected.d.ts +58 -0
- package/owned_or_injected.js +98 -0
- package/package.json +14 -13
- package/proxy_configuration.d.ts +24 -132
- package/proxy_configuration.js +32 -147
- package/recoverable_state.d.ts +155 -0
- package/recoverable_state.js +231 -0
- package/request.d.ts +80 -16
- package/request.js +147 -68
- package/router.d.ts +193 -21
- package/router.js +188 -43
- package/serialization.d.ts +0 -1
- package/serialization.js +15 -15
- package/service_locator.d.ts +165 -0
- package/service_locator.js +253 -0
- package/session_pool/consts.d.ts +1 -2
- package/session_pool/consts.js +1 -2
- package/session_pool/errors.d.ts +0 -1
- package/session_pool/errors.js +0 -1
- package/session_pool/fingerprint.d.ts +9 -0
- package/session_pool/fingerprint.js +30 -0
- package/session_pool/index.d.ts +0 -2
- package/session_pool/index.js +0 -2
- package/session_pool/session.d.ts +35 -88
- package/session_pool/session.js +101 -159
- package/session_pool/session_pool.d.ts +73 -91
- package/session_pool/session_pool.js +172 -165
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +114 -54
- package/storages/dataset.js +291 -148
- package/storages/index.d.ts +10 -8
- package/storages/index.js +8 -8
- package/storages/key_value_store.d.ts +185 -42
- package/storages/key_value_store.js +445 -169
- package/storages/key_value_store_codec.d.ts +32 -0
- package/storages/key_value_store_codec.js +113 -0
- package/storages/request_dedup_cache.d.ts +22 -0
- package/storages/request_dedup_cache.js +48 -0
- package/storages/request_list.d.ts +52 -115
- package/storages/request_list.js +183 -152
- package/storages/request_loader.d.ts +101 -0
- package/storages/request_loader.js +1 -0
- package/storages/request_manager.d.ts +33 -0
- package/storages/request_manager.js +1 -0
- package/storages/request_manager_tandem.d.ts +97 -0
- package/storages/request_manager_tandem.js +197 -0
- package/storages/request_queue.d.ts +290 -47
- package/storages/request_queue.js +762 -216
- package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +45 -89
- package/storages/sitemap_request_loader.js +438 -0
- package/storages/storage_instance_manager.d.ts +87 -0
- package/storages/storage_instance_manager.js +256 -0
- package/storages/storage_stats.d.ts +48 -0
- package/storages/storage_stats.js +29 -0
- 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 +59 -11
- package/storages/utils.js +75 -15
- package/system-info/cpu-info.d.ts +67 -0
- package/system-info/cpu-info.js +216 -0
- package/system-info/memory-info.d.ts +31 -0
- package/system-info/memory-info.js +115 -0
- package/system-info/ps-tree.d.ts +17 -0
- package/system-info/ps-tree.js +144 -0
- package/system-info/runtime.d.ts +14 -0
- package/system-info/runtime.js +80 -0
- package/typedefs.d.ts +0 -6
- package/typedefs.js +0 -1
- package/url.d.ts +9 -0
- package/url.js +11 -0
- package/validators.d.ts +22 -18
- package/validators.js +13 -18
- package/autoscaling/autoscaled_pool.d.ts.map +0 -1
- package/autoscaling/autoscaled_pool.js.map +0 -1
- package/autoscaling/index.d.ts.map +0 -1
- package/autoscaling/index.js.map +0 -1
- package/autoscaling/snapshotter.d.ts.map +0 -1
- package/autoscaling/snapshotter.js.map +0 -1
- package/autoscaling/system_status.d.ts.map +0 -1
- package/autoscaling/system_status.js.map +0 -1
- package/configuration.d.ts.map +0 -1
- package/configuration.js.map +0 -1
- package/cookie_utils.d.ts.map +0 -1
- package/cookie_utils.js.map +0 -1
- package/crawlers/context_pipeline.d.ts.map +0 -1
- package/crawlers/context_pipeline.js.map +0 -1
- package/crawlers/crawler_commons.d.ts.map +0 -1
- package/crawlers/crawler_commons.js.map +0 -1
- package/crawlers/crawler_utils.d.ts +0 -10
- package/crawlers/crawler_utils.d.ts.map +0 -1
- package/crawlers/crawler_utils.js +0 -12
- package/crawlers/crawler_utils.js.map +0 -1
- package/crawlers/error_snapshotter.d.ts.map +0 -1
- package/crawlers/error_snapshotter.js.map +0 -1
- package/crawlers/error_tracker.d.ts.map +0 -1
- package/crawlers/error_tracker.js.map +0 -1
- package/crawlers/index.d.ts.map +0 -1
- package/crawlers/index.js.map +0 -1
- package/crawlers/internals/types.d.ts.map +0 -1
- package/crawlers/internals/types.js.map +0 -1
- package/crawlers/statistics.d.ts.map +0 -1
- package/crawlers/statistics.js.map +0 -1
- package/enqueue_links/enqueue_links.d.ts.map +0 -1
- package/enqueue_links/enqueue_links.js.map +0 -1
- package/enqueue_links/index.d.ts.map +0 -1
- package/enqueue_links/index.js.map +0 -1
- package/enqueue_links/shared.d.ts.map +0 -1
- package/enqueue_links/shared.js.map +0 -1
- package/errors.d.ts.map +0 -1
- package/errors.js.map +0 -1
- package/events/event_manager.d.ts.map +0 -1
- package/events/event_manager.js.map +0 -1
- package/events/index.d.ts.map +0 -1
- package/events/index.js.map +0 -1
- package/events/local_event_manager.d.ts.map +0 -1
- package/events/local_event_manager.js.map +0 -1
- package/http_clients/base-http-client.d.ts +0 -140
- package/http_clients/base-http-client.d.ts.map +0 -1
- package/http_clients/base-http-client.js +0 -40
- package/http_clients/base-http-client.js.map +0 -1
- package/http_clients/form-data-like.d.ts +0 -67
- package/http_clients/form-data-like.d.ts.map +0 -1
- package/http_clients/form-data-like.js +0 -5
- package/http_clients/form-data-like.js.map +0 -1
- package/http_clients/got-scraping-http-client.d.ts +0 -20
- package/http_clients/got-scraping-http-client.d.ts.map +0 -1
- package/http_clients/got-scraping-http-client.js +0 -82
- package/http_clients/got-scraping-http-client.js.map +0 -1
- package/http_clients/index.d.ts +0 -3
- package/http_clients/index.d.ts.map +0 -1
- package/http_clients/index.js +0 -3
- package/http_clients/index.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/log.d.ts.map +0 -1
- package/log.js.map +0 -1
- package/proxy_configuration.d.ts.map +0 -1
- package/proxy_configuration.js.map +0 -1
- package/request.d.ts.map +0 -1
- package/request.js.map +0 -1
- package/router.d.ts.map +0 -1
- package/router.js.map +0 -1
- package/serialization.d.ts.map +0 -1
- package/serialization.js.map +0 -1
- package/session_pool/consts.d.ts.map +0 -1
- package/session_pool/consts.js.map +0 -1
- package/session_pool/errors.d.ts.map +0 -1
- package/session_pool/errors.js.map +0 -1
- package/session_pool/events.d.ts +0 -3
- package/session_pool/events.d.ts.map +0 -1
- package/session_pool/events.js +0 -3
- package/session_pool/events.js.map +0 -1
- package/session_pool/index.d.ts.map +0 -1
- package/session_pool/index.js.map +0 -1
- package/session_pool/session.d.ts.map +0 -1
- package/session_pool/session.js.map +0 -1
- package/session_pool/session_pool.d.ts.map +0 -1
- package/session_pool/session_pool.js.map +0 -1
- package/storages/access_checking.d.ts +0 -13
- package/storages/access_checking.d.ts.map +0 -1
- package/storages/access_checking.js +0 -14
- package/storages/access_checking.js.map +0 -1
- package/storages/dataset.d.ts.map +0 -1
- package/storages/dataset.js.map +0 -1
- package/storages/index.d.ts.map +0 -1
- package/storages/index.js.map +0 -1
- package/storages/key_value_store.d.ts.map +0 -1
- package/storages/key_value_store.js.map +0 -1
- package/storages/request_list.d.ts.map +0 -1
- package/storages/request_list.js.map +0 -1
- package/storages/request_provider.d.ts +0 -308
- package/storages/request_provider.d.ts.map +0 -1
- package/storages/request_provider.js +0 -555
- package/storages/request_provider.js.map +0 -1
- package/storages/request_queue.d.ts.map +0 -1
- package/storages/request_queue.js.map +0 -1
- package/storages/request_queue_v2.d.ts +0 -87
- package/storages/request_queue_v2.d.ts.map +0 -1
- package/storages/request_queue_v2.js +0 -438
- package/storages/request_queue_v2.js.map +0 -1
- package/storages/sitemap_request_list.d.ts.map +0 -1
- package/storages/sitemap_request_list.js +0 -430
- package/storages/sitemap_request_list.js.map +0 -1
- package/storages/storage_manager.d.ts +0 -58
- package/storages/storage_manager.d.ts.map +0 -1
- package/storages/storage_manager.js +0 -105
- package/storages/storage_manager.js.map +0 -1
- package/storages/utils.d.ts.map +0 -1
- package/storages/utils.js.map +0 -1
- package/tsconfig.build.tsbuildinfo +0 -1
- package/typedefs.d.ts.map +0 -1
- package/typedefs.js.map +0 -1
- package/validators.d.ts.map +0 -1
- package/validators.js.map +0 -1
|
@@ -1,31 +1,41 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { z } from 'zod';
|
|
2
2
|
import { addTimeoutToPromise } from '@apify/timeout';
|
|
3
3
|
import { betterClearInterval, betterSetInterval } from '@apify/utilities';
|
|
4
|
-
import { Configuration } from '../configuration.js';
|
|
5
4
|
import { CriticalError } from '../errors.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
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
|
+
});
|
|
9
24
|
/**
|
|
10
25
|
* Manages a pool of asynchronous resource-intensive tasks that are executed in parallel.
|
|
11
|
-
* The pool only starts new tasks
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* The information about the CPU and memory usage is obtained by the {@link Snapshotter} class,
|
|
15
|
-
* which makes regular snapshots of system resources that may be either local
|
|
16
|
-
* or from the Apify cloud infrastructure in case the process is running on the Apify platform.
|
|
17
|
-
* Meaningful data gathered from these snapshots is provided to `AutoscaledPool` by the {@link SystemStatus} class.
|
|
26
|
+
* The pool only starts new tasks while its {@link IConcurrencySystem|concurrency system} reports free capacity —
|
|
27
|
+
* that governor is what monitors CPU, memory and event loop load and autoscales the concurrency budget.
|
|
18
28
|
*
|
|
19
29
|
* Before running the pool, you need to implement the following three functions:
|
|
20
|
-
* {@link AutoscaledPoolOptions.runTaskFunction},
|
|
21
|
-
* {@link
|
|
22
|
-
* {@link
|
|
30
|
+
* {@link AutoscaledPoolOptions.runTaskFunction|`runTaskFunction`},
|
|
31
|
+
* {@link TaskLoopPredicates.isTaskReadyFunction|`isTaskReadyFunction`} and
|
|
32
|
+
* {@link TaskLoopPredicates.isFinishedFunction|`isFinishedFunction`}.
|
|
23
33
|
*
|
|
24
34
|
* The auto-scaled pool is started by calling the {@link AutoscaledPool.run} function.
|
|
25
|
-
* The pool periodically queries
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
35
|
+
* The pool periodically queries `isTaskReadyFunction` for more tasks, managing optimal concurrency, until the function
|
|
36
|
+
* resolves to `false`. The pool then queries `isFinishedFunction`. If it resolves to `true`, the run finishes after all
|
|
37
|
+
* running tasks complete. If it resolves to `false`, it assumes there will be more tasks available later and keeps
|
|
38
|
+
* periodically querying for tasks.
|
|
29
39
|
* If any of the tasks throws then the {@link AutoscaledPool.run} function rejects the promise with an error.
|
|
30
40
|
*
|
|
31
41
|
* The pool evaluates whether it should start a new task every time one of the tasks finishes
|
|
@@ -34,8 +44,12 @@ import { SystemStatus } from './system_status.js';
|
|
|
34
44
|
* **Example usage:**
|
|
35
45
|
*
|
|
36
46
|
* ```javascript
|
|
47
|
+
* const concurrencySystem = new ConcurrencySystem({ maxConcurrency: 50 });
|
|
48
|
+
* await concurrencySystem.start();
|
|
49
|
+
*
|
|
37
50
|
* const pool = new AutoscaledPool({
|
|
38
|
-
*
|
|
51
|
+
* concurrencySystem,
|
|
52
|
+
* consumer: { id: 'my-pool' },
|
|
39
53
|
* runTaskFunction: async () => {
|
|
40
54
|
* // Run some resource-intensive asynchronous operation here.
|
|
41
55
|
* },
|
|
@@ -50,177 +64,106 @@ import { SystemStatus } from './system_status.js';
|
|
|
50
64
|
* }
|
|
51
65
|
* });
|
|
52
66
|
*
|
|
53
|
-
*
|
|
67
|
+
* try {
|
|
68
|
+
* await pool.run();
|
|
69
|
+
* } finally {
|
|
70
|
+
* await concurrencySystem.stop();
|
|
71
|
+
* }
|
|
54
72
|
* ```
|
|
55
|
-
*
|
|
73
|
+
*
|
|
74
|
+
* @internal
|
|
56
75
|
*/
|
|
57
76
|
export class AutoscaledPool {
|
|
58
|
-
|
|
59
|
-
log;
|
|
77
|
+
#log;
|
|
60
78
|
// Configurable properties.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
runTaskFunction;
|
|
69
|
-
isFinishedFunction;
|
|
70
|
-
isTaskReadyFunction;
|
|
71
|
-
maxTasksPerMinute;
|
|
79
|
+
#maybeRunIntervalMillis;
|
|
80
|
+
#taskTimeoutMillis;
|
|
81
|
+
#runTaskFunction;
|
|
82
|
+
#isFinishedFunction;
|
|
83
|
+
#isTaskReadyFunction;
|
|
84
|
+
#concurrencySystem;
|
|
85
|
+
#consumer;
|
|
72
86
|
// Internal properties.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
resolve = null;
|
|
80
|
-
reject = null;
|
|
81
|
-
snapshotter;
|
|
82
|
-
systemStatus;
|
|
83
|
-
autoscaleInterval;
|
|
84
|
-
maybeRunInterval;
|
|
85
|
-
queryingIsTaskReady;
|
|
86
|
-
queryingIsFinished;
|
|
87
|
-
tasksDonePerSecondInterval;
|
|
88
|
-
_tasksPerMinute = Array.from({ length: 60 }, () => 0);
|
|
89
|
-
constructor(options, config = Configuration.getGlobalConfig()) {
|
|
90
|
-
this.config = config;
|
|
91
|
-
ow(options, ow.object.exactShape({
|
|
92
|
-
runTaskFunction: ow.function,
|
|
93
|
-
isFinishedFunction: ow.function,
|
|
94
|
-
isTaskReadyFunction: ow.function,
|
|
95
|
-
maxConcurrency: ow.optional.number.integer.greaterThanOrEqual(1),
|
|
96
|
-
minConcurrency: ow.optional.number.integer.greaterThanOrEqual(1),
|
|
97
|
-
desiredConcurrency: ow.optional.number.integer.greaterThanOrEqual(1),
|
|
98
|
-
desiredConcurrencyRatio: ow.optional.number.greaterThan(0).lessThan(1),
|
|
99
|
-
scaleUpStepRatio: ow.optional.number.greaterThan(0).lessThan(1),
|
|
100
|
-
scaleDownStepRatio: ow.optional.number.greaterThan(0).lessThan(1),
|
|
101
|
-
maybeRunIntervalSecs: ow.optional.number.greaterThan(0),
|
|
102
|
-
loggingIntervalSecs: ow.any(ow.number.greaterThan(0), ow.nullOrUndefined),
|
|
103
|
-
autoscaleIntervalSecs: ow.optional.number.greaterThan(0),
|
|
104
|
-
taskTimeoutSecs: ow.optional.number.greaterThanOrEqual(0),
|
|
105
|
-
systemStatusOptions: ow.optional.object,
|
|
106
|
-
snapshotterOptions: ow.optional.object,
|
|
107
|
-
log: ow.optional.object,
|
|
108
|
-
maxTasksPerMinute: ow.optional.number.integerOrInfinite.greaterThanOrEqual(1),
|
|
109
|
-
}));
|
|
110
|
-
const { runTaskFunction, isFinishedFunction, isTaskReadyFunction, maxConcurrency = 200, minConcurrency = 1, desiredConcurrency, desiredConcurrencyRatio = 0.9, scaleUpStepRatio = 0.05, scaleDownStepRatio = 0.05, maybeRunIntervalSecs = 0.5, loggingIntervalSecs = 60, taskTimeoutSecs = 0, autoscaleIntervalSecs = 10, systemStatusOptions, snapshotterOptions, log = defaultLog, maxTasksPerMinute = Infinity, } = options;
|
|
111
|
-
this.log = log.child({ prefix: 'AutoscaledPool' });
|
|
112
|
-
// Configurable properties.
|
|
113
|
-
this.desiredConcurrencyRatio = desiredConcurrencyRatio;
|
|
114
|
-
this.scaleUpStepRatio = scaleUpStepRatio;
|
|
115
|
-
this.scaleDownStepRatio = scaleDownStepRatio;
|
|
116
|
-
this.maybeRunIntervalMillis = maybeRunIntervalSecs * 1000;
|
|
117
|
-
this.loggingIntervalMillis = loggingIntervalSecs * 1000;
|
|
118
|
-
this.autoscaleIntervalMillis = autoscaleIntervalSecs * 1000;
|
|
119
|
-
this.taskTimeoutMillis = taskTimeoutSecs * 1000;
|
|
120
|
-
this.runTaskFunction = runTaskFunction;
|
|
121
|
-
this.isFinishedFunction = isFinishedFunction;
|
|
122
|
-
this.isTaskReadyFunction = isTaskReadyFunction;
|
|
123
|
-
this.maxTasksPerMinute = maxTasksPerMinute;
|
|
124
|
-
// Internal properties.
|
|
125
|
-
this._minConcurrency = minConcurrency;
|
|
126
|
-
this._maxConcurrency = maxConcurrency;
|
|
127
|
-
this._desiredConcurrency = Math.min(desiredConcurrency ?? minConcurrency, maxConcurrency);
|
|
128
|
-
this._currentConcurrency = 0;
|
|
129
|
-
this.isStopped = false;
|
|
130
|
-
this.resolve = null;
|
|
131
|
-
this.reject = null;
|
|
132
|
-
this._autoscale = this._autoscale.bind(this);
|
|
133
|
-
this._maybeRunTask = this._maybeRunTask.bind(this);
|
|
134
|
-
this._incrementTasksDonePerSecond = this._incrementTasksDonePerSecond.bind(this);
|
|
135
|
-
// Create instances with correct options.
|
|
136
|
-
const ssoCopy = { ...systemStatusOptions };
|
|
137
|
-
ssoCopy.snapshotter ??= new Snapshotter({
|
|
138
|
-
...snapshotterOptions,
|
|
139
|
-
log: this.log,
|
|
140
|
-
config: this.config,
|
|
141
|
-
client: this.config.getStorageClient(),
|
|
142
|
-
});
|
|
143
|
-
ssoCopy.config ??= this.config;
|
|
144
|
-
this.snapshotter = ssoCopy.snapshotter;
|
|
145
|
-
this.systemStatus = new SystemStatus(ssoCopy);
|
|
146
|
-
}
|
|
87
|
+
#isStopped = false;
|
|
88
|
+
#resolve = null;
|
|
89
|
+
#reject = null;
|
|
90
|
+
#maybeRunInterval;
|
|
91
|
+
#queryingIsTaskReady;
|
|
92
|
+
#queryingIsFinished;
|
|
147
93
|
/**
|
|
148
|
-
*
|
|
94
|
+
* This pool's *own* in-flight task count, as opposed to {@link AutoscaledPool.currentConcurrency}, which is the
|
|
95
|
+
* (possibly shared) governor's total. `pause()` and `maybeFinish()` care only about this pool draining.
|
|
149
96
|
*/
|
|
150
|
-
|
|
151
|
-
|
|
97
|
+
#ownConcurrency = 0;
|
|
98
|
+
constructor(options) {
|
|
99
|
+
const { runTaskFunction, isFinishedFunction, isTaskReadyFunction, maybeRunIntervalSecs, taskTimeoutSecs, log, concurrencySystem, consumer, } = parseArgument(options, autoscaledPoolOptionsSchema, 'AutoscaledPoolOptions');
|
|
100
|
+
this.#log = log.child({ prefix: 'AutoscaledPool' });
|
|
101
|
+
// Configurable properties.
|
|
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;
|
|
109
|
+
// Internal properties.
|
|
110
|
+
this.#isStopped = false;
|
|
111
|
+
this.#resolve = null;
|
|
112
|
+
this.#reject = null;
|
|
113
|
+
this.maybeRunTask = this.maybeRunTask.bind(this);
|
|
152
114
|
}
|
|
153
115
|
/**
|
|
154
|
-
*
|
|
116
|
+
* The governor backing this pool, as supplied to the constructor — exposed as the read-only
|
|
117
|
+
* {@link IConcurrencySystem} contract.
|
|
155
118
|
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*/
|
|
159
|
-
set minConcurrency(value) {
|
|
160
|
-
ow(value, ow.optional.number.integer.greaterThanOrEqual(1));
|
|
161
|
-
this._minConcurrency = value;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Gets the maximum number of tasks running in parallel.
|
|
119
|
+
* This and the two getters below are telemetry only: concurrency is configured and tuned on the concrete
|
|
120
|
+
* {@link ConcurrencySystem} its owner holds, never through the pool.
|
|
165
121
|
*/
|
|
166
|
-
get
|
|
167
|
-
return this
|
|
122
|
+
get system() {
|
|
123
|
+
return this.#concurrencySystem;
|
|
168
124
|
}
|
|
169
|
-
/**
|
|
170
|
-
* Sets the maximum number of tasks running in parallel.
|
|
171
|
-
*/
|
|
172
|
-
set maxConcurrency(value) {
|
|
173
|
-
ow(value, ow.optional.number.integer.greaterThanOrEqual(1));
|
|
174
|
-
this._maxConcurrency = value;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Gets the desired concurrency for the pool,
|
|
178
|
-
* which is an estimated number of parallel tasks that the system can currently support.
|
|
179
|
-
*/
|
|
125
|
+
/** The estimated number of parallel tasks the governor can currently support. */
|
|
180
126
|
get desiredConcurrency() {
|
|
181
|
-
return this.
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Sets the desired concurrency for the pool, i.e. the number of tasks that should be running
|
|
185
|
-
* in parallel if there's large enough supply of tasks.
|
|
186
|
-
*/
|
|
187
|
-
set desiredConcurrency(value) {
|
|
188
|
-
ow(value, ow.optional.number.integer.greaterThanOrEqual(1));
|
|
189
|
-
this._desiredConcurrency = value;
|
|
127
|
+
return this.#concurrencySystem.desiredConcurrency;
|
|
190
128
|
}
|
|
191
129
|
/**
|
|
192
|
-
*
|
|
130
|
+
* The number of parallel tasks currently booked against the governor. When it is shared, this counts every
|
|
131
|
+
* borrowing pool's tasks, not just this one's.
|
|
193
132
|
*/
|
|
194
133
|
get currentConcurrency() {
|
|
195
|
-
return this.
|
|
134
|
+
return this.#concurrencySystem.currentConcurrency;
|
|
196
135
|
}
|
|
197
136
|
/**
|
|
198
137
|
* Runs the auto-scaled pool. Returns a promise that gets resolved or rejected once
|
|
199
138
|
* all the tasks are finished or one of them fails.
|
|
139
|
+
*
|
|
140
|
+
* Throws if the {@link IConcurrencySystem|concurrency system} it borrows was never started — the pool assumes
|
|
141
|
+
* a running governor and cannot start one it does not own.
|
|
200
142
|
*/
|
|
201
143
|
async run() {
|
|
144
|
+
// Checked here, on an awaited path — the capacity queries inside the task loop run from intervals and
|
|
145
|
+
// `setImmediate`, where a throw would become an unhandled rejection and hang `run()` forever.
|
|
146
|
+
if (!this.#concurrencySystem.isRunning) {
|
|
147
|
+
throw new CriticalError('The ConcurrencySystem this AutoscaledPool borrows has not been started, so system load would not be ' +
|
|
148
|
+
'monitored and the concurrency would never be adjusted. Whoever creates a ConcurrencySystem owns ' +
|
|
149
|
+
'its lifecycle: call `await concurrencySystem.start()` before running the pools or crawlers that ' +
|
|
150
|
+
'use it, and `await concurrencySystem.stop()` once they are all done.');
|
|
151
|
+
}
|
|
202
152
|
const poolPromise = new Promise((resolve, reject) => {
|
|
203
|
-
this
|
|
204
|
-
this
|
|
153
|
+
this.#resolve = resolve;
|
|
154
|
+
this.#reject = reject;
|
|
205
155
|
});
|
|
206
|
-
await this.snapshotter.start();
|
|
207
|
-
// This interval checks the system status and updates the desired concurrency accordingly.
|
|
208
|
-
this.autoscaleInterval = betterSetInterval(this._autoscale, this.autoscaleIntervalMillis);
|
|
209
156
|
// This is here because if we scale down to let's say 1, then after each promise is finished
|
|
210
|
-
// this.
|
|
157
|
+
// this.maybeRunTask() doesn't trigger another one. So if that 1 instance gets stuck it results
|
|
211
158
|
// in the crawler getting stuck and even after scaling up it never triggers another promise.
|
|
212
|
-
this
|
|
213
|
-
if (this.maxTasksPerMinute !== Infinity) {
|
|
214
|
-
// Start the interval that resets the counter of tasks per minute.
|
|
215
|
-
this.tasksDonePerSecondInterval = betterSetInterval(this._incrementTasksDonePerSecond, 1000);
|
|
216
|
-
}
|
|
159
|
+
this.#maybeRunInterval = betterSetInterval(this.maybeRunTask, this.#maybeRunIntervalMillis);
|
|
217
160
|
try {
|
|
218
161
|
await poolPromise;
|
|
219
162
|
}
|
|
220
163
|
finally {
|
|
221
164
|
// If resolve is null, the pool is already destroyed.
|
|
222
|
-
if (this
|
|
223
|
-
await this.
|
|
165
|
+
if (this.#resolve)
|
|
166
|
+
await this.destroy();
|
|
224
167
|
}
|
|
225
168
|
}
|
|
226
169
|
/**
|
|
@@ -235,10 +178,10 @@ export class AutoscaledPool {
|
|
|
235
178
|
* parts of their asynchronous chains of commands will not execute.
|
|
236
179
|
*/
|
|
237
180
|
async abort() {
|
|
238
|
-
this
|
|
239
|
-
if (this
|
|
240
|
-
this
|
|
241
|
-
await this.
|
|
181
|
+
this.#isStopped = true;
|
|
182
|
+
if (this.#resolve) {
|
|
183
|
+
this.#resolve();
|
|
184
|
+
await this.destroy();
|
|
242
185
|
}
|
|
243
186
|
}
|
|
244
187
|
/**
|
|
@@ -251,29 +194,36 @@ export class AutoscaledPool {
|
|
|
251
194
|
*
|
|
252
195
|
* The promise returned from the {@link AutoscaledPool.run} function will not resolve
|
|
253
196
|
* when `.pause()` is invoked (unlike abort, which resolves it).
|
|
197
|
+
*
|
|
198
|
+
* > *NOTE:* Pausing the pool does not suspend the (possibly shared) {@link ConcurrencySystem} — its
|
|
199
|
+
* autoscaling and resource monitoring keep running, since other pools borrowing it may still be active. To silence
|
|
200
|
+
* it during a long pause, its owner can `stop()` and `start()` it again.
|
|
254
201
|
*/
|
|
255
202
|
async pause(timeoutSecs) {
|
|
256
|
-
if (this
|
|
203
|
+
if (this.#isStopped)
|
|
257
204
|
return;
|
|
258
|
-
this
|
|
205
|
+
this.#isStopped = true;
|
|
259
206
|
await new Promise((resolve, reject) => {
|
|
260
207
|
let timeout;
|
|
208
|
+
let interval;
|
|
261
209
|
if (timeoutSecs) {
|
|
262
210
|
timeout = setTimeout(() => {
|
|
211
|
+
// Clean up the polling interval to prevent it from leaking on timeout.
|
|
212
|
+
clearInterval(interval);
|
|
263
213
|
const err = new Error("The pool's running tasks did not finish" +
|
|
264
214
|
`in ${timeoutSecs} secs after pool.pause() invocation.`);
|
|
265
215
|
reject(err);
|
|
266
216
|
}, timeoutSecs);
|
|
267
217
|
}
|
|
268
|
-
|
|
269
|
-
if (this
|
|
218
|
+
interval = setInterval(() => {
|
|
219
|
+
if (this.#ownConcurrency <= 0) {
|
|
270
220
|
// Clean up timeout and interval to prevent process hanging.
|
|
271
221
|
if (timeout)
|
|
272
222
|
clearTimeout(timeout);
|
|
273
223
|
clearInterval(interval);
|
|
274
224
|
resolve();
|
|
275
225
|
}
|
|
276
|
-
}, this
|
|
226
|
+
}, this.#maybeRunIntervalMillis);
|
|
277
227
|
});
|
|
278
228
|
}
|
|
279
229
|
/**
|
|
@@ -283,251 +233,154 @@ export class AutoscaledPool {
|
|
|
283
233
|
* Tasks will automatically start running again in `options.maybeRunIntervalSecs`.
|
|
284
234
|
*/
|
|
285
235
|
resume() {
|
|
286
|
-
this
|
|
236
|
+
this.#isStopped = false;
|
|
287
237
|
}
|
|
288
238
|
/**
|
|
289
239
|
* Explicitly check the queue for new tasks. The AutoscaledPool checks the queue for new tasks periodically,
|
|
290
240
|
* every `maybeRunIntervalSecs` seconds. If you want to trigger the processing immediately, use this method.
|
|
291
241
|
*/
|
|
292
242
|
async notify() {
|
|
293
|
-
setImmediate(this.
|
|
243
|
+
setImmediate(this.maybeRunTask);
|
|
294
244
|
}
|
|
295
245
|
/**
|
|
296
246
|
* Starts a new task
|
|
297
247
|
* if the number of running tasks (current concurrency) is lower than desired concurrency
|
|
298
248
|
* and the system is not currently overloaded
|
|
299
|
-
* and
|
|
249
|
+
* and `isTaskReadyFunction()` returns true.
|
|
300
250
|
*
|
|
301
251
|
* It doesn't allow multiple concurrent runs of this method.
|
|
302
252
|
*/
|
|
303
|
-
async
|
|
304
|
-
this
|
|
253
|
+
async maybeRunTask(intervalCallback) {
|
|
254
|
+
this.#log.perf('Attempting to run a task.');
|
|
305
255
|
// Check if the function was invoked by the maybeRunInterval and use an empty function if not.
|
|
306
256
|
const done = intervalCallback || (() => { });
|
|
307
257
|
// Prevent starting a new task if:
|
|
308
258
|
// - the pool is paused or aborted
|
|
309
|
-
if (this
|
|
310
|
-
this
|
|
259
|
+
if (this.#isStopped) {
|
|
260
|
+
this.#log.perf('Task will not run. AutoscaledPool is stopped.');
|
|
311
261
|
return done();
|
|
312
262
|
}
|
|
313
263
|
// - we are already querying for a task.
|
|
314
|
-
if (this
|
|
315
|
-
this
|
|
264
|
+
if (this.#queryingIsTaskReady) {
|
|
265
|
+
this.#log.perf('Task will not run. Waiting for a ready task.');
|
|
316
266
|
return done();
|
|
317
267
|
}
|
|
318
|
-
// -
|
|
319
|
-
if (this.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (!isSystemIdle && this._currentConcurrency >= this._minConcurrency) {
|
|
327
|
-
this.log.perf('Task will not be run. System is overloaded.', currentStatus);
|
|
328
|
-
return done();
|
|
268
|
+
// - the budget has room for us.
|
|
269
|
+
if (!this.#concurrencySystem.hasCapacityForTask(this.#consumer)) {
|
|
270
|
+
done();
|
|
271
|
+
// A shared governor's budget can stay saturated by another pool indefinitely, so we still have to be able
|
|
272
|
+
// to notice that *this* pool has run out of work — `maybeFinish()` is the only thing that ever resolves
|
|
273
|
+
// `run()`. It no-ops while this pool has tasks of its own in flight, which is every case in which an
|
|
274
|
+
// unshared governor reports no capacity.
|
|
275
|
+
return this.maybeFinish();
|
|
329
276
|
}
|
|
330
277
|
// - a task is ready.
|
|
331
|
-
this
|
|
278
|
+
this.#queryingIsTaskReady = true;
|
|
332
279
|
let isTaskReady;
|
|
333
280
|
try {
|
|
334
|
-
this
|
|
335
|
-
isTaskReady = await this
|
|
281
|
+
this.#log.perf('Checking for ready tasks.');
|
|
282
|
+
isTaskReady = await this.#isTaskReadyFunction();
|
|
336
283
|
}
|
|
337
284
|
catch (e) {
|
|
338
285
|
const err = e;
|
|
339
|
-
this
|
|
286
|
+
this.#log.perf('Checking for ready tasks failed.');
|
|
340
287
|
// We might have already rejected this promise.
|
|
341
|
-
if (this
|
|
288
|
+
if (this.#reject) {
|
|
342
289
|
// No need to log all concurrent errors.
|
|
343
|
-
this
|
|
344
|
-
this
|
|
290
|
+
this.#log.exception(err, 'isTaskReadyFunction failed');
|
|
291
|
+
this.#reject(err);
|
|
345
292
|
}
|
|
346
293
|
}
|
|
347
294
|
finally {
|
|
348
|
-
this
|
|
295
|
+
this.#queryingIsTaskReady = false;
|
|
349
296
|
}
|
|
350
297
|
if (!isTaskReady) {
|
|
351
|
-
this
|
|
298
|
+
this.#log.perf('Task will not run. No tasks are ready.');
|
|
352
299
|
done();
|
|
353
300
|
// No tasks could mean that we're finished with all tasks.
|
|
354
|
-
return this.
|
|
301
|
+
return this.maybeFinish();
|
|
355
302
|
}
|
|
356
|
-
// -
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
if (this._isOverMaxRequestLimit) {
|
|
360
|
-
this.log.perf('Task will not run. Maximum tasks per minute reached.');
|
|
303
|
+
// - the budget still has room. Re-checked atomically, because another pool sharing the governor may have taken
|
|
304
|
+
// the last free slot while we awaited `isTaskReadyFunction` above.
|
|
305
|
+
if (!this.#concurrencySystem.tryRegisterTaskStart(this.#consumer)) {
|
|
361
306
|
return done();
|
|
362
307
|
}
|
|
308
|
+
this.#ownConcurrency++;
|
|
363
309
|
try {
|
|
364
310
|
// Everything's fine. Run task.
|
|
365
|
-
this._currentConcurrency++;
|
|
366
|
-
this._tasksPerMinute[0]++;
|
|
367
311
|
// Try to run next task to build up concurrency,
|
|
368
312
|
// but defer it so it doesn't create a cycle.
|
|
369
|
-
setImmediate(this.
|
|
313
|
+
setImmediate(this.maybeRunTask);
|
|
370
314
|
// We need to restart interval here, so that it doesn't get blocked by a stalled task.
|
|
371
315
|
done();
|
|
372
316
|
// Execute the current task.
|
|
373
|
-
this
|
|
374
|
-
if (this
|
|
375
|
-
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.`);
|
|
376
320
|
}
|
|
377
321
|
else {
|
|
378
|
-
await this
|
|
322
|
+
await this.#runTaskFunction();
|
|
379
323
|
}
|
|
380
|
-
this
|
|
381
|
-
|
|
382
|
-
//
|
|
383
|
-
setImmediate(this.
|
|
324
|
+
this.#log.perf('Task finished.');
|
|
325
|
+
// Run task after the previous one finished. Only on success: a failed task rejects the pool, and
|
|
326
|
+
// nudging the loop afterwards could start work on an already destroyed pool.
|
|
327
|
+
setImmediate(this.maybeRunTask);
|
|
384
328
|
}
|
|
385
329
|
catch (e) {
|
|
386
330
|
const err = e;
|
|
387
|
-
this
|
|
331
|
+
this.#log.perf('Running a task failed.');
|
|
388
332
|
// We might have already rejected this promise.
|
|
389
|
-
if (this
|
|
333
|
+
if (this.#reject) {
|
|
390
334
|
// No need to log all concurrent errors.
|
|
391
335
|
if (
|
|
392
336
|
// avoid reprinting the same critical error multiple times, as it will be printed by Nodejs at the end anyway
|
|
393
337
|
!(e instanceof CriticalError)) {
|
|
394
|
-
this
|
|
338
|
+
this.#log.exception(err, 'runTaskFunction failed.');
|
|
395
339
|
}
|
|
396
|
-
this
|
|
340
|
+
this.#reject(err);
|
|
397
341
|
}
|
|
398
342
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
* Gets called every autoScaleIntervalSecs and evaluates the current system status.
|
|
403
|
-
* If the system IS NOT overloaded and the settings allow it, it scales up.
|
|
404
|
-
* If the system IS overloaded and the settings allow it, it scales down.
|
|
405
|
-
*/
|
|
406
|
-
_autoscale(intervalCallback) {
|
|
407
|
-
// Don't scale if paused.
|
|
408
|
-
if (this.isStopped)
|
|
409
|
-
return intervalCallback();
|
|
410
|
-
// Don't scale if we've hit the maximum requests per minute
|
|
411
|
-
if (this._isOverMaxRequestLimit)
|
|
412
|
-
return intervalCallback();
|
|
413
|
-
// Only scale up if:
|
|
414
|
-
// - system has not been overloaded lately.
|
|
415
|
-
const systemStatus = this.systemStatus.getHistoricalStatus();
|
|
416
|
-
const { isSystemIdle } = systemStatus;
|
|
417
|
-
// - we're not already at max concurrency.
|
|
418
|
-
const weAreNotAtMax = this._desiredConcurrency < this._maxConcurrency;
|
|
419
|
-
// - current concurrency reaches at least the given ratio of desired concurrency.
|
|
420
|
-
const minCurrentConcurrency = Math.floor(this._desiredConcurrency * this.desiredConcurrencyRatio);
|
|
421
|
-
const weAreReachingDesiredConcurrency = this._currentConcurrency >= minCurrentConcurrency;
|
|
422
|
-
if (isSystemIdle && weAreNotAtMax && weAreReachingDesiredConcurrency)
|
|
423
|
-
this._scaleUp(systemStatus);
|
|
424
|
-
// Always scale down if:
|
|
425
|
-
// - the system has been overloaded lately.
|
|
426
|
-
const isSystemOverloaded = !isSystemIdle;
|
|
427
|
-
// - we're over min concurrency.
|
|
428
|
-
const weAreNotAtMin = this._desiredConcurrency > this._minConcurrency;
|
|
429
|
-
if (isSystemOverloaded && weAreNotAtMin)
|
|
430
|
-
this._scaleDown(systemStatus);
|
|
431
|
-
// On periodic intervals, print comprehensive log information
|
|
432
|
-
if (this.loggingIntervalMillis > 0) {
|
|
433
|
-
const now = Date.now();
|
|
434
|
-
if (this.lastLoggingTime == null) {
|
|
435
|
-
this.lastLoggingTime = now;
|
|
436
|
-
}
|
|
437
|
-
else if (now > this.lastLoggingTime + this.loggingIntervalMillis) {
|
|
438
|
-
this.lastLoggingTime = now;
|
|
439
|
-
this.log.info('state', {
|
|
440
|
-
currentConcurrency: this._currentConcurrency,
|
|
441
|
-
desiredConcurrency: this._desiredConcurrency,
|
|
442
|
-
systemStatus,
|
|
443
|
-
});
|
|
444
|
-
}
|
|
343
|
+
finally {
|
|
344
|
+
this.#concurrencySystem.registerTaskEnd(this.#consumer);
|
|
345
|
+
this.#ownConcurrency--;
|
|
445
346
|
}
|
|
446
|
-
|
|
447
|
-
return intervalCallback();
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Scales the pool up by increasing
|
|
451
|
-
* the desired concurrency by the scaleUpStepRatio.
|
|
452
|
-
*
|
|
453
|
-
* @param systemStatus for logging
|
|
454
|
-
*/
|
|
455
|
-
_scaleUp(systemStatus) {
|
|
456
|
-
const step = Math.ceil(this._desiredConcurrency * this.scaleUpStepRatio);
|
|
457
|
-
this._desiredConcurrency = Math.min(this._maxConcurrency, this._desiredConcurrency + step);
|
|
458
|
-
this.log.debug('scaling up', {
|
|
459
|
-
oldConcurrency: this._desiredConcurrency - step,
|
|
460
|
-
newConcurrency: this._desiredConcurrency,
|
|
461
|
-
systemStatus,
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Scales the pool down by decreasing
|
|
466
|
-
* the desired concurrency by the scaleDownStepRatio.
|
|
467
|
-
*
|
|
468
|
-
* @param systemStatus for logging
|
|
469
|
-
*/
|
|
470
|
-
_scaleDown(systemStatus) {
|
|
471
|
-
const step = Math.ceil(this._desiredConcurrency * this.scaleDownStepRatio);
|
|
472
|
-
this._desiredConcurrency = Math.max(this._minConcurrency, this._desiredConcurrency - step);
|
|
473
|
-
this.log.debug('scaling down', {
|
|
474
|
-
oldConcurrency: this._desiredConcurrency + step,
|
|
475
|
-
newConcurrency: this._desiredConcurrency,
|
|
476
|
-
systemStatus,
|
|
477
|
-
});
|
|
347
|
+
return undefined;
|
|
478
348
|
}
|
|
479
349
|
/**
|
|
480
|
-
* If there are no running tasks and
|
|
350
|
+
* If there are no running tasks and `isFinishedFunction()` returns true then closes
|
|
481
351
|
* the pool and resolves the pool's promise returned by the run() method.
|
|
482
352
|
*
|
|
483
353
|
* It doesn't allow multiple concurrent runs of this method.
|
|
484
354
|
*/
|
|
485
|
-
async
|
|
486
|
-
if (this
|
|
355
|
+
async maybeFinish() {
|
|
356
|
+
if (this.#queryingIsFinished)
|
|
487
357
|
return;
|
|
488
|
-
if (this
|
|
358
|
+
if (this.#ownConcurrency > 0)
|
|
489
359
|
return;
|
|
490
|
-
this
|
|
360
|
+
this.#queryingIsFinished = true;
|
|
491
361
|
try {
|
|
492
|
-
const isFinished = await this
|
|
493
|
-
if (isFinished && this
|
|
494
|
-
this
|
|
362
|
+
const isFinished = await this.#isFinishedFunction();
|
|
363
|
+
if (isFinished && this.#resolve)
|
|
364
|
+
this.#resolve();
|
|
495
365
|
}
|
|
496
366
|
catch (e) {
|
|
497
367
|
const err = e;
|
|
498
|
-
if (this
|
|
368
|
+
if (this.#reject) {
|
|
499
369
|
// No need to log all concurrent errors.
|
|
500
|
-
this
|
|
501
|
-
this
|
|
370
|
+
this.#log.exception(err, 'isFinishedFunction failed.');
|
|
371
|
+
this.#reject(err);
|
|
502
372
|
}
|
|
503
373
|
}
|
|
504
374
|
finally {
|
|
505
|
-
this
|
|
375
|
+
this.#queryingIsFinished = false;
|
|
506
376
|
}
|
|
507
377
|
}
|
|
508
378
|
/**
|
|
509
379
|
* Cleans up resources.
|
|
510
380
|
*/
|
|
511
|
-
async
|
|
512
|
-
this
|
|
513
|
-
this
|
|
514
|
-
betterClearInterval(this
|
|
515
|
-
betterClearInterval(this.maybeRunInterval);
|
|
516
|
-
if (this.tasksDonePerSecondInterval)
|
|
517
|
-
betterClearInterval(this.tasksDonePerSecondInterval);
|
|
518
|
-
if (this.snapshotter)
|
|
519
|
-
await this.snapshotter.stop();
|
|
520
|
-
}
|
|
521
|
-
_incrementTasksDonePerSecond(intervalCallback) {
|
|
522
|
-
this._tasksPerMinute.unshift(0);
|
|
523
|
-
this._tasksPerMinute.pop();
|
|
524
|
-
return intervalCallback();
|
|
525
|
-
}
|
|
526
|
-
get _isOverMaxRequestLimit() {
|
|
527
|
-
if (this.maxTasksPerMinute === Infinity) {
|
|
528
|
-
return false;
|
|
529
|
-
}
|
|
530
|
-
return this._tasksPerMinute.reduce((acc, curr) => acc + curr, 0) >= this.maxTasksPerMinute;
|
|
381
|
+
async destroy() {
|
|
382
|
+
this.#resolve = null;
|
|
383
|
+
this.#reject = null;
|
|
384
|
+
betterClearInterval(this.#maybeRunInterval);
|
|
531
385
|
}
|
|
532
386
|
}
|
|
533
|
-
//# sourceMappingURL=autoscaled_pool.js.map
|