@crawlee/core 4.0.0-beta.160 → 4.0.0-beta.161

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.
@@ -53,7 +53,7 @@ export declare class RequestQueueBackend extends BaseClient implements storage.R
53
53
  * Computing the flag is expensive: because an in-progress request may sit anywhere in the queue, it
54
54
  * forces a scan of every pending entry even when only `limit` items are wanted. Callers that only
55
55
  * need the head (e.g. {@link fetchNextRequest}, {@link isEmpty}) leave it off so the scan can stop as
56
- * soon as the page is filled, keeping those calls O(head) instead of O(N).
56
+ * soon as the page is filled, keeping those calls O(head) instead of O(pending).
57
57
  */
58
58
  private listPendingHead;
59
59
  fetchNextRequest(): Promise<storage.UpdateRequestSchema | undefined>;
@@ -19,11 +19,11 @@ export class RequestQueueBackend extends BaseClient {
19
19
  pendingRequestCount = 0;
20
20
  /**
21
21
  * Serializes every operation that reads-then-writes this backend's shared queue state — the
22
- * `requests` map, the `forefrontRequestIds` array, the `inProgressRequestIds` set and the request
23
- * counts. Those mutations span `await` points, so without this mutex a concurrent operation could
24
- * interleave and corrupt them (e.g. a head scan pruning `forefrontRequestIds` while
25
- * `addBatchOfRequests` pushes to it). Held by every mutating method as well as by `isEmpty`/
26
- * `isFinished`, whose head scan also prunes `forefrontRequestIds`.
22
+ * `requests` map, the `pendingRequestIds` set, the `forefrontRequestIds` array, the
23
+ * `inProgressRequestIds` set and the request counts. Those mutations span `await` points, so
24
+ * without this mutex a concurrent operation could interleave and corrupt them (e.g. a head scan
25
+ * pruning `forefrontRequestIds` while `addBatchOfRequests` pushes to it). Held by every mutating
26
+ * method as well as by `isEmpty`/`isFinished`, whose head scan also prunes `forefrontRequestIds`.
27
27
  */
28
28
  #queueStateMutex = new AsyncQueue();
29
29
  #forefrontRequestIds = [];
@@ -37,6 +37,12 @@ export class RequestQueueBackend extends BaseClient {
37
37
  */
38
38
  #inProgressRequestIds = new Set();
39
39
  #requests = new Map();
40
+ /**
41
+ * IDs of requests that are not yet handled (pending or in progress), in insertion order. Handled
42
+ * requests stay in `requests` for deduplication but are removed from here, so head scans only
43
+ * ever walk the unhandled tail instead of every request the queue has ever seen.
44
+ */
45
+ #pendingRequestIds = new Set();
40
46
  // kept as TS-private: storage-backend tests read this field at runtime
41
47
  storageBackend;
42
48
  constructor(options) {
@@ -61,6 +67,7 @@ export class RequestQueueBackend extends BaseClient {
61
67
  // leave dangling ids in `forefrontRequestIds`/`inProgressRequestIds`, which a later head
62
68
  // scan would resolve to a missing request and dereference.
63
69
  this.#requests.clear();
70
+ this.#pendingRequestIds.clear();
64
71
  this.#forefrontRequestIds = [];
65
72
  this.#inProgressRequestIds.clear();
66
73
  }
@@ -76,6 +83,7 @@ export class RequestQueueBackend extends BaseClient {
76
83
  try {
77
84
  // Clear all in-memory state
78
85
  this.#requests.clear();
86
+ this.#pendingRequestIds.clear();
79
87
  this.#forefrontRequestIds = [];
80
88
  this.#inProgressRequestIds.clear();
81
89
  this.handledRequestCount = 0;
@@ -90,9 +98,7 @@ export class RequestQueueBackend extends BaseClient {
90
98
  for (let i = this.#forefrontRequestIds.length - 1; i >= 0; i--) {
91
99
  yield this.#forefrontRequestIds[i];
92
100
  }
93
- for (const key of this.#requests.keys()) {
94
- yield key;
95
- }
101
+ yield* this.#pendingRequestIds;
96
102
  }
97
103
  /**
98
104
  * Scans the queue and returns the pending head — requests that are neither handled nor currently
@@ -107,7 +113,7 @@ export class RequestQueueBackend extends BaseClient {
107
113
  * Computing the flag is expensive: because an in-progress request may sit anywhere in the queue, it
108
114
  * forces a scan of every pending entry even when only `limit` items are wanted. Callers that only
109
115
  * need the head (e.g. {@link fetchNextRequest}, {@link isEmpty}) leave it off so the scan can stop as
110
- * soon as the page is filled, keeping those calls O(head) instead of O(N).
116
+ * soon as the page is filled, keeping those calls O(head) instead of O(pending).
111
117
  */
112
118
  async listPendingHead(limit, detectInProgressRequests = false) {
113
119
  const items = [];
@@ -127,11 +133,10 @@ export class RequestQueueBackend extends BaseClient {
127
133
  }
128
134
  seenRequestIds.add(requestId);
129
135
  const request = this.#requests.get(requestId);
130
- // Permanently-handled requests (`orderNo === null`) are in a terminal state and can be skipped.
136
+ // Only `forefrontRequestIds` can still reference a handled request (`orderNo === null`);
137
+ // `pendingRequestIds` drops them on handling. Remember the id so the list gets pruned below.
131
138
  if (request.orderNo === null) {
132
- if (this.#forefrontRequestIds.includes(requestId)) {
133
- handledForefrontIds.add(requestId);
134
- }
139
+ handledForefrontIds.add(requestId);
135
140
  continue;
136
141
  }
137
142
  // In progress (fetched but not yet handled or reclaimed) — skip it, but remember that the
@@ -193,6 +198,7 @@ export class RequestQueueBackend extends BaseClient {
193
198
  }
194
199
  this.#requests.set(requestModel.id, requestModel);
195
200
  if (requestModel.orderNo) {
201
+ this.#pendingRequestIds.add(requestModel.id);
196
202
  this.pendingRequestCount += 1;
197
203
  }
198
204
  else {
@@ -245,6 +251,7 @@ export class RequestQueueBackend extends BaseClient {
245
251
  const handledAt = request.handledAt ?? new Date().toISOString();
246
252
  const requestModel = this.createInternalRequest({ ...request, handledAt }, false);
247
253
  this.#requests.set(id, requestModel);
254
+ this.#pendingRequestIds.delete(id);
248
255
  // The request is no longer in progress for this client.
249
256
  this.#inProgressRequestIds.delete(id);
250
257
  if (!wasAlreadyHandled) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/core",
3
- "version": "4.0.0-beta.160",
3
+ "version": "4.0.0-beta.161",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -52,10 +52,10 @@
52
52
  "@apify/log": "^2.5.18",
53
53
  "@apify/timeout": "^0.4.4",
54
54
  "@apify/utilities": "^2.15.5",
55
- "@crawlee/fs-storage": "4.0.0-beta.160",
56
- "@crawlee/http-client": "4.0.0-beta.160",
57
- "@crawlee/types": "4.0.0-beta.160",
58
- "@crawlee/utils": "4.0.0-beta.160",
55
+ "@crawlee/fs-storage": "4.0.0-beta.161",
56
+ "@crawlee/http-client": "4.0.0-beta.161",
57
+ "@crawlee/types": "4.0.0-beta.161",
58
+ "@crawlee/utils": "4.0.0-beta.161",
59
59
  "@sapphire/async-queue": "^1.5.5",
60
60
  "@standard-schema/spec": "^1.0.0",
61
61
  "@vladfrangu/async_event_emitter": "^2.4.6",
@@ -78,5 +78,5 @@
78
78
  }
79
79
  }
80
80
  },
81
- "gitHead": "09fbce5ade9b84a2b191c635463e172388e52678"
81
+ "gitHead": "dc353e294d5f4d2ee468325ef11dd7af44443be2"
82
82
  }