@pipedog/asyncio 0.1.0 → 0.2.0
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/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/loop.d.ts +26 -0
- package/dist/loop.js +179 -0
- package/dist/primitives.d.ts +24 -13
- package/dist/primitives.js +439 -185
- package/dist/task.d.ts +48 -19
- package/dist/task.js +245 -152
- package/package.json +1 -1
package/dist/primitives.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// Copyright (c) 2026, pipedog contributors
|
|
2
2
|
// SPDX-License-Identifier: BSD-2-Clause
|
|
3
3
|
//
|
|
4
|
-
// asyncio synchronization primitives and helpers
|
|
5
|
-
//
|
|
6
|
-
|
|
4
|
+
// asyncio synchronization primitives and helpers, ported from CPython's
|
|
5
|
+
// asyncio (locks.py, queues.py, tasks.py, timeouts.py) on top of Future, so
|
|
6
|
+
// they wake waiters exactly as asyncio does: through the ready queue, never
|
|
7
|
+
// synchronously. Every wait here is a cancellation point (see task.ts).
|
|
8
|
+
// Durations are in seconds, as in Python.
|
|
9
|
+
import { callLater, callSoon } from "./loop.js";
|
|
10
|
+
import { abortable, awaitFuture, CancelledError, createTask, currentTask, Future, TimeoutError, } from "./task.js";
|
|
7
11
|
/** Monotonic time in seconds (loop.time()). */
|
|
8
12
|
export function monotonic() {
|
|
9
13
|
return performance.now() / 1000;
|
|
@@ -24,47 +28,75 @@ export function time() {
|
|
|
24
28
|
const origin = performance.timeOrigin;
|
|
25
29
|
return origin ? (origin + performance.now()) / 1000 : Date.now() / 1000;
|
|
26
30
|
}
|
|
27
|
-
|
|
31
|
+
function remove(items, item) {
|
|
32
|
+
const i = items.indexOf(item);
|
|
33
|
+
if (i < 0)
|
|
34
|
+
return false;
|
|
35
|
+
items.splice(i, 1);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* asyncio.sleep(seconds). `sleep(0)` yields once: the task goes to the back
|
|
40
|
+
* of the ready queue, so everything already scheduled runs first.
|
|
41
|
+
*/
|
|
28
42
|
export function sleep(seconds) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
if (!(seconds > 0))
|
|
44
|
+
return sleep0();
|
|
45
|
+
const future = new Future();
|
|
46
|
+
const handle = callLater(seconds, () => {
|
|
47
|
+
if (!future.done())
|
|
48
|
+
future.setResult(undefined);
|
|
49
|
+
});
|
|
50
|
+
const done = awaitFuture(future);
|
|
51
|
+
done.then(() => handle.cancel(), () => handle.cancel());
|
|
52
|
+
return done;
|
|
53
|
+
}
|
|
54
|
+
/** A bare `yield` in a task: reschedule it at the back of the ready queue. */
|
|
55
|
+
function sleep0() {
|
|
56
|
+
const task = currentTask();
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
callSoon(() => {
|
|
59
|
+
const cancelled = task?._takeCancelRequest();
|
|
60
|
+
if (cancelled)
|
|
61
|
+
reject(cancelled);
|
|
62
|
+
else
|
|
63
|
+
resolve();
|
|
64
|
+
});
|
|
32
65
|
});
|
|
33
66
|
}
|
|
34
67
|
/** asyncio.Event */
|
|
35
68
|
export class Event {
|
|
36
|
-
#
|
|
37
|
-
#waiters =
|
|
69
|
+
#value = false;
|
|
70
|
+
#waiters = [];
|
|
38
71
|
isSet() {
|
|
39
|
-
return this.#
|
|
72
|
+
return this.#value;
|
|
40
73
|
}
|
|
41
74
|
set() {
|
|
42
|
-
if (this.#
|
|
75
|
+
if (this.#value)
|
|
43
76
|
return;
|
|
44
|
-
this.#
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
77
|
+
this.#value = true;
|
|
78
|
+
for (const future of this.#waiters) {
|
|
79
|
+
if (!future.done())
|
|
80
|
+
future.setResult(true);
|
|
81
|
+
}
|
|
49
82
|
}
|
|
50
83
|
clear() {
|
|
51
|
-
this.#
|
|
84
|
+
this.#value = false;
|
|
52
85
|
}
|
|
53
|
-
wait() {
|
|
54
|
-
if (this.#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
86
|
+
async wait() {
|
|
87
|
+
if (this.#value) {
|
|
88
|
+
currentTask()?.checkpoint();
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
const future = new Future();
|
|
92
|
+
this.#waiters.push(future);
|
|
93
|
+
try {
|
|
94
|
+
await future;
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
remove(this.#waiters, future);
|
|
62
99
|
}
|
|
63
|
-
return suspend((resolve) => {
|
|
64
|
-
const waiter = () => resolve(true);
|
|
65
|
-
this.#waiters.add(waiter);
|
|
66
|
-
return () => this.#waiters.delete(waiter);
|
|
67
|
-
});
|
|
68
100
|
}
|
|
69
101
|
}
|
|
70
102
|
export class QueueEmpty extends Error {
|
|
@@ -77,7 +109,7 @@ export class QueueShutDown extends Error {
|
|
|
77
109
|
name = "QueueShutDown";
|
|
78
110
|
}
|
|
79
111
|
/**
|
|
80
|
-
* asyncio.Queue. Subclasses override `_init/_put/_get/_size
|
|
112
|
+
* asyncio.Queue. Subclasses override `_init/_put/_get/_size` to change
|
|
81
113
|
* ordering, as Python's PriorityQueue and LifoQueue do.
|
|
82
114
|
*/
|
|
83
115
|
export class Queue {
|
|
@@ -92,19 +124,47 @@ export class Queue {
|
|
|
92
124
|
this._init();
|
|
93
125
|
this.#finished.set();
|
|
94
126
|
}
|
|
95
|
-
// Storage hooks.
|
|
127
|
+
// Storage hooks. FIFO storage is `_items` from index `_head` on: a deque
|
|
128
|
+
// (collections.deque in Python), since Array.shift() is O(n) on V8.
|
|
96
129
|
_items = [];
|
|
130
|
+
_head = 0;
|
|
97
131
|
_init() {
|
|
98
132
|
this._items = [];
|
|
133
|
+
this._head = 0;
|
|
99
134
|
}
|
|
100
135
|
_put(item) {
|
|
101
136
|
this._items.push(item);
|
|
102
137
|
}
|
|
103
138
|
_get() {
|
|
104
|
-
|
|
139
|
+
const items = this._items;
|
|
140
|
+
const item = items[this._head];
|
|
141
|
+
items[this._head] = undefined;
|
|
142
|
+
this._head++;
|
|
143
|
+
if (this._head === items.length) {
|
|
144
|
+
items.length = 0;
|
|
145
|
+
this._head = 0;
|
|
146
|
+
}
|
|
147
|
+
else if (this._head >= 1024 && this._head * 2 >= items.length) {
|
|
148
|
+
items.splice(0, this._head);
|
|
149
|
+
this._head = 0;
|
|
150
|
+
}
|
|
151
|
+
return item;
|
|
105
152
|
}
|
|
106
153
|
_size() {
|
|
107
|
-
return this._items.length;
|
|
154
|
+
return this._items.length - this._head;
|
|
155
|
+
}
|
|
156
|
+
/** The queued items in storage order (for subclasses that inspect them). */
|
|
157
|
+
_queued() {
|
|
158
|
+
return this._head === 0 ? this._items : this._items.slice(this._head);
|
|
159
|
+
}
|
|
160
|
+
#wakeupNext(waiters) {
|
|
161
|
+
while (waiters.length > 0) {
|
|
162
|
+
const waiter = waiters.shift();
|
|
163
|
+
if (!waiter.done()) {
|
|
164
|
+
waiter.setResult(undefined);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
108
168
|
}
|
|
109
169
|
qsize() {
|
|
110
170
|
return this._size();
|
|
@@ -122,14 +182,19 @@ export class Queue {
|
|
|
122
182
|
while (this.full()) {
|
|
123
183
|
if (this.#shutdown)
|
|
124
184
|
throw new QueueShutDown();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
185
|
+
const putter = new Future();
|
|
186
|
+
this.#putters.push(putter);
|
|
187
|
+
try {
|
|
188
|
+
await putter;
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
putter.cancel();
|
|
192
|
+
remove(this.#putters, putter);
|
|
193
|
+
// Woken up by getNowait() but can't take the slot: pass it on.
|
|
194
|
+
if (!this.full() && !putter.cancelled())
|
|
195
|
+
this.#wakeupNext(this.#putters);
|
|
196
|
+
throw e;
|
|
197
|
+
}
|
|
133
198
|
}
|
|
134
199
|
this.putNowait(item);
|
|
135
200
|
}
|
|
@@ -141,26 +206,29 @@ export class Queue {
|
|
|
141
206
|
this._put(item);
|
|
142
207
|
this.#unfinished++;
|
|
143
208
|
this.#finished.clear();
|
|
144
|
-
this.#
|
|
209
|
+
this.#wakeupNext(this.#getters);
|
|
145
210
|
}
|
|
146
211
|
async get() {
|
|
147
|
-
if (!this.empty()
|
|
212
|
+
if (!this.empty())
|
|
148
213
|
currentTask()?.checkpoint();
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return suspend((resolve, reject) => {
|
|
154
|
-
const getter = { resolve, reject };
|
|
214
|
+
while (this.empty()) {
|
|
215
|
+
if (this.#shutdown && this.empty())
|
|
216
|
+
throw new QueueShutDown();
|
|
217
|
+
const getter = new Future();
|
|
155
218
|
this.#getters.push(getter);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
219
|
+
try {
|
|
220
|
+
await getter;
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
getter.cancel();
|
|
224
|
+
remove(this.#getters, getter);
|
|
225
|
+
// Woken up by putNowait() but can't take the item: pass it on.
|
|
226
|
+
if (!this.empty() && !getter.cancelled())
|
|
227
|
+
this.#wakeupNext(this.#getters);
|
|
228
|
+
throw e;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return this.getNowait();
|
|
164
232
|
}
|
|
165
233
|
getNowait() {
|
|
166
234
|
if (this.empty()) {
|
|
@@ -169,7 +237,7 @@ export class Queue {
|
|
|
169
237
|
throw new QueueEmpty();
|
|
170
238
|
}
|
|
171
239
|
const item = this._get();
|
|
172
|
-
this.#
|
|
240
|
+
this.#wakeupNext(this.#putters);
|
|
173
241
|
return item;
|
|
174
242
|
}
|
|
175
243
|
taskDone() {
|
|
@@ -194,22 +262,15 @@ export class Queue {
|
|
|
194
262
|
if (this.#unfinished === 0)
|
|
195
263
|
this.#finished.set();
|
|
196
264
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const item = this._get();
|
|
206
|
-
getter.resolve(item);
|
|
207
|
-
this.#wakePutter();
|
|
265
|
+
// All getters need to re-check queue-empty to raise QueueShutDown.
|
|
266
|
+
for (const getter of this.#getters.splice(0)) {
|
|
267
|
+
if (!getter.done())
|
|
268
|
+
getter.setResult(undefined);
|
|
269
|
+
}
|
|
270
|
+
for (const putter of this.#putters.splice(0)) {
|
|
271
|
+
if (!putter.done())
|
|
272
|
+
putter.setResult(undefined);
|
|
208
273
|
}
|
|
209
|
-
}
|
|
210
|
-
#wakePutter() {
|
|
211
|
-
const putter = this.#putters.shift();
|
|
212
|
-
putter?.();
|
|
213
274
|
}
|
|
214
275
|
}
|
|
215
276
|
/** asyncio.LifoQueue */
|
|
@@ -289,23 +350,32 @@ export class PriorityQueue extends Queue {
|
|
|
289
350
|
/** asyncio.Lock */
|
|
290
351
|
export class Lock {
|
|
291
352
|
#locked = false;
|
|
292
|
-
#waiters =
|
|
353
|
+
#waiters = null;
|
|
293
354
|
locked() {
|
|
294
355
|
return this.#locked;
|
|
295
356
|
}
|
|
296
357
|
async acquire() {
|
|
297
|
-
if (!this.#locked && this.#waiters
|
|
358
|
+
if (!this.#locked && (this.#waiters === null || this.#waiters.every((w) => w.cancelled()))) {
|
|
298
359
|
this.#locked = true;
|
|
299
360
|
return true;
|
|
300
361
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
362
|
+
this.#waiters ??= [];
|
|
363
|
+
const future = new Future();
|
|
364
|
+
this.#waiters.push(future);
|
|
365
|
+
try {
|
|
366
|
+
try {
|
|
367
|
+
await future;
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
remove(this.#waiters, future);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
// If the lock is free, make sure the next waiter in line runs.
|
|
375
|
+
if (e instanceof CancelledError && !this.#locked)
|
|
376
|
+
this.#wakeUpFirst();
|
|
377
|
+
throw e;
|
|
378
|
+
}
|
|
309
379
|
this.#locked = true;
|
|
310
380
|
return true;
|
|
311
381
|
}
|
|
@@ -313,13 +383,13 @@ export class Lock {
|
|
|
313
383
|
if (!this.#locked)
|
|
314
384
|
throw new Error("Lock is not acquired.");
|
|
315
385
|
this.#locked = false;
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
386
|
+
this.#wakeUpFirst();
|
|
387
|
+
}
|
|
388
|
+
#wakeUpFirst() {
|
|
389
|
+
const first = this.#waiters?.[0];
|
|
390
|
+
// done() means that the waiter is already set to wake up.
|
|
391
|
+
if (first && !first.done())
|
|
392
|
+
first.setResult(true);
|
|
323
393
|
}
|
|
324
394
|
/** `async with lock:` → `await lock.run(async () => {...})` */
|
|
325
395
|
async run(fn) {
|
|
@@ -335,34 +405,64 @@ export class Lock {
|
|
|
335
405
|
/** asyncio.Semaphore */
|
|
336
406
|
export class Semaphore {
|
|
337
407
|
#value;
|
|
338
|
-
#waiters =
|
|
408
|
+
#waiters = null;
|
|
339
409
|
constructor(value = 1) {
|
|
410
|
+
if (value < 0)
|
|
411
|
+
throw new RangeError("Semaphore initial value must be >= 0");
|
|
340
412
|
this.#value = value;
|
|
341
413
|
}
|
|
414
|
+
/** Due to state, or FIFO rules (must allow others to run first). */
|
|
342
415
|
locked() {
|
|
343
|
-
return this.#value === 0;
|
|
416
|
+
return this.#value === 0 || (this.#waiters ?? []).some((w) => !w.cancelled());
|
|
344
417
|
}
|
|
345
418
|
async acquire() {
|
|
346
|
-
if (this
|
|
419
|
+
if (!this.locked()) {
|
|
420
|
+
// Maintain FIFO, wait for others to start even if value > 0.
|
|
347
421
|
this.#value--;
|
|
348
422
|
return true;
|
|
349
423
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
424
|
+
this.#waiters ??= [];
|
|
425
|
+
const future = new Future();
|
|
426
|
+
this.#waiters.push(future);
|
|
427
|
+
try {
|
|
428
|
+
try {
|
|
429
|
+
await future;
|
|
430
|
+
}
|
|
431
|
+
finally {
|
|
432
|
+
remove(this.#waiters, future);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch (e) {
|
|
436
|
+
if (e instanceof CancelledError && future.done() && !future.cancelled()) {
|
|
437
|
+
// Woken up by #wakeUpNext() but not acquiring: undo its bookkeeping.
|
|
438
|
+
this.#value++;
|
|
439
|
+
}
|
|
440
|
+
throw e;
|
|
441
|
+
}
|
|
442
|
+
finally {
|
|
443
|
+
// New waiters may have arrived but had to wait due to FIFO.
|
|
444
|
+
while (this.#value > 0) {
|
|
445
|
+
if (!this.#wakeUpNext())
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
358
449
|
return true;
|
|
359
450
|
}
|
|
360
451
|
release() {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
452
|
+
this.#value++;
|
|
453
|
+
this.#wakeUpNext();
|
|
454
|
+
}
|
|
455
|
+
#wakeUpNext() {
|
|
456
|
+
if (!this.#waiters)
|
|
457
|
+
return false;
|
|
458
|
+
for (const future of this.#waiters) {
|
|
459
|
+
if (!future.done()) {
|
|
460
|
+
this.#value--;
|
|
461
|
+
future.setResult(true);
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return false;
|
|
366
466
|
}
|
|
367
467
|
async run(fn) {
|
|
368
468
|
await this.acquire();
|
|
@@ -374,63 +474,197 @@ export class Semaphore {
|
|
|
374
474
|
}
|
|
375
475
|
}
|
|
376
476
|
}
|
|
377
|
-
|
|
378
|
-
|
|
477
|
+
/** asyncio.ensure_future: functions and foreign promises run as tasks. */
|
|
478
|
+
function ensureFuture(aw, name) {
|
|
479
|
+
if (aw instanceof Future)
|
|
379
480
|
return aw;
|
|
380
481
|
if (typeof aw === "function")
|
|
381
482
|
return createTask(aw, name);
|
|
382
483
|
return createTask(() => aw, name);
|
|
383
484
|
}
|
|
485
|
+
/** Await `aw` in the current task, as `await aw` would in Python. */
|
|
486
|
+
function awaitInline(aw) {
|
|
487
|
+
if (aw instanceof Future)
|
|
488
|
+
return awaitFuture(aw);
|
|
489
|
+
if (typeof aw === "function")
|
|
490
|
+
return aw();
|
|
491
|
+
// A foreign promise: make the wait cancellable.
|
|
492
|
+
return abortable(aw);
|
|
493
|
+
}
|
|
494
|
+
/** tasks._cancel_and_wait: cancel `future` and wait until it is done. */
|
|
495
|
+
async function cancelAndWait(future) {
|
|
496
|
+
const waiter = new Future();
|
|
497
|
+
const release = () => {
|
|
498
|
+
if (!waiter.done())
|
|
499
|
+
waiter.setResult(undefined);
|
|
500
|
+
};
|
|
501
|
+
future.addDoneCallback(release);
|
|
502
|
+
try {
|
|
503
|
+
future.cancel();
|
|
504
|
+
// Not waiting on `future` directly keeps this reliably cancellable.
|
|
505
|
+
await waiter;
|
|
506
|
+
}
|
|
507
|
+
finally {
|
|
508
|
+
future.removeDoneCallback(release);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
384
511
|
/**
|
|
385
|
-
* asyncio.
|
|
386
|
-
*
|
|
512
|
+
* asyncio.timeout(seconds) around `fn`, run in the current task: on expiry
|
|
513
|
+
* the task is cancelled and the CancelledError becomes TimeoutError
|
|
514
|
+
* (timeouts.Timeout). `seconds` null never expires.
|
|
387
515
|
*/
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
if (
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
516
|
+
async function withTimeout(seconds, fn) {
|
|
517
|
+
const task = currentTask();
|
|
518
|
+
if (!task) {
|
|
519
|
+
// asyncio.timeout() needs a task; give it one.
|
|
520
|
+
return await createTask(() => withTimeout(seconds, fn), "timeout");
|
|
521
|
+
}
|
|
522
|
+
let expiring = false;
|
|
523
|
+
const cancelling = task.cancelling();
|
|
524
|
+
let handle = null;
|
|
525
|
+
if (seconds !== null && seconds !== undefined) {
|
|
526
|
+
const onTimeout = () => {
|
|
527
|
+
task.cancel();
|
|
528
|
+
expiring = true;
|
|
529
|
+
handle = null;
|
|
530
|
+
};
|
|
531
|
+
handle = seconds <= 0 ? callSoon(onTimeout) : callLater(seconds, onTimeout);
|
|
532
|
+
}
|
|
533
|
+
let result;
|
|
397
534
|
try {
|
|
398
|
-
|
|
535
|
+
result = await fn();
|
|
399
536
|
}
|
|
400
537
|
catch (e) {
|
|
401
|
-
|
|
538
|
+
handle?.cancel();
|
|
539
|
+
if (expiring && task.uncancel() <= cancelling && e instanceof CancelledError) {
|
|
402
540
|
throw new TimeoutError();
|
|
403
541
|
}
|
|
404
542
|
throw e;
|
|
405
543
|
}
|
|
406
|
-
|
|
407
|
-
|
|
544
|
+
handle?.cancel();
|
|
545
|
+
if (expiring)
|
|
546
|
+
task.uncancel();
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* asyncio.wait_for(aw, timeout). A function (a "coroutine") or a foreign
|
|
551
|
+
* promise is awaited in the current task, a Task is awaited (and cancelled on
|
|
552
|
+
* timeout). `timeout` null waits forever.
|
|
553
|
+
*/
|
|
554
|
+
export async function waitFor(aw, timeout) {
|
|
555
|
+
if (timeout !== null && timeout !== undefined && timeout <= 0) {
|
|
556
|
+
const future = ensureFuture(aw, "wait_for");
|
|
557
|
+
if (future.done())
|
|
558
|
+
return future.result();
|
|
559
|
+
await cancelAndWait(future);
|
|
560
|
+
try {
|
|
561
|
+
return future.result();
|
|
562
|
+
}
|
|
563
|
+
catch (e) {
|
|
564
|
+
if (e instanceof CancelledError)
|
|
565
|
+
throw new TimeoutError();
|
|
566
|
+
throw e;
|
|
567
|
+
}
|
|
408
568
|
}
|
|
569
|
+
return await withTimeout(timeout, () => awaitInline(aw));
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* asyncio.timeout(seconds) as a scope: runs `fn` and cancels it on timeout,
|
|
573
|
+
* raising TimeoutError.
|
|
574
|
+
*/
|
|
575
|
+
export function timeout(seconds, fn) {
|
|
576
|
+
return withTimeout(seconds, fn);
|
|
577
|
+
}
|
|
578
|
+
/** tasks._GatheringFuture: cancelling it cancels the children. */
|
|
579
|
+
class GatheringFuture extends Future {
|
|
580
|
+
#children;
|
|
581
|
+
cancelRequested = false;
|
|
582
|
+
constructor(children) {
|
|
583
|
+
super();
|
|
584
|
+
this.#children = children;
|
|
585
|
+
}
|
|
586
|
+
cancel(message) {
|
|
587
|
+
if (this.done())
|
|
588
|
+
return false;
|
|
589
|
+
let cancelled = false;
|
|
590
|
+
for (const child of this.#children) {
|
|
591
|
+
if (child.cancel(message))
|
|
592
|
+
cancelled = true;
|
|
593
|
+
}
|
|
594
|
+
// Propagate the cancellation once the children are done.
|
|
595
|
+
if (cancelled)
|
|
596
|
+
this.cancelRequested = true;
|
|
597
|
+
return cancelled;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function gatherFuture(aws, returnExceptions) {
|
|
601
|
+
if (aws.length === 0) {
|
|
602
|
+
const outer = new Future();
|
|
603
|
+
outer.setResult([]);
|
|
604
|
+
return outer;
|
|
605
|
+
}
|
|
606
|
+
const children = [];
|
|
607
|
+
const doneFutures = [];
|
|
608
|
+
const seen = new Map();
|
|
609
|
+
let nfutures = 0;
|
|
610
|
+
let nfinished = 0;
|
|
611
|
+
let outer = null;
|
|
612
|
+
const onDone = (future) => {
|
|
613
|
+
nfinished++;
|
|
614
|
+
if (outer === null || outer.done())
|
|
615
|
+
return;
|
|
616
|
+
if (!returnExceptions) {
|
|
617
|
+
if (future.cancelled()) {
|
|
618
|
+
outer.setException(future._makeCancelledError());
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const error = future.exception();
|
|
622
|
+
if (error !== undefined) {
|
|
623
|
+
outer.setException(error);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (nfinished === nfutures) {
|
|
628
|
+
const results = children.map((child) => {
|
|
629
|
+
if (child.cancelled())
|
|
630
|
+
return child._makeCancelledError();
|
|
631
|
+
const error = child.exception();
|
|
632
|
+
return error !== undefined ? error : child.result();
|
|
633
|
+
});
|
|
634
|
+
if (outer.cancelRequested)
|
|
635
|
+
outer.setException(future._makeCancelledError());
|
|
636
|
+
else
|
|
637
|
+
outer.setResult(results);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
for (const aw of aws) {
|
|
641
|
+
let future = seen.get(aw);
|
|
642
|
+
if (!future) {
|
|
643
|
+
future = ensureFuture(aw);
|
|
644
|
+
nfutures++;
|
|
645
|
+
seen.set(aw, future);
|
|
646
|
+
if (future.done())
|
|
647
|
+
doneFutures.push(future);
|
|
648
|
+
else
|
|
649
|
+
future.addDoneCallback(onDone);
|
|
650
|
+
}
|
|
651
|
+
children.push(future);
|
|
652
|
+
}
|
|
653
|
+
outer = new GatheringFuture(children);
|
|
654
|
+
for (const future of doneFutures)
|
|
655
|
+
onDone(future);
|
|
656
|
+
return outer;
|
|
409
657
|
}
|
|
410
658
|
/**
|
|
411
659
|
* asyncio.gather(*aws). Functions run as child tasks; if the caller is
|
|
412
660
|
* cancelled, all children are cancelled.
|
|
413
661
|
*/
|
|
414
|
-
export
|
|
415
|
-
|
|
416
|
-
return (await suspend((resolve, reject) => {
|
|
417
|
-
Promise.all(tasks.map((t) => t.promise)).then(resolve, reject);
|
|
418
|
-
return () => { };
|
|
419
|
-
}, () => {
|
|
420
|
-
for (const t of tasks)
|
|
421
|
-
t.cancel();
|
|
422
|
-
}));
|
|
662
|
+
export function gather(...aws) {
|
|
663
|
+
return awaitFuture(gatherFuture(aws, false));
|
|
423
664
|
}
|
|
424
665
|
/** asyncio.gather(*aws, return_exceptions=True) */
|
|
425
|
-
export
|
|
426
|
-
|
|
427
|
-
return await suspend((resolve) => {
|
|
428
|
-
Promise.allSettled(tasks.map((t) => t.promise)).then((results) => resolve(results.map((r) => (r.status === "fulfilled" ? r.value : r.reason))));
|
|
429
|
-
return () => { };
|
|
430
|
-
}, () => {
|
|
431
|
-
for (const t of tasks)
|
|
432
|
-
t.cancel();
|
|
433
|
-
});
|
|
666
|
+
export function gatherSettled(...aws) {
|
|
667
|
+
return awaitFuture(gatherFuture(aws, true));
|
|
434
668
|
}
|
|
435
669
|
export const FIRST_COMPLETED = "FIRST_COMPLETED";
|
|
436
670
|
export const FIRST_EXCEPTION = "FIRST_EXCEPTION";
|
|
@@ -439,54 +673,74 @@ export const ALL_COMPLETED = "ALL_COMPLETED";
|
|
|
439
673
|
* asyncio.wait(tasks, timeout, return_when). Does not cancel anything on
|
|
440
674
|
* timeout. Returns [done, pending].
|
|
441
675
|
*/
|
|
442
|
-
export function wait(tasks, options = {}) {
|
|
443
|
-
const all = [...tasks];
|
|
676
|
+
export async function wait(tasks, options = {}) {
|
|
677
|
+
const all = [...new Set(tasks)];
|
|
444
678
|
const returnWhen = options.returnWhen ?? ALL_COMPLETED;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
];
|
|
449
|
-
return suspend((resolve) => {
|
|
450
|
-
let timer;
|
|
451
|
-
const check = () => {
|
|
452
|
-
const done = all.filter((t) => t.done());
|
|
453
|
-
const finished = done.length === all.length ||
|
|
454
|
-
(returnWhen === FIRST_COMPLETED && done.length > 0) ||
|
|
455
|
-
(returnWhen === FIRST_EXCEPTION &&
|
|
456
|
-
done.some((t) => !t.cancelled() && t.exception() !== undefined));
|
|
457
|
-
if (finished) {
|
|
458
|
-
if (timer)
|
|
459
|
-
clearTimeout(timer);
|
|
460
|
-
resolve(result());
|
|
461
|
-
}
|
|
462
|
-
};
|
|
463
|
-
for (const t of all)
|
|
464
|
-
t.addDoneCallback(check);
|
|
679
|
+
if (all.length > 0) {
|
|
680
|
+
const waiter = new Future();
|
|
681
|
+
let timeoutHandle = null;
|
|
465
682
|
if (options.timeout !== undefined && options.timeout !== null) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
683
|
+
timeoutHandle = callLater(options.timeout, () => {
|
|
684
|
+
if (!waiter.done())
|
|
685
|
+
waiter.setResult(undefined);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
let counter = all.length;
|
|
689
|
+
const onCompletion = (future) => {
|
|
690
|
+
counter--;
|
|
691
|
+
if (counter <= 0 ||
|
|
692
|
+
returnWhen === FIRST_COMPLETED ||
|
|
693
|
+
(returnWhen === FIRST_EXCEPTION && !future.cancelled() && future.exception() !== undefined)) {
|
|
694
|
+
timeoutHandle?.cancel();
|
|
695
|
+
if (!waiter.done())
|
|
696
|
+
waiter.setResult(undefined);
|
|
697
|
+
}
|
|
474
698
|
};
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
699
|
+
for (const future of all)
|
|
700
|
+
future.addDoneCallback(onCompletion);
|
|
701
|
+
try {
|
|
702
|
+
await waiter;
|
|
703
|
+
}
|
|
704
|
+
finally {
|
|
705
|
+
timeoutHandle?.cancel();
|
|
706
|
+
for (const future of all)
|
|
707
|
+
future.removeDoneCallback(onCompletion);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const done = new Set();
|
|
711
|
+
const pending = new Set();
|
|
712
|
+
for (const future of all)
|
|
713
|
+
(future.done() ? done : pending).add(future);
|
|
714
|
+
return [done, pending];
|
|
483
715
|
}
|
|
484
716
|
/** asyncio.shield(aw): the caller can be cancelled without cancelling `aw`. */
|
|
485
717
|
export function shield(aw) {
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
718
|
+
const inner = ensureFuture(aw);
|
|
719
|
+
if (inner.done())
|
|
720
|
+
return awaitFuture(inner);
|
|
721
|
+
const outer = new Future();
|
|
722
|
+
const onInnerDone = () => {
|
|
723
|
+
if (outer.cancelled())
|
|
724
|
+
return;
|
|
725
|
+
if (inner.cancelled()) {
|
|
726
|
+
outer.cancel();
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const error = inner.exception();
|
|
730
|
+
if (error !== undefined)
|
|
731
|
+
outer.setException(error);
|
|
732
|
+
else
|
|
733
|
+
outer.setResult(inner.result());
|
|
734
|
+
};
|
|
735
|
+
inner.addDoneCallback(onInnerDone);
|
|
736
|
+
outer.addDoneCallback(() => {
|
|
737
|
+
if (!inner.done())
|
|
738
|
+
inner.removeDoneCallback(onInnerDone);
|
|
490
739
|
});
|
|
740
|
+
return awaitFuture(outer);
|
|
741
|
+
}
|
|
742
|
+
/** Whether `value` is a Future or Task (asyncio.isfuture). */
|
|
743
|
+
export function isFuture(value) {
|
|
744
|
+
return value instanceof Future;
|
|
491
745
|
}
|
|
492
746
|
//# sourceMappingURL=primitives.js.map
|