ciorent 0.2.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/README.md +190 -179
- package/fiber.d.ts +9 -7
- package/fiber.js +1 -1
- package/index.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
@@ -7,127 +7,141 @@ A lightweight, low-overhead concurrency library.
|
|
7
7
|
- Fully type-safe.
|
8
8
|
|
9
9
|
## Examples
|
10
|
-
###
|
11
|
-
|
12
|
-
|
10
|
+
### Utilities
|
11
|
+
#### Spawning tasks
|
12
|
+
Utilities to create and run tasks.
|
13
13
|
```ts
|
14
|
-
import * as semaphore from 'ciorent/semaphore';
|
15
14
|
import * as co from 'ciorent';
|
16
15
|
|
17
|
-
// Only allow 2 task to run concurrently
|
18
|
-
const sem = semaphore.init(2);
|
19
|
-
|
20
16
|
const task = async (id: number) => {
|
21
|
-
|
22
|
-
|
17
|
+
await co.sleep((10 - id) * 20 + 50);
|
18
|
+
console.log('Task', id, 'done');
|
19
|
+
}
|
23
20
|
|
24
|
-
|
21
|
+
// Spawn and run 5 tasks sequentially
|
22
|
+
console.log('Running 5 tasks sequentially:');
|
23
|
+
await co.sequential(5, task);
|
25
24
|
|
26
|
-
|
27
|
-
|
25
|
+
// Spawn and run 5 tasks concurrently
|
26
|
+
console.log('Running 5 tasks concurrently:');
|
27
|
+
await Promise.all(co.spawn(5, task));
|
28
|
+
```
|
28
29
|
|
29
|
-
|
30
|
+
#### Sleep
|
31
|
+
Cross-runtime synchronous and asynchronous sleep functions.
|
32
|
+
```ts
|
33
|
+
import * as co from 'ciorent';
|
30
34
|
|
31
|
-
|
32
|
-
semaphore.signal(sem);
|
33
|
-
}
|
35
|
+
const logTime = (label: string) => console.log(label + ':', Math.floor(performance.now()) + 'ms');
|
34
36
|
|
35
|
-
|
36
|
-
co.spawn(5, task);
|
37
|
-
```
|
37
|
+
logTime('Start');
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
// Non-blocking
|
40
|
+
await co.sleep(500);
|
41
|
+
logTime('After about 0.5s');
|
42
|
+
|
43
|
+
// This blocks the event loop
|
44
|
+
// On the browser this only works in workers and blocks the worker thread
|
45
|
+
co.sleepSync(500);
|
46
|
+
logTime('After another 0.5s');
|
47
|
+
```
|
41
48
|
|
49
|
+
#### Debounce
|
50
|
+
Postpones execution until after an idle period.
|
42
51
|
```ts
|
43
52
|
import * as co from 'ciorent';
|
44
|
-
import * as fiber from 'ciorent/fiber';
|
45
|
-
|
46
|
-
const f1 = fiber.fn(function* () {
|
47
|
-
// Wait for a promise
|
48
|
-
yield co.sleep(1000);
|
49
53
|
|
50
|
-
|
51
|
-
|
52
|
-
|
54
|
+
const fn = co.debounce((id: number) => {
|
55
|
+
console.log('ID:', id);
|
56
|
+
}, 500);
|
53
57
|
|
54
|
-
|
55
|
-
|
58
|
+
fn(1); // fn(1) gets skipped
|
59
|
+
await co.sleep(100);
|
60
|
+
fn(2); // fn(2) gets executed
|
61
|
+
```
|
56
62
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
console.log('Fiber 2 recieved:', res);
|
63
|
+
#### Throttle
|
64
|
+
Executes a function at a regular interval.
|
65
|
+
```ts
|
66
|
+
import * as co from 'ciorent';
|
62
67
|
|
63
|
-
|
64
|
-
|
65
|
-
fiber.mount(childProc, proc);
|
66
|
-
});
|
68
|
+
// Allow 2 calls in 500ms
|
69
|
+
const throttle = co.throttle(500, 2);
|
67
70
|
|
68
|
-
|
71
|
+
co.spawn(8, async (id) => {
|
72
|
+
await throttle();
|
73
|
+
console.log(id + ': ' + Math.floor(performance.now()) + 'ms');
|
74
|
+
});
|
75
|
+
```
|
69
76
|
|
70
|
-
|
71
|
-
|
72
|
-
|
77
|
+
#### Pausing
|
78
|
+
Delay the execution of a function for other asynchronous tasks to run.
|
79
|
+
```ts
|
80
|
+
import * as co from 'ciorent';
|
73
81
|
|
74
|
-
|
75
|
-
|
76
|
-
|
82
|
+
// Expensive sync task
|
83
|
+
const task1 = async () => {
|
84
|
+
let x = 0;
|
77
85
|
|
78
|
-
//
|
79
|
-
|
86
|
+
// Yield control back to the runtime, allowing it to
|
87
|
+
// schedule other tasks
|
88
|
+
await co.pause;
|
80
89
|
|
81
|
-
//
|
82
|
-
|
83
|
-
|
90
|
+
// Simulate heavy operation
|
91
|
+
for (let i = 0; i < (Math.random() + 15) * 1e6; i++)
|
92
|
+
x += Math.random() * 32 + i * Math.round(Math.random() * 16);
|
84
93
|
|
85
|
-
|
86
|
-
|
94
|
+
console.log('Finish task 1:', x);
|
95
|
+
};
|
87
96
|
|
88
|
-
|
89
|
-
|
97
|
+
// Short async task
|
98
|
+
const task2 = async () => {
|
99
|
+
console.log('Start fetching...');
|
100
|
+
const txt = await fetch('http://example.com');
|
101
|
+
console.log('Fetch status', txt.status);
|
102
|
+
};
|
90
103
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
}
|
104
|
+
// Task 2 will not get blocked by task 1
|
105
|
+
task1();
|
106
|
+
task2();
|
95
107
|
```
|
96
108
|
|
97
|
-
###
|
98
|
-
A
|
109
|
+
### Channel
|
110
|
+
Channel is a synchronization primitive via message passing. A message may be sent over a channel, and another process is able to receive messages sent over a channel it has a reference to.
|
99
111
|
|
100
112
|
```ts
|
101
|
-
import * as
|
113
|
+
import * as channel from 'ciorent/channel';
|
102
114
|
import * as co from 'ciorent';
|
103
115
|
|
104
|
-
const
|
116
|
+
const c = channel.init<number>();
|
105
117
|
|
106
|
-
|
107
|
-
|
108
|
-
for (let i = 0; i < 3; i++) {
|
118
|
+
const run = async () => {
|
119
|
+
for (let i = 0; i < 5; i++) {
|
109
120
|
await co.sleep(100);
|
110
|
-
|
121
|
+
channel.send(c, i);
|
122
|
+
console.log('Sent', i);
|
111
123
|
}
|
112
124
|
|
113
|
-
// Resolve all waiting promises
|
114
|
-
//
|
115
|
-
|
116
|
-
}
|
117
|
-
|
118
|
-
// Spawn 3 tasks that recieve messages
|
119
|
-
co.spawn(3, async (id: number) => {
|
120
|
-
const sub = topic.subscribe(messages);
|
125
|
+
// Resolve all waiting promises with `undefined`
|
126
|
+
// This is a way to tell the reciever to not listen to more data
|
127
|
+
channel.flush(c);
|
128
|
+
};
|
121
129
|
|
130
|
+
const log = async () => {
|
122
131
|
while (true) {
|
123
|
-
//
|
124
|
-
const x = await
|
132
|
+
// Wait until a value is sent to the channel
|
133
|
+
const x = await channel.recieve(c);
|
125
134
|
if (x == null) break;
|
126
|
-
console.log(`Task ${id} recieved: ${x}`);
|
127
|
-
}
|
128
|
-
});
|
129
135
|
|
130
|
-
|
136
|
+
console.log('Recieved', x);
|
137
|
+
};
|
138
|
+
}
|
139
|
+
|
140
|
+
log();
|
141
|
+
run();
|
142
|
+
|
143
|
+
// This runs first
|
144
|
+
console.log('Starting...');
|
131
145
|
```
|
132
146
|
|
133
147
|
### Latch
|
@@ -157,140 +171,137 @@ task();
|
|
157
171
|
prepare();
|
158
172
|
```
|
159
173
|
|
160
|
-
###
|
161
|
-
|
174
|
+
### Semaphore
|
175
|
+
Semaphore is a concurrency primitive used to control access to a common resource by multiple processes.
|
162
176
|
|
163
177
|
```ts
|
164
|
-
import * as
|
178
|
+
import * as semaphore from 'ciorent/semaphore';
|
165
179
|
import * as co from 'ciorent';
|
166
180
|
|
167
|
-
|
181
|
+
// Only allow 2 task to run concurrently
|
182
|
+
const sem = semaphore.init(2);
|
168
183
|
|
169
|
-
const
|
170
|
-
|
171
|
-
|
172
|
-
channel.send(c, i);
|
173
|
-
console.log('Sent', i);
|
174
|
-
}
|
184
|
+
const task = async (id: number) => {
|
185
|
+
// Acquire the semaphore or wait for the semaphore to be available
|
186
|
+
await semaphore.pause(sem);
|
175
187
|
|
176
|
-
|
177
|
-
// This is a way to tell the reciever to not listen to more data
|
178
|
-
channel.flush(c);
|
179
|
-
};
|
188
|
+
console.log('Task', id, 'started');
|
180
189
|
|
181
|
-
|
182
|
-
|
183
|
-
// Wait until a value is sent to the channel
|
184
|
-
const x = await channel.recieve(c);
|
185
|
-
if (x == null) break;
|
190
|
+
// Let the main thread schedules other tasks
|
191
|
+
for (let i = 1; i <= 5; i++) await co.pause;
|
186
192
|
|
187
|
-
|
188
|
-
};
|
189
|
-
}
|
193
|
+
console.log('Task', id, 'end');
|
190
194
|
|
191
|
-
|
192
|
-
|
195
|
+
// Release the semaphore
|
196
|
+
semaphore.signal(sem);
|
197
|
+
}
|
193
198
|
|
194
|
-
//
|
195
|
-
|
199
|
+
// Try to run 5 tasks concurrently
|
200
|
+
co.spawn(5, task);
|
196
201
|
```
|
197
202
|
|
198
|
-
###
|
199
|
-
|
200
|
-
|
203
|
+
### Pubsub
|
204
|
+
A fast, simple publish-subscribe API.
|
205
|
+
|
201
206
|
```ts
|
207
|
+
import * as topic from 'ciorent/topic';
|
202
208
|
import * as co from 'ciorent';
|
203
209
|
|
204
|
-
|
205
|
-
const task1 = async () => {
|
206
|
-
let x = 0;
|
210
|
+
const messages = topic.init<number>();
|
207
211
|
|
208
|
-
|
209
|
-
|
210
|
-
|
212
|
+
// A task that publish messages
|
213
|
+
const publisher = async () => {
|
214
|
+
for (let i = 0; i < 3; i++) {
|
215
|
+
await co.sleep(100);
|
216
|
+
topic.publish(messages, i);
|
217
|
+
}
|
211
218
|
|
212
|
-
//
|
213
|
-
|
214
|
-
|
219
|
+
// Resolve all waiting promises
|
220
|
+
// And clear the value queue
|
221
|
+
topic.flush(messages);
|
222
|
+
}
|
215
223
|
|
216
|
-
|
217
|
-
|
224
|
+
// Spawn 3 tasks that recieve messages
|
225
|
+
co.spawn(3, async (id: number) => {
|
226
|
+
const sub = topic.subscribe(messages);
|
218
227
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
}
|
228
|
+
while (true) {
|
229
|
+
// Block until the value is sent
|
230
|
+
const x = await topic.recieve(sub);
|
231
|
+
if (x == null) break;
|
232
|
+
console.log(`Task ${id} recieved: ${x}`);
|
233
|
+
}
|
234
|
+
});
|
225
235
|
|
226
|
-
|
227
|
-
task1();
|
228
|
-
task2();
|
236
|
+
publisher();
|
229
237
|
```
|
230
238
|
|
231
|
-
|
232
|
-
|
239
|
+
### Fibers
|
240
|
+
Virtual threads with more controlled execution.
|
241
|
+
|
233
242
|
```ts
|
234
243
|
import * as co from 'ciorent';
|
244
|
+
import * as fiber from 'ciorent/fiber';
|
235
245
|
|
236
|
-
const
|
246
|
+
const f1 = fiber.fn(function* () {
|
247
|
+
// Wait for a promise
|
248
|
+
yield co.sleep(1000);
|
237
249
|
|
238
|
-
|
250
|
+
// Wait for a promise and return its result
|
251
|
+
const res = yield* fiber.unwrap(Promise.resolve(1));
|
252
|
+
console.log('Fiber 1 recieved:', res);
|
239
253
|
|
240
|
-
|
241
|
-
|
242
|
-
logTime('After about 0.5s');
|
254
|
+
return Math.random();
|
255
|
+
});
|
243
256
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
257
|
+
{
|
258
|
+
const main = fiber.spawn(function* (proc) {
|
259
|
+
// Start f1, wait for the process to complete and get the result
|
260
|
+
const res = yield* fiber.join(fiber.spawn(f1));
|
261
|
+
console.log('Fiber 2 recieved:', res);
|
249
262
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
263
|
+
// Start f1 and make its lifetime depends on current fiber
|
264
|
+
const childProc = fiber.spawn(f1);
|
265
|
+
fiber.mount(childProc, proc);
|
266
|
+
});
|
254
267
|
|
255
|
-
|
256
|
-
await co.sleep((10 - id) * 20 + 50);
|
257
|
-
console.log('Task', id, 'done');
|
258
|
-
}
|
268
|
+
console.log('Fiber 2 started:', fiber.resumed(main));
|
259
269
|
|
260
|
-
//
|
261
|
-
|
262
|
-
|
270
|
+
// Pause the current fiber process
|
271
|
+
fiber.pause(main);
|
272
|
+
console.log('Fiber 2 is paused:', fiber.paused(main));
|
263
273
|
|
264
|
-
//
|
265
|
-
|
266
|
-
|
267
|
-
```
|
274
|
+
// Resume the fiber
|
275
|
+
fiber.resume(main);
|
276
|
+
console.log('Fiber 2 is resumed:', fiber.resumed(main));
|
268
277
|
|
269
|
-
|
270
|
-
|
271
|
-
```ts
|
272
|
-
import * as co from 'ciorent';
|
278
|
+
// Wait for the fiber process to finish
|
279
|
+
await fiber.done(main);
|
273
280
|
|
274
|
-
|
275
|
-
console.log('
|
276
|
-
}
|
281
|
+
// Check finish status
|
282
|
+
console.log('Fiber 2 completed:', fiber.completed(main));
|
283
|
+
}
|
277
284
|
|
278
|
-
|
279
|
-
|
280
|
-
fn(2); // fn(2) gets executed
|
281
|
-
```
|
285
|
+
{
|
286
|
+
console.log('------------------------');
|
282
287
|
|
283
|
-
|
284
|
-
|
285
|
-
```ts
|
286
|
-
import * as co from 'ciorent';
|
288
|
+
const main = fiber.spawn(f1);
|
289
|
+
console.log('Fiber 1 started:', fiber.resumed(main));
|
287
290
|
|
288
|
-
//
|
289
|
-
|
291
|
+
// Stop a fiber
|
292
|
+
fiber.interrupt(main);
|
293
|
+
console.log('Fiber 1 interrupted:', fiber.interrupted(main));
|
294
|
+
}
|
290
295
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
296
|
+
{
|
297
|
+
console.log('------------------------');
|
298
|
+
|
299
|
+
const main = fiber.spawn(f1);
|
300
|
+
console.log('Fiber 1 started:', fiber.resumed(main));
|
301
|
+
|
302
|
+
// Timeout a fiber
|
303
|
+
await fiber.timeout(main, 500);
|
304
|
+
console.log('Fiber 1 stopped:', fiber.interrupted(main));
|
305
|
+
}
|
295
306
|
```
|
296
307
|
|
package/fiber.d.ts
CHANGED
@@ -41,11 +41,7 @@ export declare const completed: (t: Process) => boolean;
|
|
41
41
|
/**
|
42
42
|
* Check whether the fiber has been interrupted
|
43
43
|
*/
|
44
|
-
export declare const
|
45
|
-
/**
|
46
|
-
* Create a fiber function
|
47
|
-
* @param f
|
48
|
-
*/
|
44
|
+
export declare const interrupted: (t: Process) => boolean;
|
49
45
|
export declare const fn: <const Fn extends (thread: Process, ...args: any[]) => Generator>(f: Fn) => Fn;
|
50
46
|
/**
|
51
47
|
* A basic fiber runtime
|
@@ -63,10 +59,16 @@ export declare const pause: (t: Process) => void;
|
|
63
59
|
*/
|
64
60
|
export declare const resume: (t: Process) => void;
|
65
61
|
/**
|
66
|
-
*
|
62
|
+
* Interrupt the execution of a fiber
|
63
|
+
* @param t
|
64
|
+
*/
|
65
|
+
export declare const interrupt: (t: Process) => void;
|
66
|
+
/**
|
67
|
+
* Timeout a fiber
|
67
68
|
* @param t
|
69
|
+
* @param ms
|
68
70
|
*/
|
69
|
-
export declare const
|
71
|
+
export declare const timeout: (t: Process, ms: number) => Promise<void>;
|
70
72
|
/**
|
71
73
|
* Wait for a fiber and retrieve its result
|
72
74
|
* @param t
|
package/fiber.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
export let paused=(t)=>t[1]===0;export let resumed=(t)=>t[1]===1;export let completed=(t)=>t[1]===2;export let
|
1
|
+
import{sleep}from"./index.js";export let paused=(t)=>t[1]===0;export let resumed=(t)=>t[1]===1;export let completed=(t)=>t[1]===2;export let interrupted=(t)=>t[1]===3;let invoke=async(g,thread)=>{try{let t=g.next();while(!t.done){let v=await t.value;if(thread[1]===0){let r;let p=new Promise((res)=>{r=res});thread[2]=r;await p}if(thread[1]===3)return;t=g.next(v)}thread[1]=2;return t.value}finally{if(thread[1]!==2)thread[1]=3;thread[3].forEach(stop)}};export let fn=(f)=>f;export let spawn=(f,...args)=>{let thread=[null,1,null,[]];thread[0]=invoke(f(thread,...args),thread);return thread};export let pause=(t)=>{if(t[1]===1)t[1]=0};export let resume=(t)=>{if(t[1]===0){t[1]=1;t[2]?.()}};export let interrupt=(t)=>{if(t[1]!==2){if(t[1]===0)t[2]?.();t[1]=3}};export let timeout=async(t,ms)=>{await sleep(ms);interrupt(t)};export function*join(t){return yield t[0]}export let done=(t)=>t[0];export let mount=(child,parent)=>{parent[3].push(child)};export let control=(t,signal)=>{signal.addEventListener("abort",()=>{interrupt(t)})};export function*unwrap(t){return yield t}
|
package/index.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
export let pause=Promise.resolve();export let sleep=globalThis.Bun?.sleep??globalThis.process?.getBuiltinModule?.("timers/promises").setTimeout??((ms)=>new Promise((res)=>{setTimeout(res,ms)}));let sharedBuf=new Int32Array(new SharedArrayBuffer(4));export let sleepSync=globalThis.Bun?.sleepSync??((ms)=>{Atomics.wait(sharedBuf,0,0,ms)});export let sequential=async(n,task,...args)=>{for(let i=0;i<n;i++)await task(...args,i)};export let spawn=(n,task,...args)=>{let arr=new Array(n);for(let i=0;i<n;i++)arr[i]=task(...args,i);return arr};export let debounce=(f,ms)=>{let id;return(...a)=>{clearTimeout(id);id=setTimeout(f,ms,...a)}};export let throttle=(ms,limit)=>{let head=[null];let tail=head;let cur=limit;let scheduled=false;let unlock=()=>{cur=limit;
|
1
|
+
export let pause=Promise.resolve();export let sleep=globalThis.Bun?.sleep??globalThis.process?.getBuiltinModule?.("timers/promises").setTimeout??((ms)=>new Promise((res)=>{setTimeout(res,ms)}));let sharedBuf=new Int32Array(new SharedArrayBuffer(4));export let sleepSync=globalThis.Bun?.sleepSync??((ms)=>{Atomics.wait(sharedBuf,0,0,ms)});export let sequential=async(n,task,...args)=>{for(let i=0;i<n;i++)await task(...args,i)};export let spawn=(n,task,...args)=>{let arr=new Array(n);for(let i=0;i<n;i++)arr[i]=task(...args,i);return arr};export let debounce=(f,ms)=>{let id;return(...a)=>{clearTimeout(id);id=setTimeout(f,ms,...a)}};export let throttle=(ms,limit)=>{let head=[null];let tail=head;let cur=limit;let scheduled=false;let unlock=()=>{cur=limit;if(tail===head){scheduled=false;return}do{cur--;(tail=tail[0])[1]()}while(cur>0&&tail!==head);setTimeout(unlock,ms)};return()=>{if(cur===0){let r;let p=new Promise((res)=>{r=res});head=head[0]=[null,r];return p}if(!scheduled){scheduled=true;setTimeout(unlock,ms)}cur--;return pause}};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "ciorent",
|
3
|
-
"version": "0.2.
|
3
|
+
"version": "0.2.1",
|
4
4
|
"description": "A lightweight, low-overhead concurrency library",
|
5
5
|
"homepage": "https://ciorent.netlify.app",
|
6
6
|
"repository": {
|
@@ -18,14 +18,14 @@
|
|
18
18
|
"main": "./index.js",
|
19
19
|
"types": "./index.d.ts",
|
20
20
|
"exports": {
|
21
|
+
"./sliding-queue": "./sliding-queue.js",
|
21
22
|
"./fixed-queue": "./fixed-queue.js",
|
22
23
|
"./fiber": "./fiber.js",
|
23
|
-
"
|
24
|
-
"./semaphore": "./semaphore.js",
|
24
|
+
".": "./index.js",
|
25
25
|
"./dropping-queue": "./dropping-queue.js",
|
26
26
|
"./channel": "./channel.js",
|
27
|
-
".": "./index.js",
|
28
27
|
"./topic": "./topic.js",
|
28
|
+
"./semaphore": "./semaphore.js",
|
29
29
|
"./latch": "./latch.js"
|
30
30
|
}
|
31
31
|
}
|