@crawlee/core 4.0.0-beta.99 → 4.0.0-rc.1

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.
Files changed (133) hide show
  1. package/README.md +1 -1
  2. package/configuration.d.ts +16 -47
  3. package/configuration.js +13 -25
  4. package/debug.js +4 -4
  5. package/errors.d.ts +28 -38
  6. package/errors.js +33 -47
  7. package/events/event_manager.d.ts +2 -2
  8. package/events/event_manager.js +7 -6
  9. package/events/index.d.ts +1 -0
  10. package/events/local_event_manager.d.ts +1 -8
  11. package/events/local_event_manager.js +13 -13
  12. package/events/system_info.d.ts +38 -0
  13. package/index.d.ts +2 -8
  14. package/index.js +4 -8
  15. package/internal.d.ts +8 -0
  16. package/internal.js +9 -0
  17. package/log.d.ts +10 -11
  18. package/log.js +52 -20
  19. package/memory-storage/memory-storage.d.ts +15 -18
  20. package/memory-storage/memory-storage.js +80 -58
  21. package/memory-storage/resource-clients/dataset.d.ts +1 -6
  22. package/memory-storage/resource-clients/dataset.js +23 -31
  23. package/memory-storage/resource-clients/key-value-store.d.ts +1 -10
  24. package/memory-storage/resource-clients/key-value-store.js +43 -67
  25. package/memory-storage/resource-clients/request-queue.d.ts +1 -42
  26. package/memory-storage/resource-clients/request-queue.js +109 -117
  27. package/owned_or_injected.d.ts +1 -3
  28. package/owned_or_injected.js +17 -17
  29. package/package.json +17 -20
  30. package/proxy_configuration.d.ts +21 -26
  31. package/proxy_configuration.js +35 -25
  32. package/recoverable_state.d.ts +104 -47
  33. package/recoverable_state.js +199 -74
  34. package/request.d.ts +20 -107
  35. package/request.js +78 -244
  36. package/serialization.js +17 -16
  37. package/service_locator.d.ts +22 -10
  38. package/service_locator.js +59 -48
  39. package/storages/batched_adds.d.ts +37 -0
  40. package/storages/batched_adds.js +73 -0
  41. package/storages/dataset.d.ts +13 -8
  42. package/storages/dataset.js +149 -40
  43. package/storages/index.d.ts +4 -4
  44. package/storages/index.js +2 -4
  45. package/storages/key_value_store.d.ts +16 -35
  46. package/storages/key_value_store.js +223 -110
  47. package/storages/key_value_store_codec.js +6 -11
  48. package/storages/request_dedup_cache.d.ts +1 -4
  49. package/storages/request_dedup_cache.js +15 -15
  50. package/storages/request_list.d.ts +9 -104
  51. package/storages/request_list.js +236 -233
  52. package/storages/request_loader.d.ts +49 -18
  53. package/storages/request_loader.js +36 -1
  54. package/storages/request_manager.d.ts +86 -0
  55. package/storages/request_manager_tandem.d.ts +14 -38
  56. package/storages/request_manager_tandem.js +67 -64
  57. package/storages/request_queue.d.ts +23 -50
  58. package/storages/request_queue.js +371 -226
  59. package/storages/storage_instance_manager.d.ts +2 -4
  60. package/storages/storage_instance_manager.js +21 -21
  61. package/storages/storage_stats.d.ts +1 -1
  62. package/storages/storage_stats.js +4 -4
  63. package/storages/transaction.d.ts +270 -0
  64. package/storages/transaction.js +296 -0
  65. package/storages/utils.d.ts +6 -3
  66. package/storages/utils.js +11 -2
  67. package/system-info/runtime.js +7 -7
  68. package/url.d.ts +9 -0
  69. package/url.js +11 -0
  70. package/validators.d.ts +23 -25
  71. package/validators.js +14 -25
  72. package/autoscaling/autoscaled_pool.d.ts +0 -213
  73. package/autoscaling/autoscaled_pool.js +0 -378
  74. package/autoscaling/client_load_signal.d.ts +0 -59
  75. package/autoscaling/client_load_signal.js +0 -73
  76. package/autoscaling/concurrency_system.d.ts +0 -283
  77. package/autoscaling/concurrency_system.js +0 -350
  78. package/autoscaling/cpu_load_signal.d.ts +0 -44
  79. package/autoscaling/cpu_load_signal.js +0 -46
  80. package/autoscaling/event_loop_load_signal.d.ts +0 -54
  81. package/autoscaling/event_loop_load_signal.js +0 -60
  82. package/autoscaling/index.d.ts +0 -9
  83. package/autoscaling/index.js +0 -9
  84. package/autoscaling/load_signal.d.ts +0 -99
  85. package/autoscaling/load_signal.js +0 -103
  86. package/autoscaling/memory_load_signal.d.ts +0 -56
  87. package/autoscaling/memory_load_signal.js +0 -106
  88. package/autoscaling/snapshotter.d.ts +0 -87
  89. package/autoscaling/snapshotter.js +0 -67
  90. package/autoscaling/system_status.d.ts +0 -161
  91. package/autoscaling/system_status.js +0 -139
  92. package/autoscaling/weighted_avg.d.ts +0 -5
  93. package/autoscaling/weighted_avg.js +0 -14
  94. package/cookie_utils.d.ts +0 -44
  95. package/cookie_utils.js +0 -122
  96. package/crawlers/context_pipeline.d.ts +0 -70
  97. package/crawlers/context_pipeline.js +0 -122
  98. package/crawlers/crawler_commons.d.ts +0 -257
  99. package/crawlers/crawler_commons.js +0 -107
  100. package/crawlers/error_snapshotter.d.ts +0 -59
  101. package/crawlers/error_snapshotter.js +0 -117
  102. package/crawlers/error_tracker.d.ts +0 -54
  103. package/crawlers/error_tracker.js +0 -308
  104. package/crawlers/index.d.ts +0 -5
  105. package/crawlers/index.js +0 -5
  106. package/crawlers/internals/types.d.ts +0 -7
  107. package/crawlers/statistics.d.ts +0 -209
  108. package/crawlers/statistics.js +0 -350
  109. package/enqueue_links/enqueue_links.d.ts +0 -264
  110. package/enqueue_links/enqueue_links.js +0 -271
  111. package/enqueue_links/index.d.ts +0 -2
  112. package/enqueue_links/index.js +0 -2
  113. package/enqueue_links/shared.d.ts +0 -83
  114. package/enqueue_links/shared.js +0 -221
  115. package/router.d.ts +0 -309
  116. package/router.js +0 -309
  117. package/session_pool/consts.d.ts +0 -3
  118. package/session_pool/consts.js +0 -3
  119. package/session_pool/errors.d.ts +0 -7
  120. package/session_pool/errors.js +0 -11
  121. package/session_pool/fingerprint.d.ts +0 -9
  122. package/session_pool/fingerprint.js +0 -30
  123. package/session_pool/index.d.ts +0 -4
  124. package/session_pool/index.js +0 -4
  125. package/session_pool/session.d.ts +0 -161
  126. package/session_pool/session.js +0 -218
  127. package/session_pool/session_pool.d.ts +0 -246
  128. package/session_pool/session_pool.js +0 -386
  129. package/storages/access_checking.d.ts +0 -12
  130. package/storages/access_checking.js +0 -17
  131. package/storages/sitemap_request_loader.d.ts +0 -249
  132. package/storages/sitemap_request_loader.js +0 -432
  133. /package/{crawlers/internals/types.js → events/system_info.js} +0 -0
@@ -1,213 +0,0 @@
1
- import type { ConcurrencyConsumer, IConcurrencySystem } from './concurrency_system.js';
2
- import type { CrawleeLogger } from '../log.js';
3
- /**
4
- * The two predicates that steer a task loop: *is there work ready?* and *are we done?* These are the parts of the loop
5
- * a caller legitimately overrides, as opposed to the task itself (`runTaskFunction`), which the loop's driver owns.
6
- */
7
- export interface TaskLoopPredicates {
8
- /**
9
- * A function that indicates whether `runTaskFunction` should be called.
10
- * This function is called every time there is free capacity for a new task and it should
11
- * indicate whether it should start a new task or not by resolving to either `true` or `false`.
12
- * Besides its obvious use, it is also useful for task throttling to save resources.
13
- */
14
- isTaskReadyFunction?: () => Promise<boolean>;
15
- /**
16
- * A function that is called only when there are no tasks to be processed.
17
- * If it resolves to `true` then the run finishes. Being called only
18
- * when there are no tasks being processed means that as long as `isTaskReadyFunction()`
19
- * keeps resolving to `true`, `isFinishedFunction()` will never be called.
20
- */
21
- isFinishedFunction?: () => Promise<boolean>;
22
- }
23
- /** @internal */
24
- export interface AutoscaledPoolOptions extends TaskLoopPredicates {
25
- /**
26
- * The governor that decides whether there is free compute for one more task. Typically a
27
- * {@link ConcurrencySystem}, but any {@link IConcurrencySystem} works. Share a single instance across
28
- * multiple pools (and therefore multiple crawlers) to cap their *combined* concurrency against one budget.
29
- *
30
- * All concurrency/scaling/snapshotter configuration lives on the governor — the pool only owns the task loop and
31
- * its cadence.
32
- */
33
- concurrencySystem: IConcurrencySystem;
34
- /**
35
- * Who this pool is, presented to the governor on every capacity query and booking so that a shared one can tell
36
- * several pools apart. Worth naming meaningfully — a governor that allocates per consumer reports this `id`.
37
- */
38
- consumer: ConcurrencyConsumer;
39
- /**
40
- * A function that performs an asynchronous resource-intensive task.
41
- * The function must either be labeled `async` or return a promise.
42
- */
43
- runTaskFunction?: () => Promise<unknown>;
44
- /**
45
- * Indicates how often the pool should call the `runTaskFunction()` to start a new task, in seconds.
46
- * This has no effect on starting new tasks immediately after a task completes.
47
- * @default 0.5
48
- */
49
- maybeRunIntervalSecs?: number;
50
- /**
51
- * Timeout in which the `runTaskFunction` needs to finish, given in seconds.
52
- * @default 0
53
- */
54
- taskTimeoutSecs?: number;
55
- log?: CrawleeLogger;
56
- }
57
- /**
58
- * Manages a pool of asynchronous resource-intensive tasks that are executed in parallel.
59
- * The pool only starts new tasks while its {@link IConcurrencySystem|concurrency system} reports free capacity —
60
- * that governor is what monitors CPU, memory and event loop load and autoscales the concurrency budget.
61
- *
62
- * Before running the pool, you need to implement the following three functions:
63
- * {@link AutoscaledPoolOptions.runTaskFunction|`runTaskFunction`},
64
- * {@link TaskLoopPredicates.isTaskReadyFunction|`isTaskReadyFunction`} and
65
- * {@link TaskLoopPredicates.isFinishedFunction|`isFinishedFunction`}.
66
- *
67
- * The auto-scaled pool is started by calling the {@link AutoscaledPool.run} function.
68
- * The pool periodically queries `isTaskReadyFunction` for more tasks, managing optimal concurrency, until the function
69
- * resolves to `false`. The pool then queries `isFinishedFunction`. If it resolves to `true`, the run finishes after all
70
- * running tasks complete. If it resolves to `false`, it assumes there will be more tasks available later and keeps
71
- * periodically querying for tasks.
72
- * If any of the tasks throws then the {@link AutoscaledPool.run} function rejects the promise with an error.
73
- *
74
- * The pool evaluates whether it should start a new task every time one of the tasks finishes
75
- * and also in the interval set by the `options.maybeRunIntervalSecs` parameter.
76
- *
77
- * **Example usage:**
78
- *
79
- * ```javascript
80
- * const concurrencySystem = new ConcurrencySystem({ maxConcurrency: 50 });
81
- * await concurrencySystem.start();
82
- *
83
- * const pool = new AutoscaledPool({
84
- * concurrencySystem,
85
- * consumer: { id: 'my-pool' },
86
- * runTaskFunction: async () => {
87
- * // Run some resource-intensive asynchronous operation here.
88
- * },
89
- * isTaskReadyFunction: async () => {
90
- * // Tell the pool whether more tasks are ready to be processed.
91
- * // Return true or false
92
- * },
93
- * isFinishedFunction: async () => {
94
- * // Tell the pool whether it should finish
95
- * // or wait for more tasks to become available.
96
- * // Return true or false
97
- * }
98
- * });
99
- *
100
- * try {
101
- * await pool.run();
102
- * } finally {
103
- * await concurrencySystem.stop();
104
- * }
105
- * ```
106
- *
107
- * @internal
108
- */
109
- export declare class AutoscaledPool {
110
- private readonly log;
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;
129
- constructor(options: AutoscaledPoolOptions);
130
- /**
131
- * The governor backing this pool, as supplied to the constructor — exposed as the read-only
132
- * {@link IConcurrencySystem} contract.
133
- *
134
- * This and the two getters below are telemetry only: concurrency is configured and tuned on the concrete
135
- * {@link ConcurrencySystem} its owner holds, never through the pool.
136
- */
137
- get system(): IConcurrencySystem;
138
- /** The estimated number of parallel tasks the governor can currently support. */
139
- get desiredConcurrency(): number;
140
- /**
141
- * The number of parallel tasks currently booked against the governor. When it is shared, this counts every
142
- * borrowing pool's tasks, not just this one's.
143
- */
144
- get currentConcurrency(): number;
145
- /**
146
- * Runs the auto-scaled pool. Returns a promise that gets resolved or rejected once
147
- * all the tasks are finished or one of them fails.
148
- *
149
- * Throws if the {@link IConcurrencySystem|concurrency system} it borrows was never started — the pool assumes
150
- * a running governor and cannot start one it does not own.
151
- */
152
- run(): Promise<void>;
153
- /**
154
- * Aborts the run of the auto-scaled pool and destroys it. The promise returned from
155
- * the {@link AutoscaledPool.run} function will immediately resolve, no more new tasks
156
- * will be spawned and all running tasks will be left in their current state.
157
- *
158
- * Due to the nature of the tasks, auto-scaled pool cannot reliably guarantee abortion
159
- * of all the running tasks, therefore, no abortion is attempted and some of the tasks
160
- * may finish, while others may not. Essentially, auto-scaled pool doesn't care about
161
- * their state after the invocation of `.abort()`, but that does not mean that some
162
- * parts of their asynchronous chains of commands will not execute.
163
- */
164
- abort(): Promise<void>;
165
- /**
166
- * Prevents the auto-scaled pool from starting new tasks, but allows the running ones to finish
167
- * (unlike abort, which terminates them). Used together with {@link AutoscaledPool.resume}
168
- *
169
- * The function's promise will resolve once all running tasks have completed and the pool
170
- * is effectively idle. If the `timeoutSecs` argument is provided, the promise will reject
171
- * with a timeout error after the `timeoutSecs` seconds.
172
- *
173
- * The promise returned from the {@link AutoscaledPool.run} function will not resolve
174
- * when `.pause()` is invoked (unlike abort, which resolves it).
175
- *
176
- * > *NOTE:* Pausing the pool does not suspend the (possibly shared) {@link ConcurrencySystem} — its
177
- * autoscaling and resource monitoring keep running, since other pools borrowing it may still be active. To silence
178
- * it during a long pause, its owner can `stop()` and `start()` it again.
179
- */
180
- pause(timeoutSecs?: number): Promise<void>;
181
- /**
182
- * Resumes the operation of the autoscaled-pool by allowing more tasks to be run.
183
- * Used together with {@link AutoscaledPool.pause}
184
- *
185
- * Tasks will automatically start running again in `options.maybeRunIntervalSecs`.
186
- */
187
- resume(): void;
188
- /**
189
- * Explicitly check the queue for new tasks. The AutoscaledPool checks the queue for new tasks periodically,
190
- * every `maybeRunIntervalSecs` seconds. If you want to trigger the processing immediately, use this method.
191
- */
192
- notify(): Promise<void>;
193
- /**
194
- * Starts a new task
195
- * if the number of running tasks (current concurrency) is lower than desired concurrency
196
- * and the system is not currently overloaded
197
- * and this.isTaskReadyFunction() returns true.
198
- *
199
- * It doesn't allow multiple concurrent runs of this method.
200
- */
201
- private maybeRunTask;
202
- /**
203
- * If there are no running tasks and this.isFinishedFunction() returns true then closes
204
- * the pool and resolves the pool's promise returned by the run() method.
205
- *
206
- * It doesn't allow multiple concurrent runs of this method.
207
- */
208
- private maybeFinish;
209
- /**
210
- * Cleans up resources.
211
- */
212
- private destroy;
213
- }
@@ -1,378 +0,0 @@
1
- import ow from 'ow';
2
- import { addTimeoutToPromise } from '@apify/timeout';
3
- import { betterClearInterval, betterSetInterval } from '@apify/utilities';
4
- import { CriticalError } from '../errors.js';
5
- import { serviceLocator } from '../service_locator.js';
6
- /**
7
- * Manages a pool of asynchronous resource-intensive tasks that are executed in parallel.
8
- * The pool only starts new tasks while its {@link IConcurrencySystem|concurrency system} reports free capacity —
9
- * that governor is what monitors CPU, memory and event loop load and autoscales the concurrency budget.
10
- *
11
- * Before running the pool, you need to implement the following three functions:
12
- * {@link AutoscaledPoolOptions.runTaskFunction|`runTaskFunction`},
13
- * {@link TaskLoopPredicates.isTaskReadyFunction|`isTaskReadyFunction`} and
14
- * {@link TaskLoopPredicates.isFinishedFunction|`isFinishedFunction`}.
15
- *
16
- * The auto-scaled pool is started by calling the {@link AutoscaledPool.run} function.
17
- * The pool periodically queries `isTaskReadyFunction` for more tasks, managing optimal concurrency, until the function
18
- * resolves to `false`. The pool then queries `isFinishedFunction`. If it resolves to `true`, the run finishes after all
19
- * running tasks complete. If it resolves to `false`, it assumes there will be more tasks available later and keeps
20
- * periodically querying for tasks.
21
- * If any of the tasks throws then the {@link AutoscaledPool.run} function rejects the promise with an error.
22
- *
23
- * The pool evaluates whether it should start a new task every time one of the tasks finishes
24
- * and also in the interval set by the `options.maybeRunIntervalSecs` parameter.
25
- *
26
- * **Example usage:**
27
- *
28
- * ```javascript
29
- * const concurrencySystem = new ConcurrencySystem({ maxConcurrency: 50 });
30
- * await concurrencySystem.start();
31
- *
32
- * const pool = new AutoscaledPool({
33
- * concurrencySystem,
34
- * consumer: { id: 'my-pool' },
35
- * runTaskFunction: async () => {
36
- * // Run some resource-intensive asynchronous operation here.
37
- * },
38
- * isTaskReadyFunction: async () => {
39
- * // Tell the pool whether more tasks are ready to be processed.
40
- * // Return true or false
41
- * },
42
- * isFinishedFunction: async () => {
43
- * // Tell the pool whether it should finish
44
- * // or wait for more tasks to become available.
45
- * // Return true or false
46
- * }
47
- * });
48
- *
49
- * try {
50
- * await pool.run();
51
- * } finally {
52
- * await concurrencySystem.stop();
53
- * }
54
- * ```
55
- *
56
- * @internal
57
- */
58
- export class AutoscaledPool {
59
- log;
60
- // Configurable properties.
61
- maybeRunIntervalMillis;
62
- taskTimeoutMillis;
63
- runTaskFunction;
64
- isFinishedFunction;
65
- isTaskReadyFunction;
66
- concurrencySystem;
67
- consumer;
68
- // Internal properties.
69
- isStopped = false;
70
- resolve = null;
71
- reject = null;
72
- maybeRunInterval;
73
- queryingIsTaskReady;
74
- queryingIsFinished;
75
- /**
76
- * This pool's *own* in-flight task count, as opposed to {@link AutoscaledPool.currentConcurrency}, which is the
77
- * (possibly shared) governor's total. `pause()` and `maybeFinish()` care only about this pool draining.
78
- */
79
- ownConcurrency = 0;
80
- constructor(options) {
81
- ow(options, ow.object.exactShape({
82
- runTaskFunction: ow.function,
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' });
93
- // Configurable properties.
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
- // Internal properties.
102
- this.isStopped = false;
103
- this.resolve = null;
104
- this.reject = null;
105
- this.maybeRunTask = this.maybeRunTask.bind(this);
106
- }
107
- /**
108
- * The governor backing this pool, as supplied to the constructor — exposed as the read-only
109
- * {@link IConcurrencySystem} contract.
110
- *
111
- * This and the two getters below are telemetry only: concurrency is configured and tuned on the concrete
112
- * {@link ConcurrencySystem} its owner holds, never through the pool.
113
- */
114
- get system() {
115
- return this.concurrencySystem;
116
- }
117
- /** The estimated number of parallel tasks the governor can currently support. */
118
- get desiredConcurrency() {
119
- return this.concurrencySystem.desiredConcurrency;
120
- }
121
- /**
122
- * The number of parallel tasks currently booked against the governor. When it is shared, this counts every
123
- * borrowing pool's tasks, not just this one's.
124
- */
125
- get currentConcurrency() {
126
- return this.concurrencySystem.currentConcurrency;
127
- }
128
- /**
129
- * Runs the auto-scaled pool. Returns a promise that gets resolved or rejected once
130
- * all the tasks are finished or one of them fails.
131
- *
132
- * Throws if the {@link IConcurrencySystem|concurrency system} it borrows was never started — the pool assumes
133
- * a running governor and cannot start one it does not own.
134
- */
135
- async run() {
136
- // Checked here, on an awaited path — the capacity queries inside the task loop run from intervals and
137
- // `setImmediate`, where a throw would become an unhandled rejection and hang `run()` forever.
138
- if (!this.concurrencySystem.isRunning) {
139
- throw new CriticalError('The ConcurrencySystem this AutoscaledPool borrows has not been started, so system load would not be ' +
140
- 'monitored and the concurrency would never be adjusted. Whoever creates a ConcurrencySystem owns ' +
141
- 'its lifecycle: call `await concurrencySystem.start()` before running the pools or crawlers that ' +
142
- 'use it, and `await concurrencySystem.stop()` once they are all done.');
143
- }
144
- const poolPromise = new Promise((resolve, reject) => {
145
- this.resolve = resolve;
146
- this.reject = reject;
147
- });
148
- // This is here because if we scale down to let's say 1, then after each promise is finished
149
- // this.maybeRunTask() doesn't trigger another one. So if that 1 instance gets stuck it results
150
- // in the crawler getting stuck and even after scaling up it never triggers another promise.
151
- this.maybeRunInterval = betterSetInterval(this.maybeRunTask, this.maybeRunIntervalMillis);
152
- try {
153
- await poolPromise;
154
- }
155
- finally {
156
- // If resolve is null, the pool is already destroyed.
157
- if (this.resolve)
158
- await this.destroy();
159
- }
160
- }
161
- /**
162
- * Aborts the run of the auto-scaled pool and destroys it. The promise returned from
163
- * the {@link AutoscaledPool.run} function will immediately resolve, no more new tasks
164
- * will be spawned and all running tasks will be left in their current state.
165
- *
166
- * Due to the nature of the tasks, auto-scaled pool cannot reliably guarantee abortion
167
- * of all the running tasks, therefore, no abortion is attempted and some of the tasks
168
- * may finish, while others may not. Essentially, auto-scaled pool doesn't care about
169
- * their state after the invocation of `.abort()`, but that does not mean that some
170
- * parts of their asynchronous chains of commands will not execute.
171
- */
172
- async abort() {
173
- this.isStopped = true;
174
- if (this.resolve) {
175
- this.resolve();
176
- await this.destroy();
177
- }
178
- }
179
- /**
180
- * Prevents the auto-scaled pool from starting new tasks, but allows the running ones to finish
181
- * (unlike abort, which terminates them). Used together with {@link AutoscaledPool.resume}
182
- *
183
- * The function's promise will resolve once all running tasks have completed and the pool
184
- * is effectively idle. If the `timeoutSecs` argument is provided, the promise will reject
185
- * with a timeout error after the `timeoutSecs` seconds.
186
- *
187
- * The promise returned from the {@link AutoscaledPool.run} function will not resolve
188
- * when `.pause()` is invoked (unlike abort, which resolves it).
189
- *
190
- * > *NOTE:* Pausing the pool does not suspend the (possibly shared) {@link ConcurrencySystem} — its
191
- * autoscaling and resource monitoring keep running, since other pools borrowing it may still be active. To silence
192
- * it during a long pause, its owner can `stop()` and `start()` it again.
193
- */
194
- async pause(timeoutSecs) {
195
- if (this.isStopped)
196
- return;
197
- this.isStopped = true;
198
- await new Promise((resolve, reject) => {
199
- let timeout;
200
- let interval;
201
- if (timeoutSecs) {
202
- timeout = setTimeout(() => {
203
- // Clean up the polling interval to prevent it from leaking on timeout.
204
- clearInterval(interval);
205
- const err = new Error("The pool's running tasks did not finish" +
206
- `in ${timeoutSecs} secs after pool.pause() invocation.`);
207
- reject(err);
208
- }, timeoutSecs);
209
- }
210
- interval = setInterval(() => {
211
- if (this.ownConcurrency <= 0) {
212
- // Clean up timeout and interval to prevent process hanging.
213
- if (timeout)
214
- clearTimeout(timeout);
215
- clearInterval(interval);
216
- resolve();
217
- }
218
- }, this.maybeRunIntervalMillis);
219
- });
220
- }
221
- /**
222
- * Resumes the operation of the autoscaled-pool by allowing more tasks to be run.
223
- * Used together with {@link AutoscaledPool.pause}
224
- *
225
- * Tasks will automatically start running again in `options.maybeRunIntervalSecs`.
226
- */
227
- resume() {
228
- this.isStopped = false;
229
- }
230
- /**
231
- * Explicitly check the queue for new tasks. The AutoscaledPool checks the queue for new tasks periodically,
232
- * every `maybeRunIntervalSecs` seconds. If you want to trigger the processing immediately, use this method.
233
- */
234
- async notify() {
235
- setImmediate(this.maybeRunTask);
236
- }
237
- /**
238
- * Starts a new task
239
- * if the number of running tasks (current concurrency) is lower than desired concurrency
240
- * and the system is not currently overloaded
241
- * and this.isTaskReadyFunction() returns true.
242
- *
243
- * It doesn't allow multiple concurrent runs of this method.
244
- */
245
- async maybeRunTask(intervalCallback) {
246
- this.log.perf('Attempting to run a task.');
247
- // Check if the function was invoked by the maybeRunInterval and use an empty function if not.
248
- const done = intervalCallback || (() => { });
249
- // Prevent starting a new task if:
250
- // - the pool is paused or aborted
251
- if (this.isStopped) {
252
- this.log.perf('Task will not run. AutoscaledPool is stopped.');
253
- return done();
254
- }
255
- // - we are already querying for a task.
256
- if (this.queryingIsTaskReady) {
257
- this.log.perf('Task will not run. Waiting for a ready task.');
258
- return done();
259
- }
260
- // - the budget has room for us.
261
- if (!this.concurrencySystem.hasCapacityForTask(this.consumer)) {
262
- done();
263
- // A shared governor's budget can stay saturated by another pool indefinitely, so we still have to be able
264
- // to notice that *this* pool has run out of work — `maybeFinish()` is the only thing that ever resolves
265
- // `run()`. It no-ops while this pool has tasks of its own in flight, which is every case in which an
266
- // unshared governor reports no capacity.
267
- return this.maybeFinish();
268
- }
269
- // - a task is ready.
270
- this.queryingIsTaskReady = true;
271
- let isTaskReady;
272
- try {
273
- this.log.perf('Checking for ready tasks.');
274
- isTaskReady = await this.isTaskReadyFunction();
275
- }
276
- catch (e) {
277
- const err = e;
278
- this.log.perf('Checking for ready tasks failed.');
279
- // We might have already rejected this promise.
280
- if (this.reject) {
281
- // No need to log all concurrent errors.
282
- this.log.exception(err, 'isTaskReadyFunction failed');
283
- this.reject(err);
284
- }
285
- }
286
- finally {
287
- this.queryingIsTaskReady = false;
288
- }
289
- if (!isTaskReady) {
290
- this.log.perf('Task will not run. No tasks are ready.');
291
- done();
292
- // No tasks could mean that we're finished with all tasks.
293
- return this.maybeFinish();
294
- }
295
- // - the budget still has room. Re-checked atomically, because another pool sharing the governor may have taken
296
- // the last free slot while we awaited `isTaskReadyFunction` above.
297
- if (!this.concurrencySystem.tryRegisterTaskStart(this.consumer)) {
298
- return done();
299
- }
300
- this.ownConcurrency++;
301
- try {
302
- // Everything's fine. Run task.
303
- // Try to run next task to build up concurrency,
304
- // but defer it so it doesn't create a cycle.
305
- setImmediate(this.maybeRunTask);
306
- // We need to restart interval here, so that it doesn't get blocked by a stalled task.
307
- done();
308
- // Execute the current task.
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
- }
313
- else {
314
- await this.runTaskFunction();
315
- }
316
- this.log.perf('Task finished.');
317
- // Run task after the previous one finished. Only on success: a failed task rejects the pool, and
318
- // nudging the loop afterwards could start work on an already destroyed pool.
319
- setImmediate(this.maybeRunTask);
320
- }
321
- catch (e) {
322
- const err = e;
323
- this.log.perf('Running a task failed.');
324
- // We might have already rejected this promise.
325
- if (this.reject) {
326
- // No need to log all concurrent errors.
327
- if (
328
- // avoid reprinting the same critical error multiple times, as it will be printed by Nodejs at the end anyway
329
- !(e instanceof CriticalError)) {
330
- this.log.exception(err, 'runTaskFunction failed.');
331
- }
332
- this.reject(err);
333
- }
334
- }
335
- finally {
336
- this.concurrencySystem.registerTaskEnd(this.consumer);
337
- this.ownConcurrency--;
338
- }
339
- return undefined;
340
- }
341
- /**
342
- * If there are no running tasks and this.isFinishedFunction() returns true then closes
343
- * the pool and resolves the pool's promise returned by the run() method.
344
- *
345
- * It doesn't allow multiple concurrent runs of this method.
346
- */
347
- async maybeFinish() {
348
- if (this.queryingIsFinished)
349
- return;
350
- if (this.ownConcurrency > 0)
351
- return;
352
- this.queryingIsFinished = true;
353
- try {
354
- const isFinished = await this.isFinishedFunction();
355
- if (isFinished && this.resolve)
356
- this.resolve();
357
- }
358
- catch (e) {
359
- const err = e;
360
- if (this.reject) {
361
- // No need to log all concurrent errors.
362
- this.log.exception(err, 'isFinishedFunction failed.');
363
- this.reject(err);
364
- }
365
- }
366
- finally {
367
- this.queryingIsFinished = false;
368
- }
369
- }
370
- /**
371
- * Cleans up resources.
372
- */
373
- async destroy() {
374
- this.resolve = null;
375
- this.reject = null;
376
- betterClearInterval(this.maybeRunInterval);
377
- }
378
- }
@@ -1,59 +0,0 @@
1
- import type { LoadSignal, LoadSignalStartContext, LoadSnapshot } from './load_signal.js';
2
- /**
3
- * A snapshot produced by the built-in client (rate-limit) signal.
4
- * @internal
5
- */
6
- export interface ClientSnapshot extends LoadSnapshot {
7
- rateLimitErrorCount: number;
8
- }
9
- /**
10
- * Tuning for the built-in **client** (rate-limit) load signal, as accepted both by {@link ClientLoadSignal} and by
11
- * the {@link LoadSignalsOptions.client|`client`} shorthand on {@link LoadSignalsOptions}.
12
- */
13
- export interface ClientLoadSignalOptions {
14
- /**
15
- * Defines the interval of checking the current state of the remote API client, in seconds.
16
- * @default 1
17
- */
18
- snapshotIntervalSecs?: number;
19
- /**
20
- * Defines the maximum number of new rate limit errors within the given interval.
21
- * @default 3
22
- */
23
- maxErrors?: number;
24
- /**
25
- * Maximum ratio of overloaded snapshots in a sample before the client counts as overloaded.
26
- * @default 0.3
27
- */
28
- overloadedRatio?: number;
29
- }
30
- /**
31
- * Periodically checks the storage backend for rate-limit errors (HTTP 429) and reports overload when the error delta
32
- * exceeds a threshold.
33
- *
34
- * Built by default; construct one yourself only to wrap or adapt it — see {@link LoadSignal}.
35
- *
36
- * Switch it off entirely ({@link LoadSignalsOptions.client|`client: false`}) if the storage backend reports no
37
- * rate-limit statistics, since it otherwise polls it every second to no purpose.
38
- *
39
- * @category Scaling
40
- */
41
- export declare class ClientLoadSignal implements LoadSignal {
42
- readonly name = "clientInfo";
43
- readonly overloadedRatio: number;
44
- private readonly store;
45
- private readonly intervalMillis;
46
- private readonly maxErrors;
47
- private interval?;
48
- private client?;
49
- constructor(options?: ClientLoadSignalOptions);
50
- start(context: LoadSignalStartContext): Promise<void>;
51
- stop(): Promise<void>;
52
- getSample(sampleDurationMillis?: number): LoadSnapshot[];
53
- /**
54
- * Records one snapshot, overloaded when rate-limit errors grew by more than the configured limit since the
55
- * previous one.
56
- * @internal Also lets tests drive the measurement without waiting on a timer.
57
- */
58
- handle(intervalCallback: () => unknown): void;
59
- }