1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 | const { SyncHook, AsyncSeriesHook } = require("tapable");
|
9 | const { makeWebpackError } = require("../HookWebpackError");
|
10 | const WebpackError = require("../WebpackError");
|
11 | const ArrayQueue = require("./ArrayQueue");
|
12 |
|
13 | const QUEUED_STATE = 0;
|
14 | const PROCESSING_STATE = 1;
|
15 | const DONE_STATE = 2;
|
16 |
|
17 | let inHandleResult = 0;
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 | class AsyncQueueEntry {
|
32 | |
33 |
|
34 |
|
35 |
|
36 | constructor(item, callback) {
|
37 | this.item = item;
|
38 |
|
39 | this.state = QUEUED_STATE;
|
40 | this.callback = callback;
|
41 |
|
42 | this.callbacks = undefined;
|
43 | this.result = undefined;
|
44 |
|
45 | this.error = undefined;
|
46 | }
|
47 | }
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 | class AsyncQueue {
|
55 | |
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | constructor({ name, parallelism, parent, processor, getKey }) {
|
64 | this._name = name;
|
65 | this._parallelism = parallelism || 1;
|
66 | this._processor = processor;
|
67 | this._getKey =
|
68 | getKey || (item => (item));
|
69 |
|
70 | this._entries = new Map();
|
71 |
|
72 | this._queued = new ArrayQueue();
|
73 |
|
74 | this._children = undefined;
|
75 | this._activeTasks = 0;
|
76 | this._willEnsureProcessing = false;
|
77 | this._needProcessing = false;
|
78 | this._stopped = false;
|
79 | this._root = parent ? parent._root : this;
|
80 | if (parent) {
|
81 | if (this._root._children === undefined) {
|
82 | this._root._children = [this];
|
83 | } else {
|
84 | this._root._children.push(this);
|
85 | }
|
86 | }
|
87 |
|
88 | this.hooks = {
|
89 |
|
90 | beforeAdd: new AsyncSeriesHook(["item"]),
|
91 |
|
92 | added: new SyncHook(["item"]),
|
93 |
|
94 | beforeStart: new AsyncSeriesHook(["item"]),
|
95 |
|
96 | started: new SyncHook(["item"]),
|
97 |
|
98 | result: new SyncHook(["item", "error", "result"])
|
99 | };
|
100 |
|
101 | this._ensureProcessing = this._ensureProcessing.bind(this);
|
102 | }
|
103 |
|
104 | |
105 |
|
106 |
|
107 |
|
108 |
|
109 | add(item, callback) {
|
110 | if (this._stopped) return callback(new WebpackError("Queue was stopped"));
|
111 | this.hooks.beforeAdd.callAsync(item, err => {
|
112 | if (err) {
|
113 | callback(
|
114 | makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeAdd`)
|
115 | );
|
116 | return;
|
117 | }
|
118 | const key = this._getKey(item);
|
119 | const entry = this._entries.get(key);
|
120 | if (entry !== undefined) {
|
121 | if (entry.state === DONE_STATE) {
|
122 | if (inHandleResult++ > 3) {
|
123 | process.nextTick(() => callback(entry.error, entry.result));
|
124 | } else {
|
125 | callback(entry.error, entry.result);
|
126 | }
|
127 | inHandleResult--;
|
128 | } else if (entry.callbacks === undefined) {
|
129 | entry.callbacks = [callback];
|
130 | } else {
|
131 | entry.callbacks.push(callback);
|
132 | }
|
133 | return;
|
134 | }
|
135 | const newEntry = new AsyncQueueEntry(item, callback);
|
136 | if (this._stopped) {
|
137 | this.hooks.added.call(item);
|
138 | this._root._activeTasks++;
|
139 | process.nextTick(() =>
|
140 | this._handleResult(newEntry, new WebpackError("Queue was stopped"))
|
141 | );
|
142 | } else {
|
143 | this._entries.set(key, newEntry);
|
144 | this._queued.enqueue(newEntry);
|
145 | const root = this._root;
|
146 | root._needProcessing = true;
|
147 | if (root._willEnsureProcessing === false) {
|
148 | root._willEnsureProcessing = true;
|
149 | setImmediate(root._ensureProcessing);
|
150 | }
|
151 | this.hooks.added.call(item);
|
152 | }
|
153 | });
|
154 | }
|
155 |
|
156 | |
157 |
|
158 |
|
159 |
|
160 | invalidate(item) {
|
161 | const key = this._getKey(item);
|
162 | const entry = this._entries.get(key);
|
163 | this._entries.delete(key);
|
164 | if (entry.state === QUEUED_STATE) {
|
165 | this._queued.delete(entry);
|
166 | }
|
167 | }
|
168 |
|
169 | |
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | waitFor(item, callback) {
|
176 | const key = this._getKey(item);
|
177 | const entry = this._entries.get(key);
|
178 | if (entry === undefined) {
|
179 | return callback(
|
180 | new WebpackError(
|
181 | "waitFor can only be called for an already started item"
|
182 | )
|
183 | );
|
184 | }
|
185 | if (entry.state === DONE_STATE) {
|
186 | process.nextTick(() => callback(entry.error, entry.result));
|
187 | } else if (entry.callbacks === undefined) {
|
188 | entry.callbacks = [callback];
|
189 | } else {
|
190 | entry.callbacks.push(callback);
|
191 | }
|
192 | }
|
193 |
|
194 | |
195 |
|
196 |
|
197 | stop() {
|
198 | this._stopped = true;
|
199 | const queue = this._queued;
|
200 | this._queued = new ArrayQueue();
|
201 | const root = this._root;
|
202 | for (const entry of queue) {
|
203 | this._entries.delete(this._getKey(entry.item));
|
204 | root._activeTasks++;
|
205 | this._handleResult(entry, new WebpackError("Queue was stopped"));
|
206 | }
|
207 | }
|
208 |
|
209 | |
210 |
|
211 |
|
212 | increaseParallelism() {
|
213 | const root = this._root;
|
214 | root._parallelism++;
|
215 |
|
216 | if (root._willEnsureProcessing === false && root._needProcessing) {
|
217 | root._willEnsureProcessing = true;
|
218 | setImmediate(root._ensureProcessing);
|
219 | }
|
220 | }
|
221 |
|
222 | |
223 |
|
224 |
|
225 | decreaseParallelism() {
|
226 | const root = this._root;
|
227 | root._parallelism--;
|
228 | }
|
229 |
|
230 | |
231 |
|
232 |
|
233 |
|
234 | isProcessing(item) {
|
235 | const key = this._getKey(item);
|
236 | const entry = this._entries.get(key);
|
237 | return entry !== undefined && entry.state === PROCESSING_STATE;
|
238 | }
|
239 |
|
240 | |
241 |
|
242 |
|
243 |
|
244 | isQueued(item) {
|
245 | const key = this._getKey(item);
|
246 | const entry = this._entries.get(key);
|
247 | return entry !== undefined && entry.state === QUEUED_STATE;
|
248 | }
|
249 |
|
250 | |
251 |
|
252 |
|
253 |
|
254 | isDone(item) {
|
255 | const key = this._getKey(item);
|
256 | const entry = this._entries.get(key);
|
257 | return entry !== undefined && entry.state === DONE_STATE;
|
258 | }
|
259 |
|
260 | |
261 |
|
262 |
|
263 | _ensureProcessing() {
|
264 | while (this._activeTasks < this._parallelism) {
|
265 | const entry = this._queued.dequeue();
|
266 | if (entry === undefined) break;
|
267 | this._activeTasks++;
|
268 | entry.state = PROCESSING_STATE;
|
269 | this._startProcessing(entry);
|
270 | }
|
271 | this._willEnsureProcessing = false;
|
272 | if (this._queued.length > 0) return;
|
273 | if (this._children !== undefined) {
|
274 | for (const child of this._children) {
|
275 | while (this._activeTasks < this._parallelism) {
|
276 | const entry = child._queued.dequeue();
|
277 | if (entry === undefined) break;
|
278 | this._activeTasks++;
|
279 | entry.state = PROCESSING_STATE;
|
280 | child._startProcessing(entry);
|
281 | }
|
282 | if (child._queued.length > 0) return;
|
283 | }
|
284 | }
|
285 | if (!this._willEnsureProcessing) this._needProcessing = false;
|
286 | }
|
287 |
|
288 | |
289 |
|
290 |
|
291 |
|
292 | _startProcessing(entry) {
|
293 | this.hooks.beforeStart.callAsync(entry.item, err => {
|
294 | if (err) {
|
295 | this._handleResult(
|
296 | entry,
|
297 | makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeStart`)
|
298 | );
|
299 | return;
|
300 | }
|
301 | let inCallback = false;
|
302 | try {
|
303 | this._processor(entry.item, (e, r) => {
|
304 | inCallback = true;
|
305 | this._handleResult(entry, e, r);
|
306 | });
|
307 | } catch (err) {
|
308 | if (inCallback) throw err;
|
309 | this._handleResult(entry, err, null);
|
310 | }
|
311 | this.hooks.started.call(entry.item);
|
312 | });
|
313 | }
|
314 |
|
315 | |
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 | _handleResult(entry, err, result) {
|
322 | this.hooks.result.callAsync(entry.item, err, result, hookError => {
|
323 | const error = hookError
|
324 | ? makeWebpackError(hookError, `AsyncQueue(${this._name}).hooks.result`)
|
325 | : err;
|
326 |
|
327 | const callback = entry.callback;
|
328 | const callbacks = entry.callbacks;
|
329 | entry.state = DONE_STATE;
|
330 | entry.callback = undefined;
|
331 | entry.callbacks = undefined;
|
332 | entry.result = result;
|
333 | entry.error = error;
|
334 |
|
335 | const root = this._root;
|
336 | root._activeTasks--;
|
337 | if (root._willEnsureProcessing === false && root._needProcessing) {
|
338 | root._willEnsureProcessing = true;
|
339 | setImmediate(root._ensureProcessing);
|
340 | }
|
341 |
|
342 | if (inHandleResult++ > 3) {
|
343 | process.nextTick(() => {
|
344 | callback(error, result);
|
345 | if (callbacks !== undefined) {
|
346 | for (const callback of callbacks) {
|
347 | callback(error, result);
|
348 | }
|
349 | }
|
350 | });
|
351 | } else {
|
352 | callback(error, result);
|
353 | if (callbacks !== undefined) {
|
354 | for (const callback of callbacks) {
|
355 | callback(error, result);
|
356 | }
|
357 | }
|
358 | }
|
359 | inHandleResult--;
|
360 | });
|
361 | }
|
362 |
|
363 | clear() {
|
364 | this._entries.clear();
|
365 | this._queued.clear();
|
366 | this._activeTasks = 0;
|
367 | this._willEnsureProcessing = false;
|
368 | this._needProcessing = false;
|
369 | this._stopped = false;
|
370 | }
|
371 | }
|
372 |
|
373 | module.exports = AsyncQueue;
|