@crawlee/core 4.0.0-beta.12 → 4.0.0-beta.120

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 (279) hide show
  1. package/README.md +17 -13
  2. package/autoscaling/autoscaled_pool.d.ts +67 -172
  3. package/autoscaling/autoscaled_pool.js +165 -320
  4. package/autoscaling/client_load_signal.d.ts +55 -0
  5. package/autoscaling/client_load_signal.js +73 -0
  6. package/autoscaling/concurrency_system.d.ts +268 -0
  7. package/autoscaling/concurrency_system.js +351 -0
  8. package/autoscaling/cpu_load_signal.d.ts +43 -0
  9. package/autoscaling/cpu_load_signal.js +47 -0
  10. package/autoscaling/event_loop_load_signal.d.ts +51 -0
  11. package/autoscaling/event_loop_load_signal.js +60 -0
  12. package/autoscaling/index.d.ts +6 -1
  13. package/autoscaling/index.js +6 -1
  14. package/autoscaling/load_signal.d.ts +99 -0
  15. package/autoscaling/load_signal.js +104 -0
  16. package/autoscaling/memory_load_signal.d.ts +47 -0
  17. package/autoscaling/memory_load_signal.js +106 -0
  18. package/autoscaling/snapshotter.d.ts +58 -163
  19. package/autoscaling/snapshotter.js +45 -263
  20. package/autoscaling/system_status.d.ts +62 -84
  21. package/autoscaling/system_status.js +92 -122
  22. package/autoscaling/weighted_avg.d.ts +5 -0
  23. package/autoscaling/weighted_avg.js +14 -0
  24. package/byte_utils.d.ts +17 -0
  25. package/byte_utils.js +42 -0
  26. package/configuration.d.ts +96 -223
  27. package/configuration.js +170 -222
  28. package/cookie_utils.d.ts +4 -3
  29. package/cookie_utils.js +22 -13
  30. package/crawlers/context_pipeline.d.ts +10 -1
  31. package/crawlers/context_pipeline.js +31 -8
  32. package/crawlers/crawler_commons.d.ts +90 -83
  33. package/crawlers/crawler_commons.js +1 -116
  34. package/crawlers/error_snapshotter.d.ts +1 -4
  35. package/crawlers/error_snapshotter.js +3 -6
  36. package/crawlers/error_tracker.d.ts +0 -1
  37. package/crawlers/error_tracker.js +0 -1
  38. package/crawlers/index.d.ts +1 -3
  39. package/crawlers/index.js +0 -3
  40. package/crawlers/internals/types.d.ts +0 -1
  41. package/crawlers/internals/types.js +0 -1
  42. package/crawlers/statistics.d.ts +143 -59
  43. package/crawlers/statistics.js +243 -153
  44. package/debug.d.ts +36 -0
  45. package/debug.js +70 -0
  46. package/enqueue_links/enqueue_links.d.ts +59 -68
  47. package/enqueue_links/enqueue_links.js +57 -62
  48. package/enqueue_links/index.d.ts +0 -1
  49. package/enqueue_links/index.js +0 -1
  50. package/enqueue_links/shared.d.ts +40 -27
  51. package/enqueue_links/shared.js +90 -68
  52. package/errors.d.ts +72 -4
  53. package/errors.js +89 -5
  54. package/events/event_manager.d.ts +35 -9
  55. package/events/event_manager.js +10 -12
  56. package/events/index.d.ts +0 -1
  57. package/events/index.js +0 -1
  58. package/events/local_event_manager.d.ts +15 -3
  59. package/events/local_event_manager.js +39 -13
  60. package/http.d.ts +9 -0
  61. package/http.js +28 -0
  62. package/index.d.ts +7 -4
  63. package/index.js +6 -3
  64. package/iterables.d.ts +79 -0
  65. package/iterables.js +134 -0
  66. package/log.d.ts +82 -3
  67. package/log.js +106 -1
  68. package/memory-storage/consts.d.ts +4 -0
  69. package/memory-storage/consts.js +4 -0
  70. package/memory-storage/index.d.ts +1 -0
  71. package/memory-storage/index.js +1 -0
  72. package/memory-storage/memory-storage.d.ts +38 -0
  73. package/memory-storage/memory-storage.js +130 -0
  74. package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
  75. package/memory-storage/resource-clients/common/base-client.js +6 -0
  76. package/memory-storage/resource-clients/dataset.d.ts +40 -0
  77. package/memory-storage/resource-clients/dataset.js +114 -0
  78. package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
  79. package/memory-storage/resource-clients/key-value-store.js +204 -0
  80. package/memory-storage/resource-clients/request-queue.d.ts +77 -0
  81. package/memory-storage/resource-clients/request-queue.js +422 -0
  82. package/memory-storage/utils.d.ts +16 -0
  83. package/memory-storage/utils.js +41 -0
  84. package/owned_or_injected.d.ts +58 -0
  85. package/owned_or_injected.js +98 -0
  86. package/package.json +13 -12
  87. package/proxy_configuration.d.ts +24 -132
  88. package/proxy_configuration.js +24 -143
  89. package/recoverable_state.d.ts +140 -0
  90. package/recoverable_state.js +212 -0
  91. package/request.d.ts +86 -17
  92. package/request.js +117 -41
  93. package/router.d.ts +193 -21
  94. package/router.js +188 -43
  95. package/serialization.d.ts +0 -1
  96. package/serialization.js +9 -11
  97. package/service_locator.d.ts +147 -0
  98. package/service_locator.js +244 -0
  99. package/session_pool/consts.d.ts +1 -2
  100. package/session_pool/consts.js +1 -2
  101. package/session_pool/errors.d.ts +0 -1
  102. package/session_pool/errors.js +0 -1
  103. package/session_pool/fingerprint.d.ts +9 -0
  104. package/session_pool/fingerprint.js +30 -0
  105. package/session_pool/index.d.ts +0 -2
  106. package/session_pool/index.js +0 -2
  107. package/session_pool/session.d.ts +35 -89
  108. package/session_pool/session.js +82 -142
  109. package/session_pool/session_pool.d.ts +69 -90
  110. package/session_pool/session_pool.js +151 -150
  111. package/storages/batched_adds.d.ts +37 -0
  112. package/storages/batched_adds.js +73 -0
  113. package/storages/dataset.d.ts +114 -54
  114. package/storages/dataset.js +285 -144
  115. package/storages/index.d.ts +10 -8
  116. package/storages/index.js +8 -8
  117. package/storages/key_value_store.d.ts +185 -42
  118. package/storages/key_value_store.js +424 -151
  119. package/storages/key_value_store_codec.d.ts +32 -0
  120. package/storages/key_value_store_codec.js +113 -0
  121. package/storages/request_dedup_cache.d.ts +22 -0
  122. package/storages/request_dedup_cache.js +48 -0
  123. package/storages/request_list.d.ts +52 -116
  124. package/storages/request_list.js +159 -133
  125. package/storages/request_loader.d.ts +101 -0
  126. package/storages/request_loader.js +1 -0
  127. package/storages/request_manager.d.ts +33 -0
  128. package/storages/request_manager.js +1 -0
  129. package/storages/request_manager_tandem.d.ts +97 -0
  130. package/storages/request_manager_tandem.js +197 -0
  131. package/storages/request_queue.d.ts +290 -47
  132. package/storages/request_queue.js +757 -216
  133. package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +37 -88
  134. package/storages/{sitemap_request_list.js → sitemap_request_loader.js} +137 -143
  135. package/storages/storage_instance_manager.d.ts +87 -0
  136. package/storages/storage_instance_manager.js +256 -0
  137. package/storages/storage_stats.d.ts +48 -0
  138. package/storages/storage_stats.js +29 -0
  139. package/storages/throttling_request_manager.d.ts +216 -0
  140. package/storages/throttling_request_manager.js +453 -0
  141. package/storages/transaction.d.ts +252 -0
  142. package/storages/transaction.js +251 -0
  143. package/storages/utils.d.ts +58 -11
  144. package/storages/utils.js +64 -13
  145. package/system-info/cpu-info.d.ts +67 -0
  146. package/system-info/cpu-info.js +216 -0
  147. package/system-info/memory-info.d.ts +31 -0
  148. package/system-info/memory-info.js +115 -0
  149. package/system-info/ps-tree.d.ts +17 -0
  150. package/system-info/ps-tree.js +144 -0
  151. package/system-info/runtime.d.ts +14 -0
  152. package/system-info/runtime.js +80 -0
  153. package/typedefs.d.ts +0 -6
  154. package/typedefs.js +0 -1
  155. package/url.d.ts +9 -0
  156. package/url.js +11 -0
  157. package/validators.d.ts +8 -1
  158. package/validators.js +10 -3
  159. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  160. package/autoscaling/autoscaled_pool.js.map +0 -1
  161. package/autoscaling/index.d.ts.map +0 -1
  162. package/autoscaling/index.js.map +0 -1
  163. package/autoscaling/snapshotter.d.ts.map +0 -1
  164. package/autoscaling/snapshotter.js.map +0 -1
  165. package/autoscaling/system_status.d.ts.map +0 -1
  166. package/autoscaling/system_status.js.map +0 -1
  167. package/configuration.d.ts.map +0 -1
  168. package/configuration.js.map +0 -1
  169. package/cookie_utils.d.ts.map +0 -1
  170. package/cookie_utils.js.map +0 -1
  171. package/crawlers/context_pipeline.d.ts.map +0 -1
  172. package/crawlers/context_pipeline.js.map +0 -1
  173. package/crawlers/crawler_commons.d.ts.map +0 -1
  174. package/crawlers/crawler_commons.js.map +0 -1
  175. package/crawlers/crawler_utils.d.ts +0 -10
  176. package/crawlers/crawler_utils.d.ts.map +0 -1
  177. package/crawlers/crawler_utils.js +0 -12
  178. package/crawlers/crawler_utils.js.map +0 -1
  179. package/crawlers/error_snapshotter.d.ts.map +0 -1
  180. package/crawlers/error_snapshotter.js.map +0 -1
  181. package/crawlers/error_tracker.d.ts.map +0 -1
  182. package/crawlers/error_tracker.js.map +0 -1
  183. package/crawlers/index.d.ts.map +0 -1
  184. package/crawlers/index.js.map +0 -1
  185. package/crawlers/internals/types.d.ts.map +0 -1
  186. package/crawlers/internals/types.js.map +0 -1
  187. package/crawlers/statistics.d.ts.map +0 -1
  188. package/crawlers/statistics.js.map +0 -1
  189. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  190. package/enqueue_links/enqueue_links.js.map +0 -1
  191. package/enqueue_links/index.d.ts.map +0 -1
  192. package/enqueue_links/index.js.map +0 -1
  193. package/enqueue_links/shared.d.ts.map +0 -1
  194. package/enqueue_links/shared.js.map +0 -1
  195. package/errors.d.ts.map +0 -1
  196. package/errors.js.map +0 -1
  197. package/events/event_manager.d.ts.map +0 -1
  198. package/events/event_manager.js.map +0 -1
  199. package/events/index.d.ts.map +0 -1
  200. package/events/index.js.map +0 -1
  201. package/events/local_event_manager.d.ts.map +0 -1
  202. package/events/local_event_manager.js.map +0 -1
  203. package/http_clients/base-http-client.d.ts +0 -134
  204. package/http_clients/base-http-client.d.ts.map +0 -1
  205. package/http_clients/base-http-client.js +0 -33
  206. package/http_clients/base-http-client.js.map +0 -1
  207. package/http_clients/form-data-like.d.ts +0 -67
  208. package/http_clients/form-data-like.d.ts.map +0 -1
  209. package/http_clients/form-data-like.js +0 -5
  210. package/http_clients/form-data-like.js.map +0 -1
  211. package/http_clients/got-scraping-http-client.d.ts +0 -15
  212. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  213. package/http_clients/got-scraping-http-client.js +0 -69
  214. package/http_clients/got-scraping-http-client.js.map +0 -1
  215. package/http_clients/index.d.ts +0 -3
  216. package/http_clients/index.d.ts.map +0 -1
  217. package/http_clients/index.js +0 -3
  218. package/http_clients/index.js.map +0 -1
  219. package/index.d.ts.map +0 -1
  220. package/index.js.map +0 -1
  221. package/log.d.ts.map +0 -1
  222. package/log.js.map +0 -1
  223. package/proxy_configuration.d.ts.map +0 -1
  224. package/proxy_configuration.js.map +0 -1
  225. package/request.d.ts.map +0 -1
  226. package/request.js.map +0 -1
  227. package/router.d.ts.map +0 -1
  228. package/router.js.map +0 -1
  229. package/serialization.d.ts.map +0 -1
  230. package/serialization.js.map +0 -1
  231. package/session_pool/consts.d.ts.map +0 -1
  232. package/session_pool/consts.js.map +0 -1
  233. package/session_pool/errors.d.ts.map +0 -1
  234. package/session_pool/errors.js.map +0 -1
  235. package/session_pool/events.d.ts +0 -3
  236. package/session_pool/events.d.ts.map +0 -1
  237. package/session_pool/events.js +0 -3
  238. package/session_pool/events.js.map +0 -1
  239. package/session_pool/index.d.ts.map +0 -1
  240. package/session_pool/index.js.map +0 -1
  241. package/session_pool/session.d.ts.map +0 -1
  242. package/session_pool/session.js.map +0 -1
  243. package/session_pool/session_pool.d.ts.map +0 -1
  244. package/session_pool/session_pool.js.map +0 -1
  245. package/storages/access_checking.d.ts +0 -13
  246. package/storages/access_checking.d.ts.map +0 -1
  247. package/storages/access_checking.js +0 -14
  248. package/storages/access_checking.js.map +0 -1
  249. package/storages/dataset.d.ts.map +0 -1
  250. package/storages/dataset.js.map +0 -1
  251. package/storages/index.d.ts.map +0 -1
  252. package/storages/index.js.map +0 -1
  253. package/storages/key_value_store.d.ts.map +0 -1
  254. package/storages/key_value_store.js.map +0 -1
  255. package/storages/request_list.d.ts.map +0 -1
  256. package/storages/request_list.js.map +0 -1
  257. package/storages/request_provider.d.ts +0 -308
  258. package/storages/request_provider.d.ts.map +0 -1
  259. package/storages/request_provider.js +0 -555
  260. package/storages/request_provider.js.map +0 -1
  261. package/storages/request_queue.d.ts.map +0 -1
  262. package/storages/request_queue.js.map +0 -1
  263. package/storages/request_queue_v2.d.ts +0 -87
  264. package/storages/request_queue_v2.d.ts.map +0 -1
  265. package/storages/request_queue_v2.js +0 -438
  266. package/storages/request_queue_v2.js.map +0 -1
  267. package/storages/sitemap_request_list.d.ts.map +0 -1
  268. package/storages/sitemap_request_list.js.map +0 -1
  269. package/storages/storage_manager.d.ts +0 -58
  270. package/storages/storage_manager.d.ts.map +0 -1
  271. package/storages/storage_manager.js +0 -105
  272. package/storages/storage_manager.js.map +0 -1
  273. package/storages/utils.d.ts.map +0 -1
  274. package/storages/utils.js.map +0 -1
  275. package/tsconfig.build.tsbuildinfo +0 -1
  276. package/typedefs.d.ts.map +0 -1
  277. package/typedefs.js.map +0 -1
  278. package/validators.d.ts.map +0 -1
  279. package/validators.js.map +0 -1
@@ -1,31 +1,23 @@
1
1
  import ow from 'ow';
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 { log as defaultLog } from '../log.js';
7
- import { Snapshotter } from './snapshotter.js';
8
- import { SystemStatus } from './system_status.js';
5
+ import { serviceLocator } from '../service_locator.js';
9
6
  /**
10
7
  * Manages a pool of asynchronous resource-intensive tasks that are executed in parallel.
11
- * The pool only starts new tasks if there is enough free CPU and memory available
12
- * and the Javascript event loop is not blocked.
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.
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.
18
10
  *
19
11
  * Before running the pool, you need to implement the following three functions:
20
- * {@link AutoscaledPoolOptions.runTaskFunction},
21
- * {@link AutoscaledPoolOptions.isTaskReadyFunction} and
22
- * {@link AutoscaledPoolOptions.isFinishedFunction}.
12
+ * {@link AutoscaledPoolOptions.runTaskFunction|`runTaskFunction`},
13
+ * {@link TaskLoopPredicates.isTaskReadyFunction|`isTaskReadyFunction`} and
14
+ * {@link TaskLoopPredicates.isFinishedFunction|`isFinishedFunction`}.
23
15
  *
24
16
  * The auto-scaled pool is started by calling the {@link AutoscaledPool.run} function.
25
- * The pool periodically queries the {@link AutoscaledPoolOptions.isTaskReadyFunction} function
26
- * for more tasks, managing optimal concurrency, until the function resolves to `false`. The pool then queries
27
- * the {@link AutoscaledPoolOptions.isFinishedFunction}. If it resolves to `true`, the run finishes after all running tasks complete.
28
- * If it resolves to `false`, it assumes there will be more tasks available later and keeps periodically querying for tasks.
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.
29
21
  * If any of the tasks throws then the {@link AutoscaledPool.run} function rejects the promise with an error.
30
22
  *
31
23
  * The pool evaluates whether it should start a new task every time one of the tasks finishes
@@ -34,8 +26,12 @@ import { SystemStatus } from './system_status.js';
34
26
  * **Example usage:**
35
27
  *
36
28
  * ```javascript
29
+ * const concurrencySystem = new ConcurrencySystem({ maxConcurrency: 50 });
30
+ * await concurrencySystem.start();
31
+ *
37
32
  * const pool = new AutoscaledPool({
38
- * maxConcurrency: 50,
33
+ * concurrencySystem,
34
+ * consumer: { id: 'my-pool' },
39
35
  * runTaskFunction: async () => {
40
36
  * // Run some resource-intensive asynchronous operation here.
41
37
  * },
@@ -50,177 +46,116 @@ import { SystemStatus } from './system_status.js';
50
46
  * }
51
47
  * });
52
48
  *
53
- * await pool.run();
49
+ * try {
50
+ * await pool.run();
51
+ * } finally {
52
+ * await concurrencySystem.stop();
53
+ * }
54
54
  * ```
55
- * @category Scaling
55
+ *
56
+ * @internal
56
57
  */
57
58
  export class AutoscaledPool {
58
- config;
59
- log;
59
+ #log;
60
60
  // Configurable properties.
61
- desiredConcurrencyRatio;
62
- scaleUpStepRatio;
63
- scaleDownStepRatio;
64
- maybeRunIntervalMillis;
65
- loggingIntervalMillis;
66
- autoscaleIntervalMillis;
67
- taskTimeoutMillis;
68
- runTaskFunction;
69
- isFinishedFunction;
70
- isTaskReadyFunction;
71
- maxTasksPerMinute;
61
+ #maybeRunIntervalMillis;
62
+ #taskTimeoutMillis;
63
+ #runTaskFunction;
64
+ #isFinishedFunction;
65
+ #isTaskReadyFunction;
66
+ #concurrencySystem;
67
+ #consumer;
72
68
  // Internal properties.
73
- _minConcurrency;
74
- _maxConcurrency;
75
- _desiredConcurrency;
76
- _currentConcurrency = 0;
77
- isStopped = false;
78
- lastLoggingTime;
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;
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) {
91
81
  ow(options, ow.object.exactShape({
92
82
  runTaskFunction: ow.function,
93
83
  isFinishedFunction: ow.function,
94
84
  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
85
  maybeRunIntervalSecs: ow.optional.number.greaterThan(0),
102
- loggingIntervalSecs: ow.any(ow.number.greaterThan(0), ow.nullOrUndefined),
103
- autoscaleIntervalSecs: ow.optional.number.greaterThan(0),
104
86
  taskTimeoutSecs: ow.optional.number.greaterThanOrEqual(0),
105
- systemStatusOptions: ow.optional.object,
106
- snapshotterOptions: ow.optional.object,
107
87
  log: ow.optional.object,
108
- maxTasksPerMinute: ow.optional.number.integerOrInfinite.greaterThanOrEqual(1),
88
+ concurrencySystem: ow.object,
89
+ consumer: ow.object.partialShape({ id: ow.string.nonEmpty }),
109
90
  }));
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' });
91
+ const { runTaskFunction, isFinishedFunction, isTaskReadyFunction, maybeRunIntervalSecs = 0.5, taskTimeoutSecs = 0, log = serviceLocator.getLogger(), concurrencySystem, consumer, } = options;
92
+ this.#log = log.child({ prefix: 'AutoscaledPool' });
112
93
  // 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;
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;
124
101
  // 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);
102
+ this.#isStopped = false;
103
+ this.#resolve = null;
104
+ this.#reject = null;
105
+ this.maybeRunTask = this.maybeRunTask.bind(this);
146
106
  }
147
107
  /**
148
- * Gets the minimum number of tasks running in parallel.
149
- */
150
- get minConcurrency() {
151
- return this._minConcurrency;
152
- }
153
- /**
154
- * Sets the minimum number of tasks running in parallel.
108
+ * The governor backing this pool, as supplied to the constructor exposed as the read-only
109
+ * {@link IConcurrencySystem} contract.
155
110
  *
156
- * *WARNING:* If you set this value too high with respect to the available system memory and CPU, your code might run extremely slow or crash.
157
- * If you're not sure, just keep the default value and the concurrency will scale up automatically.
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.
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.
165
113
  */
166
- get maxConcurrency() {
167
- return this._maxConcurrency;
114
+ get system() {
115
+ return this.#concurrencySystem;
168
116
  }
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
- */
117
+ /** The estimated number of parallel tasks the governor can currently support. */
180
118
  get desiredConcurrency() {
181
- return this._desiredConcurrency;
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;
119
+ return this.#concurrencySystem.desiredConcurrency;
190
120
  }
191
121
  /**
192
- * Gets the number of parallel tasks currently running in the pool.
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.
193
124
  */
194
125
  get currentConcurrency() {
195
- return this._currentConcurrency;
126
+ return this.#concurrencySystem.currentConcurrency;
196
127
  }
197
128
  /**
198
129
  * Runs the auto-scaled pool. Returns a promise that gets resolved or rejected once
199
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.
200
134
  */
201
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
+ }
202
144
  const poolPromise = new Promise((resolve, reject) => {
203
- this.resolve = resolve;
204
- this.reject = reject;
145
+ this.#resolve = resolve;
146
+ this.#reject = reject;
205
147
  });
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
148
  // This is here because if we scale down to let's say 1, then after each promise is finished
210
- // this._maybeRunTask() doesn't trigger another one. So if that 1 instance gets stuck it results
149
+ // this.maybeRunTask() doesn't trigger another one. So if that 1 instance gets stuck it results
211
150
  // in the crawler getting stuck and even after scaling up it never triggers another promise.
212
- this.maybeRunInterval = betterSetInterval(this._maybeRunTask, this.maybeRunIntervalMillis);
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
- }
151
+ this.#maybeRunInterval = betterSetInterval(this.maybeRunTask, this.#maybeRunIntervalMillis);
217
152
  try {
218
153
  await poolPromise;
219
154
  }
220
155
  finally {
221
156
  // If resolve is null, the pool is already destroyed.
222
- if (this.resolve)
223
- await this._destroy();
157
+ if (this.#resolve)
158
+ await this.destroy();
224
159
  }
225
160
  }
226
161
  /**
@@ -235,10 +170,10 @@ export class AutoscaledPool {
235
170
  * parts of their asynchronous chains of commands will not execute.
236
171
  */
237
172
  async abort() {
238
- this.isStopped = true;
239
- if (this.resolve) {
240
- this.resolve();
241
- await this._destroy();
173
+ this.#isStopped = true;
174
+ if (this.#resolve) {
175
+ this.#resolve();
176
+ await this.destroy();
242
177
  }
243
178
  }
244
179
  /**
@@ -251,29 +186,36 @@ export class AutoscaledPool {
251
186
  *
252
187
  * The promise returned from the {@link AutoscaledPool.run} function will not resolve
253
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.
254
193
  */
255
194
  async pause(timeoutSecs) {
256
- if (this.isStopped)
195
+ if (this.#isStopped)
257
196
  return;
258
- this.isStopped = true;
197
+ this.#isStopped = true;
259
198
  await new Promise((resolve, reject) => {
260
199
  let timeout;
200
+ let interval;
261
201
  if (timeoutSecs) {
262
202
  timeout = setTimeout(() => {
203
+ // Clean up the polling interval to prevent it from leaking on timeout.
204
+ clearInterval(interval);
263
205
  const err = new Error("The pool's running tasks did not finish" +
264
206
  `in ${timeoutSecs} secs after pool.pause() invocation.`);
265
207
  reject(err);
266
208
  }, timeoutSecs);
267
209
  }
268
- const interval = setInterval(() => {
269
- if (this._currentConcurrency <= 0) {
210
+ interval = setInterval(() => {
211
+ if (this.#ownConcurrency <= 0) {
270
212
  // Clean up timeout and interval to prevent process hanging.
271
213
  if (timeout)
272
214
  clearTimeout(timeout);
273
215
  clearInterval(interval);
274
216
  resolve();
275
217
  }
276
- }, this.maybeRunIntervalMillis);
218
+ }, this.#maybeRunIntervalMillis);
277
219
  });
278
220
  }
279
221
  /**
@@ -283,251 +225,154 @@ export class AutoscaledPool {
283
225
  * Tasks will automatically start running again in `options.maybeRunIntervalSecs`.
284
226
  */
285
227
  resume() {
286
- this.isStopped = false;
228
+ this.#isStopped = false;
287
229
  }
288
230
  /**
289
231
  * Explicitly check the queue for new tasks. The AutoscaledPool checks the queue for new tasks periodically,
290
232
  * every `maybeRunIntervalSecs` seconds. If you want to trigger the processing immediately, use this method.
291
233
  */
292
234
  async notify() {
293
- setImmediate(this._maybeRunTask);
235
+ setImmediate(this.maybeRunTask);
294
236
  }
295
237
  /**
296
238
  * Starts a new task
297
239
  * if the number of running tasks (current concurrency) is lower than desired concurrency
298
240
  * and the system is not currently overloaded
299
- * and this.isTaskReadyFunction() returns true.
241
+ * and `isTaskReadyFunction()` returns true.
300
242
  *
301
243
  * It doesn't allow multiple concurrent runs of this method.
302
244
  */
303
- async _maybeRunTask(intervalCallback) {
304
- this.log.perf('Attempting to run a task.');
245
+ async maybeRunTask(intervalCallback) {
246
+ this.#log.perf('Attempting to run a task.');
305
247
  // Check if the function was invoked by the maybeRunInterval and use an empty function if not.
306
248
  const done = intervalCallback || (() => { });
307
249
  // Prevent starting a new task if:
308
250
  // - the pool is paused or aborted
309
- if (this.isStopped) {
310
- this.log.perf('Task will not run. AutoscaledPool is stopped.');
251
+ if (this.#isStopped) {
252
+ this.#log.perf('Task will not run. AutoscaledPool is stopped.');
311
253
  return done();
312
254
  }
313
255
  // - we are already querying for a task.
314
- if (this.queryingIsTaskReady) {
315
- this.log.perf('Task will not run. Waiting for a ready task.');
256
+ if (this.#queryingIsTaskReady) {
257
+ this.#log.perf('Task will not run. Waiting for a ready task.');
316
258
  return done();
317
259
  }
318
- // - we would exceed desired concurrency.
319
- if (this._currentConcurrency >= this._desiredConcurrency) {
320
- this.log.perf('Task will not run. Desired concurrency achieved.');
321
- return done();
322
- }
323
- // - system is overloaded now and we are at or above minConcurrency
324
- const currentStatus = this.systemStatus.getCurrentStatus();
325
- const { isSystemIdle } = currentStatus;
326
- if (!isSystemIdle && this._currentConcurrency >= this._minConcurrency) {
327
- this.log.perf('Task will not be run. System is overloaded.', currentStatus);
328
- return done();
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();
329
268
  }
330
269
  // - a task is ready.
331
- this.queryingIsTaskReady = true;
270
+ this.#queryingIsTaskReady = true;
332
271
  let isTaskReady;
333
272
  try {
334
- this.log.perf('Checking for ready tasks.');
335
- isTaskReady = await this.isTaskReadyFunction();
273
+ this.#log.perf('Checking for ready tasks.');
274
+ isTaskReady = await this.#isTaskReadyFunction();
336
275
  }
337
276
  catch (e) {
338
277
  const err = e;
339
- this.log.perf('Checking for ready tasks failed.');
278
+ this.#log.perf('Checking for ready tasks failed.');
340
279
  // We might have already rejected this promise.
341
- if (this.reject) {
280
+ if (this.#reject) {
342
281
  // No need to log all concurrent errors.
343
- this.log.exception(err, 'isTaskReadyFunction failed');
344
- this.reject(err);
282
+ this.#log.exception(err, 'isTaskReadyFunction failed');
283
+ this.#reject(err);
345
284
  }
346
285
  }
347
286
  finally {
348
- this.queryingIsTaskReady = false;
287
+ this.#queryingIsTaskReady = false;
349
288
  }
350
289
  if (!isTaskReady) {
351
- this.log.perf('Task will not run. No tasks are ready.');
290
+ this.#log.perf('Task will not run. No tasks are ready.');
352
291
  done();
353
292
  // No tasks could mean that we're finished with all tasks.
354
- return this._maybeFinish();
293
+ return this.maybeFinish();
355
294
  }
356
- // - we have already reached the maximum tasks per minute
357
- // we need to check this *after* checking if a task is ready to prevent hanging the pool
358
- // for an extra minute if there are no more tasks
359
- if (this._isOverMaxRequestLimit) {
360
- this.log.perf('Task will not run. Maximum tasks per minute reached.');
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)) {
361
298
  return done();
362
299
  }
300
+ this.#ownConcurrency++;
363
301
  try {
364
302
  // Everything's fine. Run task.
365
- this._currentConcurrency++;
366
- this._tasksPerMinute[0]++;
367
303
  // Try to run next task to build up concurrency,
368
304
  // but defer it so it doesn't create a cycle.
369
- setImmediate(this._maybeRunTask);
305
+ setImmediate(this.maybeRunTask);
370
306
  // We need to restart interval here, so that it doesn't get blocked by a stalled task.
371
307
  done();
372
308
  // Execute the current task.
373
- this.log.perf('Running a task.');
374
- if (this.taskTimeoutMillis > 0) {
375
- await addTimeoutToPromise(async () => this.runTaskFunction(), this.taskTimeoutMillis, `runTaskFunction timed out after ${this.taskTimeoutMillis / 1000} seconds.`);
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.`);
376
312
  }
377
313
  else {
378
- await this.runTaskFunction();
314
+ await this.#runTaskFunction();
379
315
  }
380
- this.log.perf('Task finished.');
381
- this._currentConcurrency--;
382
- // Run task after the previous one finished.
383
- setImmediate(this._maybeRunTask);
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);
384
320
  }
385
321
  catch (e) {
386
322
  const err = e;
387
- this.log.perf('Running a task failed.');
323
+ this.#log.perf('Running a task failed.');
388
324
  // We might have already rejected this promise.
389
- if (this.reject) {
325
+ if (this.#reject) {
390
326
  // No need to log all concurrent errors.
391
327
  if (
392
328
  // avoid reprinting the same critical error multiple times, as it will be printed by Nodejs at the end anyway
393
329
  !(e instanceof CriticalError)) {
394
- this.log.exception(err, 'runTaskFunction failed.');
330
+ this.#log.exception(err, 'runTaskFunction failed.');
395
331
  }
396
- this.reject(err);
332
+ this.#reject(err);
397
333
  }
398
334
  }
399
- return undefined;
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
- }
335
+ finally {
336
+ this.#concurrencySystem.registerTaskEnd(this.#consumer);
337
+ this.#ownConcurrency--;
445
338
  }
446
- // Start a new interval cycle.
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
- });
339
+ return undefined;
478
340
  }
479
341
  /**
480
- * If there are no running tasks and this.isFinishedFunction() returns true then closes
342
+ * If there are no running tasks and `isFinishedFunction()` returns true then closes
481
343
  * the pool and resolves the pool's promise returned by the run() method.
482
344
  *
483
345
  * It doesn't allow multiple concurrent runs of this method.
484
346
  */
485
- async _maybeFinish() {
486
- if (this.queryingIsFinished)
347
+ async maybeFinish() {
348
+ if (this.#queryingIsFinished)
487
349
  return;
488
- if (this._currentConcurrency > 0)
350
+ if (this.#ownConcurrency > 0)
489
351
  return;
490
- this.queryingIsFinished = true;
352
+ this.#queryingIsFinished = true;
491
353
  try {
492
- const isFinished = await this.isFinishedFunction();
493
- if (isFinished && this.resolve)
494
- this.resolve();
354
+ const isFinished = await this.#isFinishedFunction();
355
+ if (isFinished && this.#resolve)
356
+ this.#resolve();
495
357
  }
496
358
  catch (e) {
497
359
  const err = e;
498
- if (this.reject) {
360
+ if (this.#reject) {
499
361
  // No need to log all concurrent errors.
500
- this.log.exception(err, 'isFinishedFunction failed.');
501
- this.reject(err);
362
+ this.#log.exception(err, 'isFinishedFunction failed.');
363
+ this.#reject(err);
502
364
  }
503
365
  }
504
366
  finally {
505
- this.queryingIsFinished = false;
367
+ this.#queryingIsFinished = false;
506
368
  }
507
369
  }
508
370
  /**
509
371
  * Cleans up resources.
510
372
  */
511
- async _destroy() {
512
- this.resolve = null;
513
- this.reject = null;
514
- betterClearInterval(this.autoscaleInterval);
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;
373
+ async destroy() {
374
+ this.#resolve = null;
375
+ this.#reject = null;
376
+ betterClearInterval(this.#maybeRunInterval);
531
377
  }
532
378
  }
533
- //# sourceMappingURL=autoscaled_pool.js.map