@crawlee/core 4.0.0-beta.15 → 4.0.0-beta.150
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 +14 -14
- package/autoscaling/autoscaled_pool.d.ts +73 -177
- package/autoscaling/autoscaled_pool.js +182 -329
- package/autoscaling/concurrency_system.d.ts +268 -0
- package/autoscaling/concurrency_system.js +364 -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 +98 -0
- package/autoscaling/load_signal.js +103 -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 -126
- package/crawlers/crawler_commons.js +1 -108
- 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 -64
- package/crawlers/statistics.js +354 -165
- package/debug.d.ts +36 -0
- package/debug.js +70 -0
- package/enqueue_links/enqueue_links.d.ts +61 -154
- package/enqueue_links/enqueue_links.js +40 -232
- package/enqueue_links/index.d.ts +0 -1
- package/enqueue_links/index.js +0 -1
- package/enqueue_links/shared.d.ts +55 -28
- package/enqueue_links/shared.js +116 -69
- 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 -5
- package/index.js +8 -4
- package/iterables.d.ts +79 -0
- package/iterables.js +134 -0
- package/log.d.ts +77 -1
- package/log.js +104 -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 +44 -0
- package/memory-storage/memory-storage.js +160 -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 +106 -0
- package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
- package/memory-storage/resource-clients/key-value-store.js +199 -0
- package/memory-storage/resource-clients/request-queue.d.ts +77 -0
- package/memory-storage/resource-clients/request-queue.js +407 -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 +13 -12
- package/proxy_configuration.d.ts +22 -128
- package/proxy_configuration.js +32 -144
- package/recoverable_state.d.ts +83 -51
- package/recoverable_state.js +163 -72
- package/request.d.ts +57 -16
- package/request.js +130 -69
- 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 +39 -89
- package/session_pool/session.js +102 -159
- package/session_pool/session_pool.d.ts +67 -91
- package/session_pool/session_pool.js +196 -187
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +109 -56
- package/storages/dataset.js +284 -149
- package/storages/index.d.ts +9 -9
- package/storages/index.js +7 -9
- package/storages/key_value_store.d.ts +183 -48
- package/storages/key_value_store.js +444 -171
- 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 +53 -115
- package/storages/request_list.js +194 -167
- package/storages/request_loader.d.ts +133 -0
- package/storages/request_loader.js +36 -0
- package/storages/request_manager.d.ts +109 -0
- package/storages/request_manager.js +1 -0
- package/storages/request_manager_tandem.d.ts +55 -23
- package/storages/request_manager_tandem.js +139 -57
- package/storages/request_queue.d.ts +286 -48
- package/storages/request_queue.js +757 -218
- package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +43 -91
- package/storages/sitemap_request_loader.js +439 -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 +222 -0
- package/storages/throttling_request_manager.js +780 -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 -85
- 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/recoverable_state.d.ts.map +0 -1
- package/recoverable_state.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_list_adapter.d.ts +0 -58
- package/storages/request_list_adapter.d.ts.map +0 -1
- package/storages/request_list_adapter.js +0 -81
- package/storages/request_list_adapter.js.map +0 -1
- package/storages/request_manager_tandem.d.ts.map +0 -1
- package/storages/request_manager_tandem.js.map +0 -1
- package/storages/request_provider.d.ts +0 -371
- package/storages/request_provider.d.ts.map +0 -1
- package/storages/request_provider.js +0 -585
- 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/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,37 @@
|
|
|
1
|
-
import { EventEmitter } from 'node:events';
|
|
2
1
|
import { AsyncQueue } from '@sapphire/async-queue';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { EventType } from '../events/event_manager.js';
|
|
4
|
+
import { serviceLocator } from '../service_locator.js';
|
|
6
5
|
import { KeyValueStore } from '../storages/key_value_store.js';
|
|
7
|
-
import {
|
|
6
|
+
import { parseArgument, schemas, validators } from '../validators.js';
|
|
7
|
+
import { MAX_POOL_SIZE, PERSIST_STATE_KEY } from './consts.js';
|
|
8
|
+
import { createDefaultSessionFingerprint } from './fingerprint.js';
|
|
8
9
|
import { Session } from './session.js';
|
|
10
|
+
const SESSION_REUSE_STRATEGIES = ['random', 'round-robin', 'use-until-failure'];
|
|
11
|
+
// `schemas.anyObject` passes values through by reference (object schemas return a pruned plain
|
|
12
|
+
// copy), so class instances like loggers keep their prototype.
|
|
13
|
+
const sessionPoolOptionsSchema = z.strictObject({
|
|
14
|
+
id: z.union([schemas.anyNumber, z.string()]).optional(),
|
|
15
|
+
maxPoolSize: schemas.anyNumber.default(MAX_POOL_SIZE),
|
|
16
|
+
persistStateKeyValueStoreId: z.string().optional(),
|
|
17
|
+
persistStateKey: z.string().optional(),
|
|
18
|
+
createSessionFunction: schemas.anyFunction.optional(),
|
|
19
|
+
sessionOptions: schemas.anyObject.default(() => ({})),
|
|
20
|
+
log: validators.logger.default(() => serviceLocator.getLogger()),
|
|
21
|
+
persistenceOptions: schemas.anyObject.default(() => ({ enable: true })),
|
|
22
|
+
sessionReuseStrategy: z.enum(SESSION_REUSE_STRATEGIES).default('random'),
|
|
23
|
+
});
|
|
24
|
+
const createSessionOptionsSchema = z.strictObject({
|
|
25
|
+
sessionOptions: schemas.anyObject.default(() => ({})),
|
|
26
|
+
});
|
|
9
27
|
/**
|
|
10
28
|
* Handles the rotation, creation and persistence of user-like sessions.
|
|
11
29
|
* Creates a pool of {@link Session} instances, that are randomly rotated.
|
|
12
30
|
* When some session is marked as blocked, it is removed and new one is created instead (the pool never returns an unusable session).
|
|
13
31
|
* Learn more in the {@doclink guides/session-management | Session management guide}.
|
|
14
32
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Session pool is already integrated into crawlers, and it can significantly improve your scraper
|
|
18
|
-
* performance with just 2 lines of code.
|
|
19
|
-
*
|
|
20
|
-
* **Example usage:**
|
|
21
|
-
*
|
|
22
|
-
* ```javascript
|
|
23
|
-
* const crawler = new CheerioCrawler({
|
|
24
|
-
* useSessionPool: true,
|
|
25
|
-
* persistCookiesPerSession: true,
|
|
26
|
-
* // ...
|
|
27
|
-
* })
|
|
28
|
-
* ```
|
|
33
|
+
* Session pool is already integrated into crawlers and is always active.
|
|
34
|
+
* All public methods are lazy-initialized — the pool initializes itself on first use.
|
|
29
35
|
*
|
|
30
36
|
* You can configure the pool with many options. See the {@link SessionPoolOptions}.
|
|
31
37
|
* Session pool is by default persisted in default {@link KeyValueStore}.
|
|
@@ -35,7 +41,7 @@ import { Session } from './session.js';
|
|
|
35
41
|
* **Advanced usage:**
|
|
36
42
|
*
|
|
37
43
|
* ```javascript
|
|
38
|
-
* const sessionPool =
|
|
44
|
+
* const sessionPool = new SessionPool({
|
|
39
45
|
* maxPoolSize: 25,
|
|
40
46
|
* sessionOptions:{
|
|
41
47
|
* maxAgeSecs: 10,
|
|
@@ -71,94 +77,84 @@ import { Session } from './session.js';
|
|
|
71
77
|
*
|
|
72
78
|
* @category Scaling
|
|
73
79
|
*/
|
|
74
|
-
export class SessionPool
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
persistenceOptions;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
this
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
persistStateKey: ow.optional.string,
|
|
101
|
-
createSessionFunction: ow.optional.function,
|
|
102
|
-
sessionOptions: ow.optional.object,
|
|
103
|
-
blockedStatusCodes: ow.optional.array.ofType(ow.number),
|
|
104
|
-
log: ow.optional.object,
|
|
105
|
-
persistenceOptions: ow.optional.object,
|
|
106
|
-
}));
|
|
107
|
-
const { maxPoolSize = MAX_POOL_SIZE, persistStateKeyValueStoreId, persistStateKey = PERSIST_STATE_KEY, createSessionFunction, sessionOptions = {}, blockedStatusCodes = BLOCKED_STATUS_CODES, log = defaultLog, persistenceOptions = {
|
|
108
|
-
enable: true,
|
|
109
|
-
}, } = options;
|
|
110
|
-
this.config = config;
|
|
111
|
-
this.blockedStatusCodes = blockedStatusCodes;
|
|
112
|
-
this.events = config.getEventManager();
|
|
113
|
-
this.log = log.child({ prefix: 'SessionPool' });
|
|
114
|
-
this.persistenceOptions = persistenceOptions;
|
|
80
|
+
export class SessionPool {
|
|
81
|
+
static #nextId = 0;
|
|
82
|
+
id;
|
|
83
|
+
#log;
|
|
84
|
+
#sessions = [];
|
|
85
|
+
#maxPoolSize;
|
|
86
|
+
#createSessionFunction;
|
|
87
|
+
#keyValueStore;
|
|
88
|
+
#sessionMap = new Map();
|
|
89
|
+
#sessionOptions;
|
|
90
|
+
#persistStateKeyValueStoreId;
|
|
91
|
+
#persistStateKey;
|
|
92
|
+
#listener;
|
|
93
|
+
#events;
|
|
94
|
+
#persistenceOptions;
|
|
95
|
+
#sessionReuseStrategy;
|
|
96
|
+
#initPromise;
|
|
97
|
+
#queue = new AsyncQueue();
|
|
98
|
+
#roundRobinIndex = 0;
|
|
99
|
+
constructor(options = {}) {
|
|
100
|
+
const { id, maxPoolSize, persistStateKeyValueStoreId, persistStateKey, createSessionFunction, sessionOptions, log, persistenceOptions, sessionReuseStrategy, } = parseArgument(options, sessionPoolOptionsSchema);
|
|
101
|
+
this.id = id != null ? String(id) : String(SessionPool.#nextId++);
|
|
102
|
+
this.#sessionReuseStrategy = sessionReuseStrategy;
|
|
103
|
+
this.#events = serviceLocator.getEventManager();
|
|
104
|
+
this.#log = log.child({ prefix: 'SessionPool' });
|
|
105
|
+
this.#persistenceOptions = persistenceOptions;
|
|
115
106
|
// Pool Configuration
|
|
116
|
-
this
|
|
117
|
-
this
|
|
118
|
-
// Session configuration
|
|
119
|
-
|
|
107
|
+
this.#maxPoolSize = maxPoolSize;
|
|
108
|
+
this.#createSessionFunction = createSessionFunction || this.defaultCreateSessionFunction;
|
|
109
|
+
// Session configuration. The pool-scoped logger is merged into per-call sessionOptions inside
|
|
110
|
+
// `invokeCreateSessionFunction`, so every Session inherits it without custom createSessionFunctions
|
|
111
|
+
// having to know about it.
|
|
112
|
+
this.#sessionOptions = {
|
|
120
113
|
...sessionOptions,
|
|
121
|
-
|
|
122
|
-
// and can't go inside _defaultCreateSessionFunction
|
|
123
|
-
log: this.log,
|
|
114
|
+
log: this.#log,
|
|
124
115
|
};
|
|
125
116
|
// Session keyValueStore
|
|
126
|
-
this
|
|
127
|
-
this
|
|
117
|
+
this.#persistStateKeyValueStoreId = persistStateKeyValueStoreId;
|
|
118
|
+
this.#persistStateKey = persistStateKey ?? `${PERSIST_STATE_KEY}_${this.id}`;
|
|
128
119
|
}
|
|
129
120
|
/**
|
|
130
121
|
* Gets count of usable sessions in the pool.
|
|
131
122
|
*/
|
|
132
|
-
|
|
133
|
-
|
|
123
|
+
async usableSessionsCount() {
|
|
124
|
+
await this.ensureInitialized();
|
|
125
|
+
return this.#sessions.filter((session) => session.isUsable()).length;
|
|
134
126
|
}
|
|
135
127
|
/**
|
|
136
128
|
* Gets count of retired sessions in the pool.
|
|
137
129
|
*/
|
|
138
|
-
|
|
139
|
-
|
|
130
|
+
async retiredSessionsCount() {
|
|
131
|
+
await this.ensureInitialized();
|
|
132
|
+
return this.#sessions.filter((session) => !session.isUsable()).length;
|
|
140
133
|
}
|
|
141
134
|
/**
|
|
142
135
|
* Starts periodic state persistence and potentially loads SessionPool state from {@link KeyValueStore}.
|
|
143
|
-
*
|
|
136
|
+
* Called automatically on first use of any public method.
|
|
144
137
|
*/
|
|
145
|
-
async
|
|
146
|
-
if (this
|
|
147
|
-
|
|
138
|
+
async ensureInitialized() {
|
|
139
|
+
if (!this.#initPromise) {
|
|
140
|
+
this.#initPromise = this.setupPool();
|
|
148
141
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
142
|
+
return this.#initPromise;
|
|
143
|
+
}
|
|
144
|
+
async setupPool() {
|
|
145
|
+
if (!this.#persistenceOptions.enable) {
|
|
152
146
|
return;
|
|
153
147
|
}
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
this.#keyValueStore = await KeyValueStore.open(this.#persistStateKeyValueStoreId ? { id: this.#persistStateKeyValueStoreId } : null, {
|
|
149
|
+
configuration: serviceLocator.getConfiguration(),
|
|
150
|
+
});
|
|
151
|
+
if (!this.#persistStateKeyValueStoreId) {
|
|
152
|
+
this.#log.debug(`No 'persistStateKeyValueStoreId' options specified, this session pool's data has been saved in the KeyValueStore with the id: ${this.#keyValueStore.id}`);
|
|
156
153
|
}
|
|
157
154
|
// in case of migration happened and SessionPool state should be restored from the keyValueStore.
|
|
158
|
-
await this.
|
|
159
|
-
this
|
|
160
|
-
this
|
|
161
|
-
this.isInitialized = true;
|
|
155
|
+
await this.maybeLoadSessionPool();
|
|
156
|
+
this.#listener = this.persistState.bind(this);
|
|
157
|
+
this.#events.on(EventType.PERSIST_STATE, this.#listener);
|
|
162
158
|
}
|
|
163
159
|
/**
|
|
164
160
|
* Adds a new session to the session pool. The pool automatically creates sessions up to the maximum size of the pool,
|
|
@@ -167,20 +163,20 @@ export class SessionPool extends EventEmitter {
|
|
|
167
163
|
* @param [options] The configuration options for the session being added to the session pool.
|
|
168
164
|
*/
|
|
169
165
|
async addSession(options = {}) {
|
|
170
|
-
this.
|
|
166
|
+
await this.ensureInitialized();
|
|
171
167
|
const { id } = options;
|
|
172
168
|
if (id) {
|
|
173
|
-
const sessionExists = this
|
|
169
|
+
const sessionExists = this.#sessionMap.has(id);
|
|
174
170
|
if (sessionExists) {
|
|
175
171
|
throw new Error(`Cannot add session with id '${id}' as it already exists in the pool`);
|
|
176
172
|
}
|
|
177
173
|
}
|
|
178
|
-
if (!this.
|
|
179
|
-
this.
|
|
174
|
+
if (!this.hasSpaceForSession()) {
|
|
175
|
+
this.removeRetiredSessions();
|
|
180
176
|
}
|
|
181
|
-
const newSession = options instanceof Session ? options : await this.
|
|
182
|
-
this
|
|
183
|
-
this.
|
|
177
|
+
const newSession = options instanceof Session ? options : await this.invokeCreateSessionFunction(options);
|
|
178
|
+
this.#log.debug(`Adding new Session - ${newSession.id}`);
|
|
179
|
+
this.registerSession(newSession);
|
|
184
180
|
}
|
|
185
181
|
/**
|
|
186
182
|
* Adds a new session to the session pool. The pool automatically creates sessions up to the maximum size of the pool,
|
|
@@ -189,9 +185,9 @@ export class SessionPool extends EventEmitter {
|
|
|
189
185
|
* @param [options] The configuration options for the session being added to the session pool.
|
|
190
186
|
*/
|
|
191
187
|
async newSession(sessionOptions) {
|
|
192
|
-
this.
|
|
193
|
-
const newSession = await this.
|
|
194
|
-
this.
|
|
188
|
+
await this.ensureInitialized();
|
|
189
|
+
const newSession = await this.invokeCreateSessionFunction(sessionOptions);
|
|
190
|
+
this.registerSession(newSession);
|
|
195
191
|
return newSession;
|
|
196
192
|
}
|
|
197
193
|
/**
|
|
@@ -202,47 +198,48 @@ export class SessionPool extends EventEmitter {
|
|
|
202
198
|
* @param [sessionId] If provided, it returns the usable session with this id, `undefined` otherwise.
|
|
203
199
|
*/
|
|
204
200
|
async getSession(sessionId) {
|
|
205
|
-
await this.
|
|
201
|
+
await this.ensureInitialized();
|
|
202
|
+
await this.#queue.wait();
|
|
206
203
|
try {
|
|
207
|
-
this._throwIfNotInitialized();
|
|
208
204
|
if (sessionId) {
|
|
209
|
-
const session = this
|
|
210
|
-
if (session
|
|
205
|
+
const session = this.#sessionMap.get(sessionId);
|
|
206
|
+
if (session?.isUsable())
|
|
211
207
|
return session;
|
|
212
208
|
return undefined;
|
|
213
209
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
const pickedSession = this._pickSession();
|
|
218
|
-
if (pickedSession.isUsable()) {
|
|
210
|
+
const pickedSession = this.pickSession();
|
|
211
|
+
if (pickedSession)
|
|
219
212
|
return pickedSession;
|
|
213
|
+
if (this.hasSpaceForSession()) {
|
|
214
|
+
return await this.createSession();
|
|
220
215
|
}
|
|
221
|
-
this.
|
|
222
|
-
return await this.
|
|
216
|
+
this.removeRetiredSessions();
|
|
217
|
+
return await this.createSession();
|
|
223
218
|
}
|
|
224
219
|
finally {
|
|
225
|
-
this
|
|
220
|
+
this.#queue.shift();
|
|
226
221
|
}
|
|
227
222
|
}
|
|
228
223
|
/**
|
|
229
224
|
* @param options - Override the persistence options provided in the constructor
|
|
230
225
|
*/
|
|
231
226
|
async resetStore(options) {
|
|
232
|
-
if (!this
|
|
227
|
+
if (!this.#persistenceOptions.enable && !options?.enable) {
|
|
233
228
|
return;
|
|
234
229
|
}
|
|
235
|
-
await this.
|
|
230
|
+
await this.ensureInitialized();
|
|
231
|
+
await this.#keyValueStore?.setValue(this.#persistStateKey, null);
|
|
236
232
|
}
|
|
237
233
|
/**
|
|
238
234
|
* Returns an object representing the internal state of the `SessionPool` instance.
|
|
239
235
|
* Note that the object's fields can change in future releases.
|
|
240
236
|
*/
|
|
241
|
-
getState() {
|
|
237
|
+
async getState() {
|
|
238
|
+
await this.ensureInitialized();
|
|
242
239
|
return {
|
|
243
|
-
usableSessionsCount: this.usableSessionsCount,
|
|
244
|
-
retiredSessionsCount: this.retiredSessionsCount,
|
|
245
|
-
sessions: this
|
|
240
|
+
usableSessionsCount: await this.usableSessionsCount(),
|
|
241
|
+
retiredSessionsCount: await this.retiredSessionsCount(),
|
|
242
|
+
sessions: this.#sessions.map((session) => session.getState()),
|
|
246
243
|
};
|
|
247
244
|
}
|
|
248
245
|
/**
|
|
@@ -251,47 +248,46 @@ export class SessionPool extends EventEmitter {
|
|
|
251
248
|
* @param options - Override the persistence options provided in the constructor
|
|
252
249
|
*/
|
|
253
250
|
async persistState(options) {
|
|
254
|
-
if (!this
|
|
251
|
+
if (!this.#persistenceOptions.enable && !options?.enable) {
|
|
255
252
|
return;
|
|
256
253
|
}
|
|
257
|
-
this.
|
|
258
|
-
|
|
259
|
-
|
|
254
|
+
await this.ensureInitialized();
|
|
255
|
+
this.#log.debug('Persisting state', {
|
|
256
|
+
persistStateKeyValueStoreId: this.#persistStateKeyValueStoreId,
|
|
257
|
+
persistStateKey: this.#persistStateKey,
|
|
260
258
|
});
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
doNotRetryTimeouts: true,
|
|
268
|
-
})
|
|
269
|
-
.catch((error) => this.log.warning(`Failed to persist the session pool stats to ${this.persistStateKey}`, { error }));
|
|
259
|
+
await this.#keyValueStore
|
|
260
|
+
?.setValue(this.#persistStateKey, await this.getState())
|
|
261
|
+
.catch((error) => this.#log.warning(`Failed to persist the session pool stats to ${this.#persistStateKey}`, { error }));
|
|
262
|
+
}
|
|
263
|
+
async [Symbol.asyncDispose]() {
|
|
264
|
+
await this.teardown({ persistState: true });
|
|
270
265
|
}
|
|
271
266
|
/**
|
|
272
267
|
* Removes listener from `persistState` event.
|
|
273
268
|
* This function should be called after you are done with using the `SessionPool` instance.
|
|
269
|
+
* @param options - Set `persistState` to false when the final state was already persisted by the event manager.
|
|
274
270
|
*/
|
|
275
|
-
async teardown() {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
271
|
+
async teardown({ persistState = true } = {}) {
|
|
272
|
+
if (!this.#initPromise)
|
|
273
|
+
return;
|
|
274
|
+
await this.ensureInitialized();
|
|
275
|
+
if (this.#listener) {
|
|
276
|
+
this.#events.off(EventType.PERSIST_STATE, this.#listener);
|
|
277
|
+
}
|
|
278
|
+
if (persistState) {
|
|
279
|
+
await this.persistState();
|
|
280
|
+
}
|
|
285
281
|
}
|
|
286
282
|
/**
|
|
287
283
|
* Removes retired `Session` instances from `SessionPool`.
|
|
288
284
|
*/
|
|
289
|
-
|
|
290
|
-
this
|
|
285
|
+
removeRetiredSessions() {
|
|
286
|
+
this.#sessions = this.#sessions.filter((storedSession) => {
|
|
291
287
|
if (storedSession.isUsable())
|
|
292
288
|
return true;
|
|
293
|
-
this
|
|
294
|
-
this
|
|
289
|
+
this.#sessionMap.delete(storedSession.id);
|
|
290
|
+
this.#log.debug(`Removed Session - ${storedSession.id}`);
|
|
295
291
|
return false;
|
|
296
292
|
});
|
|
297
293
|
}
|
|
@@ -299,89 +295,102 @@ export class SessionPool extends EventEmitter {
|
|
|
299
295
|
* Adds `Session` instance to `SessionPool`.
|
|
300
296
|
* @param newSession `Session` instance to be added.
|
|
301
297
|
*/
|
|
302
|
-
|
|
303
|
-
this
|
|
304
|
-
this
|
|
298
|
+
registerSession(newSession) {
|
|
299
|
+
this.#sessions.push(newSession);
|
|
300
|
+
this.#sessionMap.set(newSession.id, newSession);
|
|
305
301
|
}
|
|
306
302
|
/**
|
|
307
303
|
* Gets random index.
|
|
308
304
|
*/
|
|
309
|
-
|
|
310
|
-
return Math.floor(Math.random() * this
|
|
305
|
+
getRandomIndex() {
|
|
306
|
+
return Math.floor(Math.random() * this.#sessions.length);
|
|
311
307
|
}
|
|
312
308
|
/**
|
|
313
309
|
* Creates new session without any extra behavior.
|
|
314
|
-
* @param sessionPool
|
|
315
310
|
* @param [options]
|
|
316
311
|
* @param [options.sessionOptions] The configuration options for the session being created.
|
|
317
312
|
* @returns New session.
|
|
318
313
|
*/
|
|
319
|
-
async
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
314
|
+
async defaultCreateSessionFunction(options = {}) {
|
|
315
|
+
const { sessionOptions } = parseArgument(options, createSessionOptionsSchema);
|
|
316
|
+
return new Session(sessionOptions);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Invokes `createSessionFunction` with `sessionOptions` already merged from pool-wide defaults and
|
|
320
|
+
* the supplied per-call overrides, so custom implementations don't need to spread `pool.sessionOptions` themselves.
|
|
321
|
+
*
|
|
322
|
+
* A default {@link SessionFingerprint} is generated up front (host OS as
|
|
323
|
+
* `platform`, a random valid `browser`/`device` for that platform). Pool-wide
|
|
324
|
+
* and per-call options override it, and a persisted fingerprint coming
|
|
325
|
+
* through `maybeLoadSessionPool` naturally wins because it arrives in
|
|
326
|
+
* `perCallOptions`.
|
|
327
|
+
*/
|
|
328
|
+
async invokeCreateSessionFunction(perCallOptions) {
|
|
329
|
+
const sessionOptions = {
|
|
330
|
+
fingerprint: createDefaultSessionFingerprint(),
|
|
331
|
+
...this.#sessionOptions,
|
|
332
|
+
...perCallOptions,
|
|
333
|
+
};
|
|
334
|
+
return this.#createSessionFunction({ sessionOptions });
|
|
327
335
|
}
|
|
328
336
|
/**
|
|
329
337
|
* Creates new session and adds it to the pool.
|
|
330
338
|
* @returns Newly created `Session` instance.
|
|
331
339
|
*/
|
|
332
|
-
async
|
|
333
|
-
const newSession = await this.
|
|
334
|
-
this.
|
|
335
|
-
this
|
|
340
|
+
async createSession() {
|
|
341
|
+
const newSession = await this.invokeCreateSessionFunction();
|
|
342
|
+
this.registerSession(newSession);
|
|
343
|
+
this.#log.debug(`Created new Session - ${newSession.id}`);
|
|
336
344
|
return newSession;
|
|
337
345
|
}
|
|
338
346
|
/**
|
|
339
347
|
* Decides whether there is enough space for creating new session.
|
|
340
348
|
*/
|
|
341
|
-
|
|
342
|
-
return this
|
|
349
|
+
hasSpaceForSession() {
|
|
350
|
+
return this.#sessions.length < this.#maxPoolSize;
|
|
343
351
|
}
|
|
344
352
|
/**
|
|
345
|
-
* Picks
|
|
346
|
-
*
|
|
353
|
+
* Picks a session from the `SessionPool` according to the configured `sessionReuseStrategy`.
|
|
354
|
+
* Returns `undefined` when no session should be reused and a new one should be created instead.
|
|
347
355
|
*/
|
|
348
|
-
|
|
349
|
-
|
|
356
|
+
pickSession() {
|
|
357
|
+
if (this.#sessionReuseStrategy !== 'use-until-failure' && this.hasSpaceForSession())
|
|
358
|
+
return undefined;
|
|
359
|
+
if (this.#sessionReuseStrategy === 'use-until-failure') {
|
|
360
|
+
return this.#sessions.find((session) => session.isUsable());
|
|
361
|
+
}
|
|
362
|
+
let picked;
|
|
363
|
+
if (this.#sessionReuseStrategy === 'round-robin') {
|
|
364
|
+
const index = this.#roundRobinIndex % this.#sessions.length;
|
|
365
|
+
this.#roundRobinIndex = index + 1;
|
|
366
|
+
picked = this.#sessions[index];
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
picked = this.#sessions[this.getRandomIndex()];
|
|
370
|
+
}
|
|
371
|
+
return picked.isUsable() ? picked : undefined;
|
|
350
372
|
}
|
|
351
373
|
/**
|
|
352
374
|
* Potentially loads `SessionPool`.
|
|
353
375
|
* If the state was persisted it loads the `SessionPool` from the persisted state.
|
|
354
376
|
*/
|
|
355
|
-
async
|
|
356
|
-
const loadedSessionPool = await this
|
|
377
|
+
async maybeLoadSessionPool() {
|
|
378
|
+
const loadedSessionPool = await this.#keyValueStore?.getValue(this.#persistStateKey);
|
|
357
379
|
if (!loadedSessionPool)
|
|
358
380
|
return;
|
|
359
381
|
// Invalidate old sessions and load active sessions only
|
|
360
|
-
this
|
|
361
|
-
persistStateKeyValueStoreId: this
|
|
362
|
-
persistStateKey: this
|
|
382
|
+
this.#log.debug('Recreating state from KeyValueStore', {
|
|
383
|
+
persistStateKeyValueStoreId: this.#persistStateKeyValueStoreId,
|
|
384
|
+
persistStateKey: this.#persistStateKey,
|
|
363
385
|
});
|
|
364
386
|
for (const sessionObject of loadedSessionPool.sessions) {
|
|
365
|
-
sessionObject.sessionPool = this;
|
|
366
387
|
sessionObject.createdAt = new Date(sessionObject.createdAt);
|
|
367
388
|
sessionObject.expiresAt = new Date(sessionObject.expiresAt);
|
|
368
|
-
const recreatedSession = await this.
|
|
389
|
+
const recreatedSession = await this.invokeCreateSessionFunction(sessionObject);
|
|
369
390
|
if (recreatedSession.isUsable()) {
|
|
370
|
-
this.
|
|
391
|
+
this.registerSession(recreatedSession);
|
|
371
392
|
}
|
|
372
393
|
}
|
|
373
|
-
this
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Opens a SessionPool and returns a promise resolving to an instance
|
|
377
|
-
* of the {@link SessionPool} class that is already initialized.
|
|
378
|
-
*
|
|
379
|
-
* For more details and code examples, see the {@link SessionPool} class.
|
|
380
|
-
*/
|
|
381
|
-
static async open(options, config) {
|
|
382
|
-
const sessionPool = new SessionPool(options, config);
|
|
383
|
-
await sessionPool.initialize();
|
|
384
|
-
return sessionPool;
|
|
394
|
+
this.#log.debug(`${this.#sessions.length} active sessions loaded from KeyValueStore`);
|
|
385
395
|
}
|
|
386
396
|
}
|
|
387
|
-
//# sourceMappingURL=session_pool.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ProcessedRequest } from '@crawlee/types';
|
|
2
|
+
import type { Source } from '../request.js';
|
|
3
|
+
import type { AddRequestsBatchedResult } from './request_queue.js';
|
|
4
|
+
export interface DrainRequestBatchesOptions<TItem extends Source> {
|
|
5
|
+
/**
|
|
6
|
+
* The requests to add, already normalized by the caller. Consumed lazily: an unbounded or expensive
|
|
7
|
+
* iterable is only pulled from as far as the batching (and any `maxNewRequests` budget) requires.
|
|
8
|
+
*/
|
|
9
|
+
items: AsyncGenerator<TItem>;
|
|
10
|
+
batchSize: number;
|
|
11
|
+
waitBetweenBatchesMillis: number;
|
|
12
|
+
waitForAllRequestsToBeAdded: boolean;
|
|
13
|
+
maxNewRequests?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Adds a single chunk and reports what it processed.
|
|
16
|
+
*
|
|
17
|
+
* @param isInitial Whether this is the first chunk, which is added before this function returns. Later
|
|
18
|
+
* chunks land in the background, which is why some callers cache only the first.
|
|
19
|
+
*/
|
|
20
|
+
processChunk: (chunk: TItem[], isInitial: boolean) => Promise<ProcessedRequest[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Called with the promise covering every chunk after the first, so the caller can keep its own
|
|
23
|
+
* `isFinished` honest while batches are still landing.
|
|
24
|
+
*/
|
|
25
|
+
trackBackgroundBatches?: (batches: Promise<unknown>) => void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Drives the chunk-by-chunk half of `addRequestsBatched`: the first chunk is added before returning and the
|
|
29
|
+
* rest continue in the background, paced by `waitBetweenBatchesMillis`.
|
|
30
|
+
*
|
|
31
|
+
* Callers differ only in how a chunk is added and how the input is normalized, so that is all
|
|
32
|
+
* {@link DrainRequestBatchesOptions} asks for - the budget arithmetic, the lazy chunking, the
|
|
33
|
+
* over-limit reporting and the transaction handling are identical for everyone and live here. In
|
|
34
|
+
* particular, every caller has to keep its background chunks out of a transaction they will outlive,
|
|
35
|
+
* so that is read from the ambient transaction rather than asked of the caller.
|
|
36
|
+
*/
|
|
37
|
+
export declare function drainRequestBatches<TItem extends Source>(options: DrainRequestBatchesOptions<TItem>): Promise<AddRequestsBatchedResult>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
|
+
import { chunkedAsyncIterable, peekableAsyncIterable } from '../iterables.js';
|
|
3
|
+
import { activeStorageTransaction, withDirectStorageAccess } from './transaction.js';
|
|
4
|
+
/**
|
|
5
|
+
* Drives the chunk-by-chunk half of `addRequestsBatched`: the first chunk is added before returning and the
|
|
6
|
+
* rest continue in the background, paced by `waitBetweenBatchesMillis`.
|
|
7
|
+
*
|
|
8
|
+
* Callers differ only in how a chunk is added and how the input is normalized, so that is all
|
|
9
|
+
* {@link DrainRequestBatchesOptions} asks for - the budget arithmetic, the lazy chunking, the
|
|
10
|
+
* over-limit reporting and the transaction handling are identical for everyone and live here. In
|
|
11
|
+
* particular, every caller has to keep its background chunks out of a transaction they will outlive,
|
|
12
|
+
* so that is read from the ambient transaction rather than asked of the caller.
|
|
13
|
+
*/
|
|
14
|
+
export async function drainRequestBatches(options) {
|
|
15
|
+
const { items, batchSize, waitBetweenBatchesMillis, waitForAllRequestsToBeAdded, maxNewRequests, processChunk, trackBackgroundBatches, } = options;
|
|
16
|
+
const deferred = activeStorageTransaction()?.policy.requestQueue === 'deferred';
|
|
17
|
+
let remainingBudget = maxNewRequests ?? Infinity;
|
|
18
|
+
const requestsOverLimit = [];
|
|
19
|
+
// Never hand a chunk more than the budget allows, so an over-large final batch cannot overshoot.
|
|
20
|
+
const effectiveChunkSize = maxNewRequests !== undefined ? () => Math.min(batchSize, remainingBudget) : batchSize;
|
|
21
|
+
const chunks = peekableAsyncIterable(chunkedAsyncIterable(items, effectiveChunkSize));
|
|
22
|
+
const chunksIterator = chunks[Symbol.asyncIterator]();
|
|
23
|
+
const addChunk = async (chunk, isInitial) => {
|
|
24
|
+
const processedRequests = await processChunk(chunk, isInitial);
|
|
25
|
+
if (maxNewRequests !== undefined) {
|
|
26
|
+
remainingBudget -= processedRequests.filter((request) => !request.wasAlreadyPresent).length;
|
|
27
|
+
}
|
|
28
|
+
return processedRequests;
|
|
29
|
+
};
|
|
30
|
+
const buildResult = async (addedRequests, waitForAll) => {
|
|
31
|
+
if (maxNewRequests !== undefined) {
|
|
32
|
+
// `chunkedAsyncIterable` stops pulling once the budget-derived chunk size hits zero, so whatever
|
|
33
|
+
// is left is still sitting in `items` rather than in a chunk we have seen.
|
|
34
|
+
for await (const item of items) {
|
|
35
|
+
requestsOverLimit.push(item);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { addedRequests, waitForAllRequestsToBeAdded: waitForAll, requestsOverLimit };
|
|
39
|
+
};
|
|
40
|
+
const initialChunk = await chunksIterator.peek();
|
|
41
|
+
if (initialChunk === undefined) {
|
|
42
|
+
return buildResult([], Promise.resolve([]));
|
|
43
|
+
}
|
|
44
|
+
const addedRequests = await addChunk(initialChunk, true);
|
|
45
|
+
await chunksIterator.next();
|
|
46
|
+
if ((await chunksIterator.peek()) === undefined) {
|
|
47
|
+
return buildResult(addedRequests, Promise.resolve([]));
|
|
48
|
+
}
|
|
49
|
+
const processRemainingChunks = async () => {
|
|
50
|
+
const added = [];
|
|
51
|
+
for await (const chunk of chunks) {
|
|
52
|
+
added.push(...(await addChunk(chunk, false)));
|
|
53
|
+
// Under `deferred` no chunk performs backend I/O, so pacing them would only stall the handler.
|
|
54
|
+
await sleep(deferred ? 0 : waitBetweenBatchesMillis);
|
|
55
|
+
}
|
|
56
|
+
return added;
|
|
57
|
+
};
|
|
58
|
+
// With a budget we must drain everything before we can report what went over it; under `deferred` a
|
|
59
|
+
// writer that finishes after commit would have nowhere to put its journal entries.
|
|
60
|
+
const awaitsRemainder = waitForAllRequestsToBeAdded || maxNewRequests !== undefined || deferred;
|
|
61
|
+
// An un-awaited writer outlives the transaction scope it inherits, so it must not record into a
|
|
62
|
+
// transaction that may already be closed. It writes directly - its write-through additions were never
|
|
63
|
+
// going to be rolled back anyway - which means the requests it adds are not journaled.
|
|
64
|
+
// See `StorageTransactionView.enqueuedUrls`.
|
|
65
|
+
const remainder = awaitsRemainder ? processRemainingChunks() : withDirectStorageAccess(processRemainingChunks);
|
|
66
|
+
// The caller is not obliged to await `remainder`, so give it a handler of its own - an unhandled
|
|
67
|
+
// rejection here would otherwise take the process down.
|
|
68
|
+
trackBackgroundBatches?.(remainder.catch(() => { }));
|
|
69
|
+
if (awaitsRemainder) {
|
|
70
|
+
addedRequests.push(...(await remainder));
|
|
71
|
+
}
|
|
72
|
+
return buildResult(addedRequests, remainder);
|
|
73
|
+
}
|