@thi.ng/fibers 0.1.0 → 0.2.1
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/CHANGELOG.md +34 -1
- package/README.md +183 -2
- package/api.d.ts +33 -1
- package/csp.d.ts +193 -0
- package/csp.js +286 -0
- package/fiber.d.ts +3 -2
- package/fiber.js +36 -28
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/ops.d.ts +59 -7
- package/ops.js +61 -5
- package/package.json +14 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2023-08-
|
|
3
|
+
- **Last updated**: 2023-08-10T12:25:09Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -9,6 +9,39 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
9
9
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
10
|
and/or version bumps of transitive dependencies.
|
|
11
11
|
|
|
12
|
+
## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/fibers@0.2.0) (2023-08-10)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- ensure no pre-existing parent in Fiber.fork() ([612adf9](https://github.com/thi-ng/umbrella/commit/612adf9))
|
|
17
|
+
- add auto terminate option, update child handling ([e59063d](https://github.com/thi-ng/umbrella/commit/e59063d))
|
|
18
|
+
- add shuffle() operator, update deps ([b3efa79](https://github.com/thi-ng/umbrella/commit/b3efa79))
|
|
19
|
+
- add CSP primitives ([d8fa8ce](https://github.com/thi-ng/umbrella/commit/d8fa8ce))
|
|
20
|
+
- add fiber-based Channel class
|
|
21
|
+
- add various buffer implementations
|
|
22
|
+
- fifo
|
|
23
|
+
- lifo
|
|
24
|
+
- sliding
|
|
25
|
+
- dropping
|
|
26
|
+
|
|
27
|
+
#### ⏱ Performance improvements
|
|
28
|
+
|
|
29
|
+
- rewrite FIFOBuffer as ring buffer ([ebac714](https://github.com/thi-ng/umbrella/commit/ebac714))
|
|
30
|
+
- use old impl as basis for LIFOBuffer only
|
|
31
|
+
- update other buffer types to use new ring buffer impl
|
|
32
|
+
- add min. capacity assertion in ctors
|
|
33
|
+
|
|
34
|
+
#### ♻️ Refactoring
|
|
35
|
+
|
|
36
|
+
- minor update all() ([52836a8](https://github.com/thi-ng/umbrella/commit/52836a8))
|
|
37
|
+
- update arg types in various ops ([cb3c253](https://github.com/thi-ng/umbrella/commit/cb3c253))
|
|
38
|
+
|
|
39
|
+
### [0.1.1](https://github.com/thi-ng/umbrella/tree/@thi.ng/fibers@0.1.1) (2023-08-05)
|
|
40
|
+
|
|
41
|
+
#### 🩹 Bug fixes
|
|
42
|
+
|
|
43
|
+
- update dependencies ([c92ad43](https://github.com/thi-ng/umbrella/commit/c92ad43))
|
|
44
|
+
|
|
12
45
|
## [0.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/fibers@0.1.0) (2023-08-04)
|
|
13
46
|
|
|
14
47
|
#### 🚀 Features
|
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<!-- This file is generated - DO NOT EDIT! -->
|
|
2
|
+
<!-- Please see: https://github.com/thi-ng/umbrella/blob/develop/CONTRIBUTING.md#changes-to-readme-files -->
|
|
2
3
|
|
|
3
|
-
# 
|
|
4
5
|
|
|
5
6
|
[](https://www.npmjs.com/package/@thi.ng/fibers)
|
|
6
7
|

|
|
@@ -13,6 +14,10 @@ This project is part of the
|
|
|
13
14
|
- [Basic usage](#basic-usage)
|
|
14
15
|
- [Fiber operators](#fiber-operators)
|
|
15
16
|
- [Composition via transducers](#composition-via-transducers)
|
|
17
|
+
- [CSP primitives (Communicating Sequential Processes)](#csp-primitives-communicating-sequential-processes)
|
|
18
|
+
- [Buffering behaviors](#buffering-behaviors)
|
|
19
|
+
- [Channels](#channels)
|
|
20
|
+
- [CSP ping/pong example](#csp-pingpong-example)
|
|
16
21
|
- [Status](#status)
|
|
17
22
|
- [Installation](#installation)
|
|
18
23
|
- [Dependencies](#dependencies)
|
|
@@ -114,6 +119,7 @@ The following operators act as basic composition helpers to construct more elabo
|
|
|
114
119
|
- [`forkAll`](https://docs.thi.ng/umbrella/fibers/classes/Fiber.html#forkAll): create & attach multiple child processes
|
|
115
120
|
- [`join`](https://docs.thi.ng/umbrella/fibers/classes/Fiber.html#join): wait for all child processes to complete
|
|
116
121
|
- [`sequence`](https://docs.thi.ng/umbrella/fibers/functions/sequence.html): execute fibers in sequence
|
|
122
|
+
- [`shuffle`](https://docs.thi.ng/umbrella/fibers/functions/shuffle.html): execute fibers in constantly randomized order
|
|
117
123
|
- [`timeSlice`](https://docs.thi.ng/umbrella/fibers/functions/timeSlice.html): execute fiber in batches of N milliseconds
|
|
118
124
|
- [`until`](https://docs.thi.ng/umbrella/fibers/functions/until.html): wait until predicate is truthy
|
|
119
125
|
- [`untilEvent`](https://docs.thi.ng/umbrella/fibers/functions/untilEvent.html): wait until event occurs
|
|
@@ -173,6 +179,177 @@ sequence(
|
|
|
173
179
|
// part 7
|
|
174
180
|
```
|
|
175
181
|
|
|
182
|
+
### CSP primitives (Communicating Sequential Processes)
|
|
183
|
+
|
|
184
|
+
References:
|
|
185
|
+
|
|
186
|
+
- [Wikipedia](https://en.wikipedia.org/wiki/Communicating_sequential_processes)
|
|
187
|
+
- [Communicating Sequential Processes, C.A.R.
|
|
188
|
+
Hoare](https://dl.acm.org/doi/pdf/10.1145/359576.359585)
|
|
189
|
+
|
|
190
|
+
In addition to the operators above, the basic fiber implementation can also be
|
|
191
|
+
used to construct other types of primitives, like those required for
|
|
192
|
+
channel-based communication between processes, as proposed by Tony Hoare. The
|
|
193
|
+
package includes a fiber-based read/write channel primitive which can be
|
|
194
|
+
customized with different buffer implementations to control blocking behaviors
|
|
195
|
+
and backpressure handling (aka attempting to write faster to a channel than
|
|
196
|
+
values are being read, essentially a memory management issue).
|
|
197
|
+
|
|
198
|
+
#### Buffering behaviors
|
|
199
|
+
|
|
200
|
+
The following channel buffer types/behaviors are included, all accepting a max.
|
|
201
|
+
capacity and all implementing the
|
|
202
|
+
[IReadWriteBuffer](https://docs.thi.ng/umbrella/fibers/interfaces/IReadWriteBuffer.html)
|
|
203
|
+
interface required by the channel:
|
|
204
|
+
|
|
205
|
+
- [`fifo`](https://docs.thi.ng/umbrella/fibers/functions/fifo.html): First in,
|
|
206
|
+
first out ring buffer. Writes to the channel will start blocking once the
|
|
207
|
+
buffer's capacity is reached, otherwise complete immediately. Likewise,
|
|
208
|
+
channel reads are non-blocking whilst there're more buffered values available.
|
|
209
|
+
Reads will only block if the buffer is empty.
|
|
210
|
+
- [`lifo`](https://docs.thi.ng/umbrella/fibers/functions/lifo.html): Last in,
|
|
211
|
+
first out. Read/write behavior is mostly the same as with `fifo`, with the
|
|
212
|
+
important difference, that (as the name indicates), the last value written
|
|
213
|
+
will be the first value read (i.e. stack behavior).
|
|
214
|
+
- [`sliding`](https://docs.thi.ng/umbrella/fibers/functions/sliding.html):
|
|
215
|
+
Sliding window ring buffer. Writes to the channel are **never** blocking! Once
|
|
216
|
+
the buffer's capacity is reached, a new write will first expunge the oldest
|
|
217
|
+
buffered value (similar to LRU cache behavior). Read behavior is the same as
|
|
218
|
+
for `fifo`.
|
|
219
|
+
- [`dropping`](https://docs.thi.ng/umbrella/fibers/functions/dropping.html):
|
|
220
|
+
Dropping value ring buffer. Writes to the channel are **never** blocking!
|
|
221
|
+
Whilst the buffer's capacity is reached, new writes will be silently ignored.
|
|
222
|
+
Read behavior is the same as for `fifo`.
|
|
223
|
+
|
|
224
|
+
#### Channels
|
|
225
|
+
|
|
226
|
+
As mentioned previously,
|
|
227
|
+
[channels](https://docs.thi.ng/umbrella/fibers/functions/channel.html) and their
|
|
228
|
+
[read](https://docs.thi.ng/umbrella/fibers/classes/Channel.html#read),
|
|
229
|
+
[write](https://docs.thi.ng/umbrella/fibers/classes/Channel.html#write) and
|
|
230
|
+
[close](https://docs.thi.ng/umbrella/fibers/classes/Channel.html#close)
|
|
231
|
+
operations are the key building blocks for CSP. In this fiber-based
|
|
232
|
+
implementation, all channel operations are executed in individual fibers to deal
|
|
233
|
+
with the potential blocking behaviors. This is demonstrated in the simple
|
|
234
|
+
example below.
|
|
235
|
+
|
|
236
|
+
In general, due to fibers not being true multi-threaded processes (all are
|
|
237
|
+
executed in the single thread of the JS engine), any number of fibers can read
|
|
238
|
+
or write to a channel.
|
|
239
|
+
|
|
240
|
+
Channels can be created like so:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
// create unbuffered channel with single value capacity
|
|
244
|
+
const chan1 = channel();
|
|
245
|
+
|
|
246
|
+
// create channel with a FIFO buffer, capacity: 2 values
|
|
247
|
+
const chan2 = channel(2);
|
|
248
|
+
|
|
249
|
+
// create channel with a sliding window buffer and custom ID & logger
|
|
250
|
+
const chan3 = channel(
|
|
251
|
+
sliding(3),
|
|
252
|
+
{ id: "main", logger: new ConsoleLogger("chan") }
|
|
253
|
+
);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### CSP ping/pong example
|
|
257
|
+
|
|
258
|
+
```ts tangle:export/pingpong.ts
|
|
259
|
+
import { channel, fiber, wait } from "@thi.ng/fibers";
|
|
260
|
+
import { ConsoleLogger } from "@thi.ng/logger";
|
|
261
|
+
|
|
262
|
+
// create idle main fiber with custom options
|
|
263
|
+
const app = fiber(null, {
|
|
264
|
+
id: "main",
|
|
265
|
+
logger: new ConsoleLogger("app"),
|
|
266
|
+
// if true, fiber automatically terminates once all child fibers are done
|
|
267
|
+
terminate: true,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// create CSP channels (w/ default config)
|
|
271
|
+
const ping = channel<number>();
|
|
272
|
+
const pong = channel<number>();
|
|
273
|
+
|
|
274
|
+
// attach ping/pong child processes
|
|
275
|
+
app.forkAll(
|
|
276
|
+
// ping
|
|
277
|
+
function* () {
|
|
278
|
+
while (ping.readable()) {
|
|
279
|
+
// blocking read op
|
|
280
|
+
// (waits until value is available in `ping` channel)
|
|
281
|
+
const x = yield* ping.read();
|
|
282
|
+
// check if channel was closed meanwhile
|
|
283
|
+
if (x === undefined) break;
|
|
284
|
+
console.log("PING", x);
|
|
285
|
+
// possibly blocking (in general) write op to other channel
|
|
286
|
+
yield* pong.write(x);
|
|
287
|
+
// slowdown
|
|
288
|
+
yield* wait(100);
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
// pong (very similar)
|
|
292
|
+
function* () {
|
|
293
|
+
while (pong.readable()) {
|
|
294
|
+
const x = yield* pong.read();
|
|
295
|
+
if (x === undefined) break;
|
|
296
|
+
console.log("PONG", x);
|
|
297
|
+
// trigger next iteration
|
|
298
|
+
yield* ping.write(x + 1);
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
// channel managment
|
|
302
|
+
function* () {
|
|
303
|
+
// kickoff ping/pong
|
|
304
|
+
yield* ping.write(0);
|
|
305
|
+
yield* wait(1000);
|
|
306
|
+
// wait for both channels to close
|
|
307
|
+
yield* ping.close();
|
|
308
|
+
yield* pong.close();
|
|
309
|
+
}
|
|
310
|
+
);
|
|
311
|
+
app.run();
|
|
312
|
+
|
|
313
|
+
// [DEBUG] app: forking fib-0
|
|
314
|
+
// [DEBUG] app: forking fib-1
|
|
315
|
+
// [DEBUG] app: forking fib-2
|
|
316
|
+
// [DEBUG] app: running main...
|
|
317
|
+
// [DEBUG] app: init main
|
|
318
|
+
// [DEBUG] app: init fib-0
|
|
319
|
+
// [DEBUG] app: init fib-1
|
|
320
|
+
// [DEBUG] app: init fib-2
|
|
321
|
+
// PING 0
|
|
322
|
+
// PONG 0
|
|
323
|
+
// PING 1
|
|
324
|
+
// PONG 1
|
|
325
|
+
// ...
|
|
326
|
+
// PING 9
|
|
327
|
+
// PONG 9
|
|
328
|
+
// [DEBUG] app: done fib-2 undefined
|
|
329
|
+
// [DEBUG] app: deinit fib-2
|
|
330
|
+
// [DEBUG] app: done fib-1 undefined
|
|
331
|
+
// [DEBUG] app: deinit fib-1
|
|
332
|
+
// [DEBUG] app: done fib-0 undefined
|
|
333
|
+
// [DEBUG] app: deinit fib-0
|
|
334
|
+
// [DEBUG] app: cancel main
|
|
335
|
+
// [DEBUG] app: deinit main
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Additional CSP operators are planned, but since everything here is based on
|
|
339
|
+
fibers, the various channel operations can be already combined with the
|
|
340
|
+
[available fiber operators/combinators](#fiber-operators)...
|
|
341
|
+
|
|
342
|
+
For example, a channel read or write op can be combined with a timeout:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
const res = (yield* withTimeout(chan.read(), 1000)).deref();
|
|
346
|
+
if (res !== undefined) {
|
|
347
|
+
console.log("read value", x);
|
|
348
|
+
} else {
|
|
349
|
+
console.log("read timeout");
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
176
353
|
## Status
|
|
177
354
|
|
|
178
355
|
**ALPHA** - bleeding edge / work-in-progress
|
|
@@ -199,14 +376,18 @@ For Node.js REPL:
|
|
|
199
376
|
const fibers = await import("@thi.ng/fibers");
|
|
200
377
|
```
|
|
201
378
|
|
|
202
|
-
Package sizes (brotli'd, pre-treeshake): ESM:
|
|
379
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 2.25 KB
|
|
203
380
|
|
|
204
381
|
## Dependencies
|
|
205
382
|
|
|
206
383
|
- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
|
|
384
|
+
- [@thi.ng/arrays](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays)
|
|
207
385
|
- [@thi.ng/bench](https://github.com/thi-ng/umbrella/tree/develop/packages/bench)
|
|
208
386
|
- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
|
|
387
|
+
- [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
|
|
388
|
+
- [@thi.ng/idgen](https://github.com/thi-ng/umbrella/tree/develop/packages/idgen)
|
|
209
389
|
- [@thi.ng/logger](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
|
|
390
|
+
- [@thi.ng/random](https://github.com/thi-ng/umbrella/tree/develop/packages/random)
|
|
210
391
|
|
|
211
392
|
## Usage examples
|
|
212
393
|
|
package/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Fn, Fn2, IIDGen } from "@thi.ng/api";
|
|
1
|
+
import type { Fn, Fn2, IClear, IIDGen } from "@thi.ng/api";
|
|
2
2
|
import type { ILogger } from "@thi.ng/logger";
|
|
3
3
|
import type { Fiber } from "./fiber.js";
|
|
4
4
|
export type FiberFactory<T = any> = (f: Fiber<T>) => Generator<unknown, T, unknown>;
|
|
@@ -22,6 +22,15 @@ export interface FiberOpts {
|
|
|
22
22
|
* @internal
|
|
23
23
|
*/
|
|
24
24
|
parent: Fiber;
|
|
25
|
+
/**
|
|
26
|
+
* If true (default: false), the fiber cancels itself once it has no more
|
|
27
|
+
* children. See {@link Fiber.cancel}.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* When enabling this option, ensure that the fiber has child processes
|
|
31
|
+
* attached **before** execution!
|
|
32
|
+
*/
|
|
33
|
+
terminate: boolean;
|
|
25
34
|
/**
|
|
26
35
|
* User init handler
|
|
27
36
|
*/
|
|
@@ -59,4 +68,27 @@ export declare const EVENT_FIBER_CANCELED = "fiber-canceled";
|
|
|
59
68
|
*/
|
|
60
69
|
export declare const EVENT_FIBER_ERROR = "fiber-error";
|
|
61
70
|
export type FiberEventType = typeof EVENT_FIBER_DONE | typeof EVENT_FIBER_CANCELED | typeof EVENT_FIBER_ERROR | "*";
|
|
71
|
+
export interface IReadBuffer<T> {
|
|
72
|
+
/**
|
|
73
|
+
* Returns true iff the buffer has at least one value available for reading.
|
|
74
|
+
*/
|
|
75
|
+
readable(): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Unguarded read operation. Assumes the caller checked
|
|
78
|
+
* {@link IReadBuffer.readable} immediately before. Returns next value from
|
|
79
|
+
* buffer.
|
|
80
|
+
*/
|
|
81
|
+
read(): T;
|
|
82
|
+
}
|
|
83
|
+
export interface IReadWriteBuffer<T> extends IReadBuffer<T>, IClear {
|
|
84
|
+
/**
|
|
85
|
+
* Returns true iff the buffer has at least one slot available for writing.
|
|
86
|
+
*/
|
|
87
|
+
writable(): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Unguarded write operation. Assumes the caller checked
|
|
90
|
+
* {@link IReadWriteBuffer.writable} immediately before.
|
|
91
|
+
*/
|
|
92
|
+
write(x: T): void;
|
|
93
|
+
}
|
|
62
94
|
//# sourceMappingURL=api.d.ts.map
|
package/csp.d.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { FiberOpts, IReadWriteBuffer } from "./api.js";
|
|
2
|
+
import { Fiber } from "./fiber.js";
|
|
3
|
+
declare const STATE_OPEN = 0;
|
|
4
|
+
declare const STATE_CLOSING = 1;
|
|
5
|
+
declare const STATE_CLOSED = 2;
|
|
6
|
+
type CSPState = typeof STATE_OPEN | typeof STATE_CLOSING | typeof STATE_CLOSED;
|
|
7
|
+
/**
|
|
8
|
+
* Fiber-based CSP channel implementation, supporting any
|
|
9
|
+
* {@link IReadWriteBuffer} implementation to customize read/write behaviors
|
|
10
|
+
* (and ordering). By default uses a single value {@link fifo} buffer impl.
|
|
11
|
+
*/
|
|
12
|
+
export declare class Channel<T> {
|
|
13
|
+
opts?: Partial<FiberOpts> | undefined;
|
|
14
|
+
protected buffer: IReadWriteBuffer<T>;
|
|
15
|
+
protected state: CSPState;
|
|
16
|
+
constructor(buffer?: IReadWriteBuffer<T> | number, opts?: Partial<FiberOpts> | undefined);
|
|
17
|
+
/**
|
|
18
|
+
* Returns a new fiber which attempts to read a value from the channel and
|
|
19
|
+
* "blocks" until that value is available. Unless the channel has meanwhile
|
|
20
|
+
* been closed, the fiber returns the read value (otherwise: `undefined`).
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* Depending on chosen back buffer behavior/implementation and
|
|
24
|
+
* {@link Channel.close}, read requests might still be successful whilst the
|
|
25
|
+
* channel is closing and there're still buffered values.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const val = yield* chan.read();
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
read(): Fiber<T | undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* Returns a new fiber which attempts to write the given `value` to the
|
|
35
|
+
* channel and "blocks" until channel is writable (which depends on the
|
|
36
|
+
* channel's buffer implementation).
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* Once the channel has been closed (or still is closing, see
|
|
40
|
+
* {@link Channel.close}), all write requests will be silently ignored.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* yield* chan.write(23);
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
write(val: T): Fiber<any>;
|
|
48
|
+
/**
|
|
49
|
+
* Returns new fiber which closes the channel. By default this op will defer
|
|
50
|
+
* the full closing until all buffered values have been read (by another
|
|
51
|
+
* fiber), however any writes will already become unavailable/ignored even
|
|
52
|
+
* at this stage (also see {@link Channel.write}). If `wait=false`, the
|
|
53
|
+
* channel will be closed immediately, the backing buffered cleared and any
|
|
54
|
+
* in-flight reads or writes will be canceled.
|
|
55
|
+
*
|
|
56
|
+
* @param wait
|
|
57
|
+
*/
|
|
58
|
+
close(wait?: boolean): Fiber<any>;
|
|
59
|
+
/**
|
|
60
|
+
* Returns true if the channel is principally readable (i.e. not yet
|
|
61
|
+
* closed), however there might not be any values available yet and reads
|
|
62
|
+
* might block.
|
|
63
|
+
*/
|
|
64
|
+
readable(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Returns true if the channel is principally writable (i.e. not closing or
|
|
67
|
+
* closed), however depending on buffer behavior the channel might not yet
|
|
68
|
+
* accept new values and writes might block.
|
|
69
|
+
*/
|
|
70
|
+
writable(): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Returns true if the channel is fully closed and no further reads or
|
|
73
|
+
* writes are possible.
|
|
74
|
+
*/
|
|
75
|
+
closed(): boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Functional syntax sugar for {@link Channel} ctor, using provided backing
|
|
79
|
+
* buffer and shared options for all fibers returned by the channel for its
|
|
80
|
+
* various operations.
|
|
81
|
+
*
|
|
82
|
+
* @remarks
|
|
83
|
+
* If `buffer` is given as number, a {@link fifo} buffer of given capacity will
|
|
84
|
+
* be used.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* // create unbuffered channel with single value capacity
|
|
89
|
+
* const chan = channel();
|
|
90
|
+
*
|
|
91
|
+
* // create channel with a fixed buffer capacity of 3 values
|
|
92
|
+
* const chan2 = channel(3);
|
|
93
|
+
*
|
|
94
|
+
* // create channel with a sliding window buffer and custom ID & logger
|
|
95
|
+
* const chan3 = channel(
|
|
96
|
+
* sliding(3),
|
|
97
|
+
* { id: "main", logger: new ConsoleLogger("chan") }
|
|
98
|
+
* );
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @param buffer
|
|
102
|
+
* @param opts
|
|
103
|
+
*/
|
|
104
|
+
export declare const channel: <T>(buffer?: number | IReadWriteBuffer<T> | undefined, opts?: Partial<FiberOpts>) => Channel<T>;
|
|
105
|
+
/**
|
|
106
|
+
* First-in, first-out ring buffer implementation for use with {@link Channel}.
|
|
107
|
+
*/
|
|
108
|
+
export declare class FIFOBuffer<T> implements IReadWriteBuffer<T> {
|
|
109
|
+
protected buf: (T | undefined)[];
|
|
110
|
+
protected rpos: number;
|
|
111
|
+
protected wpos: number;
|
|
112
|
+
constructor(cap?: number);
|
|
113
|
+
clear(): void;
|
|
114
|
+
readable(): boolean;
|
|
115
|
+
read(): NonNullable<T>;
|
|
116
|
+
writable(): boolean;
|
|
117
|
+
write(x: T): void;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Returns a {@link FIFOBuffer} ring buffer with given capacity for use with
|
|
121
|
+
* {@link channel}.
|
|
122
|
+
*
|
|
123
|
+
* @remarks
|
|
124
|
+
* With this implementation, writes to the channel will only start blocking once
|
|
125
|
+
* the buffer's capacity is reached, otherwise complete immediately. Likewise,
|
|
126
|
+
* channel reads are non-blocking whilst there're more buffered values
|
|
127
|
+
* available. Reads will only block if the buffer is empty.
|
|
128
|
+
*
|
|
129
|
+
* Also see {@link lifo}.
|
|
130
|
+
*
|
|
131
|
+
* @param cap
|
|
132
|
+
*/
|
|
133
|
+
export declare const fifo: <T>(cap: number) => FIFOBuffer<T>;
|
|
134
|
+
export declare class LIFOBuffer<T> implements IReadWriteBuffer<T> {
|
|
135
|
+
protected cap: number;
|
|
136
|
+
protected buf: T[];
|
|
137
|
+
constructor(cap?: number);
|
|
138
|
+
clear(): void;
|
|
139
|
+
readable(): boolean;
|
|
140
|
+
read(): NonNullable<T>;
|
|
141
|
+
writable(): boolean;
|
|
142
|
+
write(x: T): void;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Returns a {@link LIFOBuffer} with given capacity for use with
|
|
146
|
+
* {@link channel}.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* Read/write behavior is mostly the same as with {@link fifo}, with the
|
|
150
|
+
* important difference, that (as the name indicates), the last value written
|
|
151
|
+
* will be the first value read (i.e. stack behavior).
|
|
152
|
+
*
|
|
153
|
+
* @param cap
|
|
154
|
+
*/
|
|
155
|
+
export declare const lifo: <T>(cap: number) => LIFOBuffer<T>;
|
|
156
|
+
export declare class SlidingBuffer<T> extends FIFOBuffer<T> {
|
|
157
|
+
writable(): boolean;
|
|
158
|
+
write(x: T): void;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Returns a {@link SlidingBuffer} with given capacity for use with
|
|
162
|
+
* {@link channel}.
|
|
163
|
+
*
|
|
164
|
+
* @remarks
|
|
165
|
+
* With this implementation, writes to the channel are **never** blocking! Once
|
|
166
|
+
* the buffer's capacity is reached, a new write will first expunge the oldest
|
|
167
|
+
* buffered value. Read behavior is the same as for {@link fifo}.
|
|
168
|
+
*
|
|
169
|
+
* Also see {@link dropping} for alternative behavior.
|
|
170
|
+
*
|
|
171
|
+
* @param cap
|
|
172
|
+
*/
|
|
173
|
+
export declare const sliding: <T>(cap: number) => SlidingBuffer<T>;
|
|
174
|
+
export declare class DroppingBuffer<T> extends FIFOBuffer<T> {
|
|
175
|
+
writable(): boolean;
|
|
176
|
+
write(x: T): void;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Returns a {@link DroppingBuffer} with given capacity for use with
|
|
180
|
+
* {@link channel}.
|
|
181
|
+
*
|
|
182
|
+
* @remarks
|
|
183
|
+
* With this implementation, writes to the channel are **never** blocking!
|
|
184
|
+
* Whilst the buffer's capacity is reached, new writes will be silently ignored.
|
|
185
|
+
* Read behavior is the same as for {@link fifo}.
|
|
186
|
+
*
|
|
187
|
+
* Also see {@link sliding} for alternative behavior.
|
|
188
|
+
*
|
|
189
|
+
* @param cap
|
|
190
|
+
*/
|
|
191
|
+
export declare const dropping: <T>(cap: number) => DroppingBuffer<T>;
|
|
192
|
+
export {};
|
|
193
|
+
//# sourceMappingURL=csp.d.ts.map
|
package/csp.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { isNumber } from "@thi.ng/checks";
|
|
2
|
+
import { assert } from "@thi.ng/errors/assert";
|
|
3
|
+
import { Fiber, fiber } from "./fiber.js";
|
|
4
|
+
const STATE_OPEN = 0;
|
|
5
|
+
const STATE_CLOSING = 1;
|
|
6
|
+
const STATE_CLOSED = 2;
|
|
7
|
+
/**
|
|
8
|
+
* Fiber-based CSP channel implementation, supporting any
|
|
9
|
+
* {@link IReadWriteBuffer} implementation to customize read/write behaviors
|
|
10
|
+
* (and ordering). By default uses a single value {@link fifo} buffer impl.
|
|
11
|
+
*/
|
|
12
|
+
export class Channel {
|
|
13
|
+
constructor(buffer = 1, opts) {
|
|
14
|
+
this.opts = opts;
|
|
15
|
+
this.state = STATE_OPEN;
|
|
16
|
+
this.buffer = isNumber(buffer) ? new FIFOBuffer(buffer) : buffer;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns a new fiber which attempts to read a value from the channel and
|
|
20
|
+
* "blocks" until that value is available. Unless the channel has meanwhile
|
|
21
|
+
* been closed, the fiber returns the read value (otherwise: `undefined`).
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* Depending on chosen back buffer behavior/implementation and
|
|
25
|
+
* {@link Channel.close}, read requests might still be successful whilst the
|
|
26
|
+
* channel is closing and there're still buffered values.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const val = yield* chan.read();
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
read() {
|
|
34
|
+
const $this = this;
|
|
35
|
+
return fiber(function* (ctx) {
|
|
36
|
+
while ($this.readable()) {
|
|
37
|
+
// wait until channel is readable
|
|
38
|
+
if ($this.buffer.readable()) {
|
|
39
|
+
const val = $this.buffer.read();
|
|
40
|
+
ctx.logger?.debug("read", val);
|
|
41
|
+
return val;
|
|
42
|
+
}
|
|
43
|
+
else if ($this.state === STATE_CLOSING) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
yield;
|
|
47
|
+
}
|
|
48
|
+
}, this.opts);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns a new fiber which attempts to write the given `value` to the
|
|
52
|
+
* channel and "blocks" until channel is writable (which depends on the
|
|
53
|
+
* channel's buffer implementation).
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* Once the channel has been closed (or still is closing, see
|
|
57
|
+
* {@link Channel.close}), all write requests will be silently ignored.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* yield* chan.write(23);
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
write(val) {
|
|
65
|
+
const $this = this;
|
|
66
|
+
return fiber(function* (ctx) {
|
|
67
|
+
while ($this.writable()) {
|
|
68
|
+
// wait until channel is writable
|
|
69
|
+
if ($this.buffer.writable()) {
|
|
70
|
+
ctx.logger?.debug("write", val);
|
|
71
|
+
$this.buffer.write(val);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
yield;
|
|
75
|
+
}
|
|
76
|
+
}, this.opts);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns new fiber which closes the channel. By default this op will defer
|
|
80
|
+
* the full closing until all buffered values have been read (by another
|
|
81
|
+
* fiber), however any writes will already become unavailable/ignored even
|
|
82
|
+
* at this stage (also see {@link Channel.write}). If `wait=false`, the
|
|
83
|
+
* channel will be closed immediately, the backing buffered cleared and any
|
|
84
|
+
* in-flight reads or writes will be canceled.
|
|
85
|
+
*
|
|
86
|
+
* @param wait
|
|
87
|
+
*/
|
|
88
|
+
close(wait = true) {
|
|
89
|
+
const $this = this;
|
|
90
|
+
return fiber(function* (ctx) {
|
|
91
|
+
if ($this.state >= STATE_CLOSING)
|
|
92
|
+
return;
|
|
93
|
+
if (wait) {
|
|
94
|
+
ctx.logger?.debug("waiting to close...");
|
|
95
|
+
$this.state = STATE_CLOSING;
|
|
96
|
+
while ($this.buffer.readable())
|
|
97
|
+
yield;
|
|
98
|
+
}
|
|
99
|
+
$this.state = STATE_CLOSED;
|
|
100
|
+
$this.buffer.clear();
|
|
101
|
+
ctx.logger?.debug("channel closed");
|
|
102
|
+
}, this.opts);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Returns true if the channel is principally readable (i.e. not yet
|
|
106
|
+
* closed), however there might not be any values available yet and reads
|
|
107
|
+
* might block.
|
|
108
|
+
*/
|
|
109
|
+
readable() {
|
|
110
|
+
return this.state <= STATE_CLOSING;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Returns true if the channel is principally writable (i.e. not closing or
|
|
114
|
+
* closed), however depending on buffer behavior the channel might not yet
|
|
115
|
+
* accept new values and writes might block.
|
|
116
|
+
*/
|
|
117
|
+
writable() {
|
|
118
|
+
return this.state === STATE_OPEN;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Returns true if the channel is fully closed and no further reads or
|
|
122
|
+
* writes are possible.
|
|
123
|
+
*/
|
|
124
|
+
closed() {
|
|
125
|
+
return this.state === STATE_CLOSED;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Functional syntax sugar for {@link Channel} ctor, using provided backing
|
|
130
|
+
* buffer and shared options for all fibers returned by the channel for its
|
|
131
|
+
* various operations.
|
|
132
|
+
*
|
|
133
|
+
* @remarks
|
|
134
|
+
* If `buffer` is given as number, a {@link fifo} buffer of given capacity will
|
|
135
|
+
* be used.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* // create unbuffered channel with single value capacity
|
|
140
|
+
* const chan = channel();
|
|
141
|
+
*
|
|
142
|
+
* // create channel with a fixed buffer capacity of 3 values
|
|
143
|
+
* const chan2 = channel(3);
|
|
144
|
+
*
|
|
145
|
+
* // create channel with a sliding window buffer and custom ID & logger
|
|
146
|
+
* const chan3 = channel(
|
|
147
|
+
* sliding(3),
|
|
148
|
+
* { id: "main", logger: new ConsoleLogger("chan") }
|
|
149
|
+
* );
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* @param buffer
|
|
153
|
+
* @param opts
|
|
154
|
+
*/
|
|
155
|
+
export const channel = (buffer, opts) => new Channel(buffer, opts);
|
|
156
|
+
/**
|
|
157
|
+
* First-in, first-out ring buffer implementation for use with {@link Channel}.
|
|
158
|
+
*/
|
|
159
|
+
export class FIFOBuffer {
|
|
160
|
+
constructor(cap = 1) {
|
|
161
|
+
this.rpos = 0;
|
|
162
|
+
this.wpos = 0;
|
|
163
|
+
assert(cap >= 1, `capacity must be >= 1`);
|
|
164
|
+
this.buf = new Array(cap + 1);
|
|
165
|
+
}
|
|
166
|
+
clear() {
|
|
167
|
+
this.buf.fill(undefined);
|
|
168
|
+
}
|
|
169
|
+
readable() {
|
|
170
|
+
return this.rpos !== this.wpos;
|
|
171
|
+
}
|
|
172
|
+
read() {
|
|
173
|
+
const { buf, rpos } = this;
|
|
174
|
+
const val = buf[rpos];
|
|
175
|
+
buf[rpos] = undefined;
|
|
176
|
+
this.rpos = (rpos + 1) % buf.length;
|
|
177
|
+
return val;
|
|
178
|
+
}
|
|
179
|
+
writable() {
|
|
180
|
+
return (this.wpos + 1) % this.buf.length !== this.rpos;
|
|
181
|
+
}
|
|
182
|
+
write(x) {
|
|
183
|
+
const { buf, wpos } = this;
|
|
184
|
+
buf[wpos] = x;
|
|
185
|
+
this.wpos = (wpos + 1) % buf.length;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Returns a {@link FIFOBuffer} ring buffer with given capacity for use with
|
|
190
|
+
* {@link channel}.
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* With this implementation, writes to the channel will only start blocking once
|
|
194
|
+
* the buffer's capacity is reached, otherwise complete immediately. Likewise,
|
|
195
|
+
* channel reads are non-blocking whilst there're more buffered values
|
|
196
|
+
* available. Reads will only block if the buffer is empty.
|
|
197
|
+
*
|
|
198
|
+
* Also see {@link lifo}.
|
|
199
|
+
*
|
|
200
|
+
* @param cap
|
|
201
|
+
*/
|
|
202
|
+
export const fifo = (cap) => new FIFOBuffer(cap);
|
|
203
|
+
export class LIFOBuffer {
|
|
204
|
+
constructor(cap = 1) {
|
|
205
|
+
this.cap = cap;
|
|
206
|
+
this.buf = [];
|
|
207
|
+
assert(cap >= 1, `capacity must be >= 1`);
|
|
208
|
+
}
|
|
209
|
+
clear() {
|
|
210
|
+
this.buf.length = 0;
|
|
211
|
+
}
|
|
212
|
+
readable() {
|
|
213
|
+
return this.buf.length > 0;
|
|
214
|
+
}
|
|
215
|
+
read() {
|
|
216
|
+
return this.buf.pop();
|
|
217
|
+
}
|
|
218
|
+
writable() {
|
|
219
|
+
return this.buf.length < this.cap;
|
|
220
|
+
}
|
|
221
|
+
write(x) {
|
|
222
|
+
this.buf.push(x);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Returns a {@link LIFOBuffer} with given capacity for use with
|
|
227
|
+
* {@link channel}.
|
|
228
|
+
*
|
|
229
|
+
* @remarks
|
|
230
|
+
* Read/write behavior is mostly the same as with {@link fifo}, with the
|
|
231
|
+
* important difference, that (as the name indicates), the last value written
|
|
232
|
+
* will be the first value read (i.e. stack behavior).
|
|
233
|
+
*
|
|
234
|
+
* @param cap
|
|
235
|
+
*/
|
|
236
|
+
export const lifo = (cap) => new LIFOBuffer(cap);
|
|
237
|
+
export class SlidingBuffer extends FIFOBuffer {
|
|
238
|
+
writable() {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
write(x) {
|
|
242
|
+
if (!super.writable()) {
|
|
243
|
+
const { buf, rpos } = this;
|
|
244
|
+
buf[rpos] = undefined;
|
|
245
|
+
this.rpos = (rpos + 1) % buf.length;
|
|
246
|
+
}
|
|
247
|
+
super.write(x);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Returns a {@link SlidingBuffer} with given capacity for use with
|
|
252
|
+
* {@link channel}.
|
|
253
|
+
*
|
|
254
|
+
* @remarks
|
|
255
|
+
* With this implementation, writes to the channel are **never** blocking! Once
|
|
256
|
+
* the buffer's capacity is reached, a new write will first expunge the oldest
|
|
257
|
+
* buffered value. Read behavior is the same as for {@link fifo}.
|
|
258
|
+
*
|
|
259
|
+
* Also see {@link dropping} for alternative behavior.
|
|
260
|
+
*
|
|
261
|
+
* @param cap
|
|
262
|
+
*/
|
|
263
|
+
export const sliding = (cap) => new SlidingBuffer(cap);
|
|
264
|
+
export class DroppingBuffer extends FIFOBuffer {
|
|
265
|
+
writable() {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
write(x) {
|
|
269
|
+
if (super.writable())
|
|
270
|
+
super.write(x);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Returns a {@link DroppingBuffer} with given capacity for use with
|
|
275
|
+
* {@link channel}.
|
|
276
|
+
*
|
|
277
|
+
* @remarks
|
|
278
|
+
* With this implementation, writes to the channel are **never** blocking!
|
|
279
|
+
* Whilst the buffer's capacity is reached, new writes will be silently ignored.
|
|
280
|
+
* Read behavior is the same as for {@link fifo}.
|
|
281
|
+
*
|
|
282
|
+
* Also see {@link sliding} for alternative behavior.
|
|
283
|
+
*
|
|
284
|
+
* @param cap
|
|
285
|
+
*/
|
|
286
|
+
export const dropping = (cap) => new DroppingBuffer(cap);
|
package/fiber.d.ts
CHANGED
|
@@ -14,7 +14,8 @@ export declare class Fiber<T = any> implements IDeref<T | undefined>, IID<string
|
|
|
14
14
|
gen: Nullable<Generator<unknown, T>>;
|
|
15
15
|
idgen?: IIDGen<string>;
|
|
16
16
|
state: State;
|
|
17
|
-
|
|
17
|
+
autoTerminate: boolean;
|
|
18
|
+
children: Fiber[];
|
|
18
19
|
value?: T;
|
|
19
20
|
error?: Error;
|
|
20
21
|
logger?: ILogger;
|
|
@@ -92,7 +93,7 @@ export declare class Fiber<T = any> implements IDeref<T | undefined>, IID<string
|
|
|
92
93
|
* @param f
|
|
93
94
|
* @param opts
|
|
94
95
|
*/
|
|
95
|
-
fork<F>(body?: Nullable<MaybeFiber<F>>, opts?: Partial<FiberOpts>): Fiber<
|
|
96
|
+
fork<F>(body?: Nullable<MaybeFiber<F>>, opts?: Partial<FiberOpts>): Fiber<any>;
|
|
96
97
|
/**
|
|
97
98
|
* Calls {@link Fiber.fork} for all given fibers and returns them as array.
|
|
98
99
|
*
|
package/fiber.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
+
var Fiber_1;
|
|
1
2
|
import { __decorate } from "tslib";
|
|
2
3
|
import { INotifyMixin } from "@thi.ng/api/mixins/inotify";
|
|
3
4
|
import { isFunction } from "@thi.ng/checks/is-function";
|
|
5
|
+
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
4
6
|
import { illegalState } from "@thi.ng/errors/illegal-state";
|
|
5
7
|
import { monotonic, prefixed } from "@thi.ng/idgen";
|
|
6
8
|
import { EVENT_FIBER_CANCELED, EVENT_FIBER_DONE, EVENT_FIBER_ERROR, STATE_ACTIVE, STATE_CANCELED, STATE_DONE, STATE_ERROR, STATE_NEW, } from "./api.js";
|
|
7
9
|
let DEFAULT_ID_GEN = prefixed("fib-", monotonic());
|
|
8
10
|
export const setDefaultIDGen = (gen) => (DEFAULT_ID_GEN = gen);
|
|
9
11
|
const NO_RESULT = { done: false, value: undefined };
|
|
10
|
-
export let Fiber = class Fiber {
|
|
12
|
+
export let Fiber = Fiber_1 = class Fiber {
|
|
11
13
|
constructor(gen, opts) {
|
|
12
14
|
this.state = STATE_NEW;
|
|
15
|
+
this.autoTerminate = false;
|
|
16
|
+
this.children = [];
|
|
13
17
|
this.value = undefined;
|
|
14
18
|
if (opts) {
|
|
19
|
+
this.autoTerminate = !!opts.terminate;
|
|
15
20
|
this.logger = opts.logger;
|
|
16
21
|
this.parent = opts.parent;
|
|
17
22
|
this.user = {
|
|
@@ -29,7 +34,7 @@ export let Fiber = class Fiber {
|
|
|
29
34
|
else {
|
|
30
35
|
this.idgen = DEFAULT_ID_GEN;
|
|
31
36
|
}
|
|
32
|
-
if (
|
|
37
|
+
if (this.idgen)
|
|
33
38
|
this.id = this.idgen.next();
|
|
34
39
|
this.gen = isFunction(gen) ? gen(this) : gen;
|
|
35
40
|
}
|
|
@@ -63,7 +68,7 @@ export let Fiber = class Fiber {
|
|
|
63
68
|
* @param id
|
|
64
69
|
*/
|
|
65
70
|
childForID(id) {
|
|
66
|
-
return this.children
|
|
71
|
+
return this.children.find((f) => f.id === id);
|
|
67
72
|
}
|
|
68
73
|
/**
|
|
69
74
|
* Adds given `body` as child process to this fiber. If not already a
|
|
@@ -118,14 +123,20 @@ export let Fiber = class Fiber {
|
|
|
118
123
|
fork(body, opts) {
|
|
119
124
|
if (!this.isActive())
|
|
120
125
|
illegalState(`fiber (id: ${this.id}) not active`);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
let $fiber;
|
|
127
|
+
if (body instanceof Fiber_1) {
|
|
128
|
+
if (body.parent)
|
|
129
|
+
illegalArgs(`fiber id: ${body.id} already has a parent`);
|
|
130
|
+
$fiber = body;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
$fiber = fiber(body, {
|
|
134
|
+
parent: this,
|
|
135
|
+
logger: this.logger,
|
|
136
|
+
idgen: this.idgen,
|
|
137
|
+
...opts,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
129
140
|
this.children.push($fiber);
|
|
130
141
|
this.logger?.debug("forking", $fiber.id);
|
|
131
142
|
return $fiber;
|
|
@@ -151,7 +162,7 @@ export let Fiber = class Fiber {
|
|
|
151
162
|
*/
|
|
152
163
|
*join() {
|
|
153
164
|
this.logger?.debug("waiting for children...");
|
|
154
|
-
while (this.children
|
|
165
|
+
while (this.children.length)
|
|
155
166
|
yield;
|
|
156
167
|
}
|
|
157
168
|
/**
|
|
@@ -174,7 +185,7 @@ export let Fiber = class Fiber {
|
|
|
174
185
|
case STATE_ACTIVE:
|
|
175
186
|
try {
|
|
176
187
|
const { children } = this;
|
|
177
|
-
if (children) {
|
|
188
|
+
if (children.length) {
|
|
178
189
|
for (let i = 0, n = children.length; i < n;) {
|
|
179
190
|
const child = children[i];
|
|
180
191
|
if (child.state > STATE_ACTIVE ||
|
|
@@ -186,6 +197,10 @@ export let Fiber = class Fiber {
|
|
|
186
197
|
i++;
|
|
187
198
|
}
|
|
188
199
|
}
|
|
200
|
+
else if (this.autoTerminate) {
|
|
201
|
+
this.cancel();
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
189
204
|
const res = this.gen ? this.gen.next(this) : NO_RESULT;
|
|
190
205
|
if (res.done)
|
|
191
206
|
this.done(res.value);
|
|
@@ -206,8 +221,7 @@ export let Fiber = class Fiber {
|
|
|
206
221
|
deinit() {
|
|
207
222
|
this.logger?.debug("deinit", this.id);
|
|
208
223
|
this.user?.deinit?.(this);
|
|
209
|
-
|
|
210
|
-
this.children = undefined;
|
|
224
|
+
this.children.length = 0;
|
|
211
225
|
this.gen = null;
|
|
212
226
|
}
|
|
213
227
|
/**
|
|
@@ -221,10 +235,8 @@ export let Fiber = class Fiber {
|
|
|
221
235
|
if (!this.isActive())
|
|
222
236
|
return;
|
|
223
237
|
this.logger?.debug("cancel", this.id);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
child.cancel();
|
|
227
|
-
}
|
|
238
|
+
for (let child of this.children)
|
|
239
|
+
child.cancel();
|
|
228
240
|
this.deinit();
|
|
229
241
|
this.state = STATE_CANCELED;
|
|
230
242
|
this.idgen?.free(this.id);
|
|
@@ -245,10 +257,8 @@ export let Fiber = class Fiber {
|
|
|
245
257
|
return;
|
|
246
258
|
this.logger?.debug("done", this.id, value);
|
|
247
259
|
this.value = value;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
child.done();
|
|
251
|
-
}
|
|
260
|
+
for (let child of this.children)
|
|
261
|
+
child.done();
|
|
252
262
|
this.deinit();
|
|
253
263
|
this.state = STATE_DONE;
|
|
254
264
|
this.idgen?.free(this.id);
|
|
@@ -272,10 +282,8 @@ export let Fiber = class Fiber {
|
|
|
272
282
|
this.logger
|
|
273
283
|
? this.logger.severe(`error ${this.id}:`, err)
|
|
274
284
|
: console.warn(`error ${this.id}:`, err);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
child.cancel();
|
|
278
|
-
}
|
|
285
|
+
for (let child of this.children)
|
|
286
|
+
child.cancel();
|
|
279
287
|
this.state = STATE_ERROR;
|
|
280
288
|
this.error = err;
|
|
281
289
|
this.deinit();
|
|
@@ -334,7 +342,7 @@ export let Fiber = class Fiber {
|
|
|
334
342
|
return this;
|
|
335
343
|
}
|
|
336
344
|
};
|
|
337
|
-
Fiber = __decorate([
|
|
345
|
+
Fiber = Fiber_1 = __decorate([
|
|
338
346
|
INotifyMixin
|
|
339
347
|
], Fiber);
|
|
340
348
|
/**
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/ops.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Fn0, Predicate } from "@thi.ng/api";
|
|
2
|
-
import
|
|
2
|
+
import type { IRandom } from "@thi.ng/random";
|
|
3
|
+
import { type FiberOpts, type MaybeFiber, type State } from "./api.js";
|
|
3
4
|
import { Fiber } from "./fiber.js";
|
|
4
5
|
/**
|
|
5
6
|
* Returns co-routine which "blocks" for given number of milliseconds or
|
|
@@ -21,7 +22,7 @@ export declare function waitFrames(delay: number): Generator<undefined, void, un
|
|
|
21
22
|
* @param fibers
|
|
22
23
|
* @param opts
|
|
23
24
|
*/
|
|
24
|
-
export declare const sequence: (fibers: Iterable<
|
|
25
|
+
export declare const sequence: (fibers: Iterable<MaybeFiber>, opts?: Partial<FiberOpts>) => Fiber<unknown>;
|
|
25
26
|
/**
|
|
26
27
|
* Returns a fiber which executes given fibers as child processes until **one**
|
|
27
28
|
* of them is finished/terminated. That child fiber itself will be the result.
|
|
@@ -44,7 +45,7 @@ export declare const sequence: (fibers: Iterable<Fiber | FiberFactory | Generato
|
|
|
44
45
|
* @param fibers
|
|
45
46
|
* @param opts
|
|
46
47
|
*/
|
|
47
|
-
export declare const first: (fibers:
|
|
48
|
+
export declare const first: (fibers: Iterable<MaybeFiber>, opts?: Partial<FiberOpts>) => Fiber<Fiber<any>>;
|
|
48
49
|
/**
|
|
49
50
|
* Returns a fiber which executes given fibers as child processes until **all**
|
|
50
51
|
* of them are finished/terminated.
|
|
@@ -55,7 +56,7 @@ export declare const first: (fibers: (Fiber | FiberFactory | Generator)[], opts?
|
|
|
55
56
|
* @param fibers
|
|
56
57
|
* @param opts
|
|
57
58
|
*/
|
|
58
|
-
export declare const all: (fibers: MaybeFiber
|
|
59
|
+
export declare const all: (fibers: Iterable<MaybeFiber>, opts?: Partial<FiberOpts>) => Fiber<void>;
|
|
59
60
|
/**
|
|
60
61
|
* Syntax sugar common use cases of {@link first} where a child fiber should be
|
|
61
62
|
* limited to a max. time period before giving up.
|
|
@@ -68,11 +69,11 @@ export declare const all: (fibers: MaybeFiber[], opts?: Partial<FiberOpts>) => F
|
|
|
68
69
|
* if (res.deref() != null) { ... }
|
|
69
70
|
* ```
|
|
70
71
|
*
|
|
71
|
-
* @param
|
|
72
|
+
* @param body
|
|
72
73
|
* @param timeout
|
|
73
74
|
* @param opts
|
|
74
75
|
*/
|
|
75
|
-
export declare const withTimeout: (
|
|
76
|
+
export declare const withTimeout: (body: MaybeFiber, timeout: number, opts?: Partial<FiberOpts>) => Fiber<Fiber<any>>;
|
|
76
77
|
/**
|
|
77
78
|
* Higher-order fiber which repeatedly executes given `fiber` until its
|
|
78
79
|
* completion, but does so in a time-sliced manner, such that the fiber never
|
|
@@ -82,7 +83,7 @@ export declare const withTimeout: (fiber: MaybeFiber, timeout: number, opts?: Pa
|
|
|
82
83
|
* @param maxTime
|
|
83
84
|
* @param opts
|
|
84
85
|
*/
|
|
85
|
-
export declare const timeSlice: (body:
|
|
86
|
+
export declare const timeSlice: (body: MaybeFiber, maxTime: number, opts?: Partial<FiberOpts>) => Fiber<void>;
|
|
86
87
|
/**
|
|
87
88
|
* Returns a fiber which "blocks" until given predicate function returns true.
|
|
88
89
|
*
|
|
@@ -127,4 +128,55 @@ export declare const untilPromise: <T>(promise: PromiseLike<T>) => Fiber<T>;
|
|
|
127
128
|
* @param opts
|
|
128
129
|
*/
|
|
129
130
|
export declare const untilEvent: (target: EventTarget, type: string, opts?: Partial<FiberOpts>) => Fiber<unknown>;
|
|
131
|
+
/**
|
|
132
|
+
* Custom fiber implementation for {@link shuffle}.
|
|
133
|
+
*/
|
|
134
|
+
export declare class Shuffle extends Fiber {
|
|
135
|
+
rnd: IRandom;
|
|
136
|
+
constructor(fibers: Iterable<MaybeFiber>, opts?: Partial<FiberOpts & {
|
|
137
|
+
rnd: IRandom;
|
|
138
|
+
}>);
|
|
139
|
+
next(): State;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Higher-order fiber for creating a constantly randomized execution order of
|
|
143
|
+
* given `fibers`, e.g. for distributing workloads. Creates and returns a new
|
|
144
|
+
* fiber as parent of the given `fibers` which then shuffles their execution
|
|
145
|
+
* order on each {@link Fiber.next} invocation/update. The fiber terminates when
|
|
146
|
+
* all children are done.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* The `rnd` option can be used to customize the
|
|
150
|
+
* [`IRandom`](https://docs.thi.ng/umbrella/random/interfaces/IRandom.html)
|
|
151
|
+
* implementation used for shuffling. Defaults to
|
|
152
|
+
* [`SYSTEM`](https://docs.thi.ng/umbrella/random/variables/SYSTEM.html).
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* import { repeatedly } from "@thi.ng/transducers";
|
|
157
|
+
*
|
|
158
|
+
* // create & run fiber with 4 children, executing in random order
|
|
159
|
+
* shuffle(
|
|
160
|
+
* repeatedly(
|
|
161
|
+
* (id) => function*() { while(true) { console.log(`worker #{id}`); yield; } },
|
|
162
|
+
* 4
|
|
163
|
+
* )
|
|
164
|
+
* ).run()
|
|
165
|
+
*
|
|
166
|
+
* // worker #0
|
|
167
|
+
* // worker #1
|
|
168
|
+
* // worker #3
|
|
169
|
+
* // worker #3
|
|
170
|
+
* // worker #2
|
|
171
|
+
* // worker #0
|
|
172
|
+
* // worker #2
|
|
173
|
+
* // ...
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* @param fibers
|
|
177
|
+
* @param opts
|
|
178
|
+
*/
|
|
179
|
+
export declare const shuffle: (fibers: Iterable<MaybeFiber>, opts?: Partial<FiberOpts & {
|
|
180
|
+
rnd: IRandom;
|
|
181
|
+
}>) => Shuffle;
|
|
130
182
|
//# sourceMappingURL=ops.d.ts.map
|
package/ops.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { shuffle as $shuffle } from "@thi.ng/arrays/shuffle";
|
|
1
2
|
import { now, timeDiff } from "@thi.ng/bench/now";
|
|
3
|
+
import { SYSTEM } from "@thi.ng/random/system";
|
|
2
4
|
import { STATE_ACTIVE, STATE_DONE, STATE_ERROR, } from "./api.js";
|
|
3
5
|
import { Fiber, fiber } from "./fiber.js";
|
|
4
6
|
/**
|
|
@@ -65,7 +67,7 @@ export const sequence = (fibers, opts) => fiber(function* (ctx) {
|
|
|
65
67
|
* @param opts
|
|
66
68
|
*/
|
|
67
69
|
export const first = (fibers, opts) => fiber(function* (ctx) {
|
|
68
|
-
const $fibers = fibers.map((f) => ctx.fork(f));
|
|
70
|
+
const $fibers = [...fibers].map((f) => ctx.fork(f));
|
|
69
71
|
while (true) {
|
|
70
72
|
for (let f of $fibers) {
|
|
71
73
|
if (!f.isActive())
|
|
@@ -85,8 +87,7 @@ export const first = (fibers, opts) => fiber(function* (ctx) {
|
|
|
85
87
|
* @param opts
|
|
86
88
|
*/
|
|
87
89
|
export const all = (fibers, opts) => fiber((ctx) => {
|
|
88
|
-
|
|
89
|
-
ctx.fork(f);
|
|
90
|
+
ctx.forkAll(...fibers);
|
|
90
91
|
return ctx.join();
|
|
91
92
|
}, opts);
|
|
92
93
|
/**
|
|
@@ -101,11 +102,11 @@ export const all = (fibers, opts) => fiber((ctx) => {
|
|
|
101
102
|
* if (res.deref() != null) { ... }
|
|
102
103
|
* ```
|
|
103
104
|
*
|
|
104
|
-
* @param
|
|
105
|
+
* @param body
|
|
105
106
|
* @param timeout
|
|
106
107
|
* @param opts
|
|
107
108
|
*/
|
|
108
|
-
export const withTimeout = (
|
|
109
|
+
export const withTimeout = (body, timeout, opts) => first([body, wait(timeout)], opts);
|
|
109
110
|
/**
|
|
110
111
|
* Higher-order fiber which repeatedly executes given `fiber` until its
|
|
111
112
|
* completion, but does so in a time-sliced manner, such that the fiber never
|
|
@@ -196,3 +197,58 @@ export const untilEvent = (target, type, opts) => {
|
|
|
196
197
|
},
|
|
197
198
|
});
|
|
198
199
|
};
|
|
200
|
+
/**
|
|
201
|
+
* Custom fiber implementation for {@link shuffle}.
|
|
202
|
+
*/
|
|
203
|
+
export class Shuffle extends Fiber {
|
|
204
|
+
constructor(fibers, opts) {
|
|
205
|
+
super((ctx) => ctx.join(), opts);
|
|
206
|
+
this.rnd = opts?.rnd || SYSTEM;
|
|
207
|
+
this.forkAll(...fibers);
|
|
208
|
+
}
|
|
209
|
+
next() {
|
|
210
|
+
if (!this.isActive())
|
|
211
|
+
return this.state;
|
|
212
|
+
$shuffle(this.children, this.children.length, this.rnd);
|
|
213
|
+
return super.next();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Higher-order fiber for creating a constantly randomized execution order of
|
|
218
|
+
* given `fibers`, e.g. for distributing workloads. Creates and returns a new
|
|
219
|
+
* fiber as parent of the given `fibers` which then shuffles their execution
|
|
220
|
+
* order on each {@link Fiber.next} invocation/update. The fiber terminates when
|
|
221
|
+
* all children are done.
|
|
222
|
+
*
|
|
223
|
+
* @remarks
|
|
224
|
+
* The `rnd` option can be used to customize the
|
|
225
|
+
* [`IRandom`](https://docs.thi.ng/umbrella/random/interfaces/IRandom.html)
|
|
226
|
+
* implementation used for shuffling. Defaults to
|
|
227
|
+
* [`SYSTEM`](https://docs.thi.ng/umbrella/random/variables/SYSTEM.html).
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* import { repeatedly } from "@thi.ng/transducers";
|
|
232
|
+
*
|
|
233
|
+
* // create & run fiber with 4 children, executing in random order
|
|
234
|
+
* shuffle(
|
|
235
|
+
* repeatedly(
|
|
236
|
+
* (id) => function*() { while(true) { console.log(`worker #{id}`); yield; } },
|
|
237
|
+
* 4
|
|
238
|
+
* )
|
|
239
|
+
* ).run()
|
|
240
|
+
*
|
|
241
|
+
* // worker #0
|
|
242
|
+
* // worker #1
|
|
243
|
+
* // worker #3
|
|
244
|
+
* // worker #3
|
|
245
|
+
* // worker #2
|
|
246
|
+
* // worker #0
|
|
247
|
+
* // worker #2
|
|
248
|
+
* // ...
|
|
249
|
+
* ```
|
|
250
|
+
*
|
|
251
|
+
* @param fibers
|
|
252
|
+
* @param opts
|
|
253
|
+
*/
|
|
254
|
+
export const shuffle = (fibers, opts) => new Shuffle(fibers, opts);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/fibers",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Process hierarchies & operators for cooperative multitasking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -34,14 +34,18 @@
|
|
|
34
34
|
"test": "testament test"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@thi.ng/api": "^8.9.
|
|
38
|
-
"@thi.ng/
|
|
39
|
-
"@thi.ng/
|
|
40
|
-
"@thi.ng/
|
|
37
|
+
"@thi.ng/api": "^8.9.2",
|
|
38
|
+
"@thi.ng/arrays": "^2.5.17",
|
|
39
|
+
"@thi.ng/bench": "^3.4.3",
|
|
40
|
+
"@thi.ng/checks": "^3.4.2",
|
|
41
|
+
"@thi.ng/errors": "^2.3.2",
|
|
42
|
+
"@thi.ng/idgen": "^2.2.2",
|
|
43
|
+
"@thi.ng/logger": "^1.4.18",
|
|
44
|
+
"@thi.ng/random": "^3.5.3"
|
|
41
45
|
},
|
|
42
46
|
"devDependencies": {
|
|
43
47
|
"@microsoft/api-extractor": "^7.36.3",
|
|
44
|
-
"@thi.ng/testament": "^0.3.
|
|
48
|
+
"@thi.ng/testament": "^0.3.20",
|
|
45
49
|
"rimraf": "^5.0.1",
|
|
46
50
|
"tools": "^0.0.1",
|
|
47
51
|
"typedoc": "^0.24.8",
|
|
@@ -71,6 +75,9 @@
|
|
|
71
75
|
"./api": {
|
|
72
76
|
"default": "./api.js"
|
|
73
77
|
},
|
|
78
|
+
"./csp": {
|
|
79
|
+
"default": "./csp.js"
|
|
80
|
+
},
|
|
74
81
|
"./fiber": {
|
|
75
82
|
"default": "./fiber.js"
|
|
76
83
|
},
|
|
@@ -82,5 +89,5 @@
|
|
|
82
89
|
"status": "alpha",
|
|
83
90
|
"year": 2023
|
|
84
91
|
},
|
|
85
|
-
"gitHead": "
|
|
92
|
+
"gitHead": "ad9ac3232c6fc5fc8a0df75ac82fc1e0e9fb0258\n"
|
|
86
93
|
}
|