@effectionx/task-buffer 1.0.2
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 +33 -0
- package/esm/mod.d.ts +2 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +1 -0
- package/esm/package.json +3 -0
- package/esm/task-buffer.d.ts +46 -0
- package/esm/task-buffer.d.ts.map +1 -0
- package/esm/task-buffer.js +82 -0
- package/package.json +29 -0
- package/script/mod.d.ts +2 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +5 -0
- package/script/package.json +3 -0
- package/script/task-buffer.d.ts +46 -0
- package/script/task-buffer.d.ts.map +1 -0
- package/script/task-buffer.js +85 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Task Buffer
|
|
2
|
+
|
|
3
|
+
Manages concurrent task execution by enforcing a maximum limit on simultaneously
|
|
4
|
+
active operations.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
When this limit is reached, the `TaskBuffer` automatically queues additional
|
|
9
|
+
spawn requests and processes them in order as capacity becomes available. This
|
|
10
|
+
prevents resource overload while ensuring all tasks are eventually executed.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { run, sleep } from "effection";
|
|
14
|
+
import { useTaskBuffer } from "@effectionx/task-buffer";
|
|
15
|
+
|
|
16
|
+
await run(function* () {
|
|
17
|
+
// Create a task buffer with a maximum of 2 concurrent tasks
|
|
18
|
+
const buffer = yield* useTaskBuffer(2);
|
|
19
|
+
|
|
20
|
+
// These tasks will execute immediately since they're within the limit
|
|
21
|
+
yield* buffer.spawn(() => sleep(10));
|
|
22
|
+
yield* buffer.spawn(() => sleep(10));
|
|
23
|
+
|
|
24
|
+
// This task will be queued until one of the running tasks completes
|
|
25
|
+
yield* buffer.spawn(() => sleep(10));
|
|
26
|
+
|
|
27
|
+
// Wait for this specific task to complete
|
|
28
|
+
yield* yield* buffer.spawn(() => sleep(10));
|
|
29
|
+
|
|
30
|
+
// Wait for all spawned tasks to complete
|
|
31
|
+
yield* buffer;
|
|
32
|
+
});
|
|
33
|
+
```
|
package/esm/mod.d.ts
ADDED
package/esm/mod.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
package/esm/mod.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useTaskBuffer } from "./task-buffer.js";
|
package/esm/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Operation, type Task } from "effection";
|
|
2
|
+
/**
|
|
3
|
+
* Spawn operations, but only allow a certain number to be active at a
|
|
4
|
+
* given time. Once the `TaskBuffer` becomes full, it will queue up spawn
|
|
5
|
+
* operations until room becomes available
|
|
6
|
+
*/
|
|
7
|
+
export interface TaskBuffer extends Operation<void> {
|
|
8
|
+
/**
|
|
9
|
+
* Spawn `op` in the task buffer when there is room available. If
|
|
10
|
+
* there is room, then this operation will complete immediately.
|
|
11
|
+
* Otherwise, it will return once there is room in the buffer and
|
|
12
|
+
* the task is successfully spawned.
|
|
13
|
+
* `spawn()` operation will not return until the task has actually
|
|
14
|
+
* been spawned.
|
|
15
|
+
*
|
|
16
|
+
* @param op - the operation to spawn in the buffer.
|
|
17
|
+
* @returns the spawned task.
|
|
18
|
+
*/
|
|
19
|
+
spawn<T>(op: () => Operation<T>): Operation<Operation<Task<T>>>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a new `TaskBuffer` attached to the current scope. It will
|
|
23
|
+
* not allow its number of active tasks to exceed `max`.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { run, sleep } from "effection";
|
|
27
|
+
* import { useTaskBuffer } from "@effectionx/task-buffer";
|
|
28
|
+
*
|
|
29
|
+
* await run(function*() {
|
|
30
|
+
* const buffer = yield* useTaskBuffer(2);
|
|
31
|
+
*
|
|
32
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
33
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
34
|
+
* // the next task won't execute until the above two tasks are completed
|
|
35
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
36
|
+
*
|
|
37
|
+
* // will wait for all tasks to be complete
|
|
38
|
+
* yield* buffer;
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @param max - the maximum number of concurrent tasks.
|
|
43
|
+
* @returns the new task buffer.
|
|
44
|
+
*/
|
|
45
|
+
export declare function useTaskBuffer(max: number): Operation<TaskBuffer>;
|
|
46
|
+
//# sourceMappingURL=task-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-buffer.d.ts","sourceRoot":"","sources":["../src/task-buffer.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EAMd,KAAK,IAAI,EAGV,MAAM,WAAW,CAAC;AAEnB;;;;GAIG;AACH,MAAM,WAAW,UAAW,SAAQ,SAAS,CAAC,IAAI,CAAC;IACjD;;;;;;;;;;OAUG;IACH,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACjE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,CAuDhE"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createChannel, Err, Ok, resource, spawn, useScope, withResolvers, } from "effection";
|
|
2
|
+
/**
|
|
3
|
+
* Create a new `TaskBuffer` attached to the current scope. It will
|
|
4
|
+
* not allow its number of active tasks to exceed `max`.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { run, sleep } from "effection";
|
|
8
|
+
* import { useTaskBuffer } from "@effectionx/task-buffer";
|
|
9
|
+
*
|
|
10
|
+
* await run(function*() {
|
|
11
|
+
* const buffer = yield* useTaskBuffer(2);
|
|
12
|
+
*
|
|
13
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
14
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
15
|
+
* // the next task won't execute until the above two tasks are completed
|
|
16
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
17
|
+
*
|
|
18
|
+
* // will wait for all tasks to be complete
|
|
19
|
+
* yield* buffer;
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @param max - the maximum number of concurrent tasks.
|
|
24
|
+
* @returns the new task buffer.
|
|
25
|
+
*/
|
|
26
|
+
export function useTaskBuffer(max) {
|
|
27
|
+
return resource(function* (provide) {
|
|
28
|
+
let input = createChannel();
|
|
29
|
+
let output = createChannel();
|
|
30
|
+
let buffer = new Set();
|
|
31
|
+
let scope = yield* useScope();
|
|
32
|
+
let requests = [];
|
|
33
|
+
yield* spawn(function* () {
|
|
34
|
+
while (true) {
|
|
35
|
+
if (requests.length === 0) {
|
|
36
|
+
yield* next(input);
|
|
37
|
+
}
|
|
38
|
+
else if (buffer.size < max) {
|
|
39
|
+
let request = requests.pop();
|
|
40
|
+
let task = yield* scope.spawn(request.operation);
|
|
41
|
+
buffer.add(task);
|
|
42
|
+
yield* spawn(function* () {
|
|
43
|
+
try {
|
|
44
|
+
let result = Ok(yield* task);
|
|
45
|
+
buffer.delete(task);
|
|
46
|
+
yield* output.send(result);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
buffer.delete(task);
|
|
50
|
+
yield* output.send(Err(error));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
request.resolve(task);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
yield* next(output);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
yield* provide({
|
|
61
|
+
*[Symbol.iterator]() {
|
|
62
|
+
let outputs = yield* output;
|
|
63
|
+
while (buffer.size > 0 || requests.length > 0) {
|
|
64
|
+
yield* outputs.next();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
*spawn(fn) {
|
|
68
|
+
let { operation, resolve } = withResolvers();
|
|
69
|
+
requests.unshift({
|
|
70
|
+
operation: fn,
|
|
71
|
+
resolve: resolve,
|
|
72
|
+
});
|
|
73
|
+
yield* input.send();
|
|
74
|
+
return operation;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function* next(stream) {
|
|
80
|
+
let subscription = yield* stream;
|
|
81
|
+
return yield* subscription.next();
|
|
82
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@effectionx/task-buffer",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"author": "engineering@frontside.com",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/thefrontside/effectionx.git"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/thefrontside/effectionx/issues"
|
|
12
|
+
},
|
|
13
|
+
"main": "./script/mod.js",
|
|
14
|
+
"module": "./esm/mod.js",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./esm/mod.js",
|
|
18
|
+
"require": "./script/mod.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">= 16"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"effection": "4.0.0-alpha.4"
|
|
27
|
+
},
|
|
28
|
+
"_generatedBy": "dnt@dev"
|
|
29
|
+
}
|
package/script/mod.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
package/script/mod.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useTaskBuffer = void 0;
|
|
4
|
+
var task_buffer_js_1 = require("./task-buffer.js");
|
|
5
|
+
Object.defineProperty(exports, "useTaskBuffer", { enumerable: true, get: function () { return task_buffer_js_1.useTaskBuffer; } });
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Operation, type Task } from "effection";
|
|
2
|
+
/**
|
|
3
|
+
* Spawn operations, but only allow a certain number to be active at a
|
|
4
|
+
* given time. Once the `TaskBuffer` becomes full, it will queue up spawn
|
|
5
|
+
* operations until room becomes available
|
|
6
|
+
*/
|
|
7
|
+
export interface TaskBuffer extends Operation<void> {
|
|
8
|
+
/**
|
|
9
|
+
* Spawn `op` in the task buffer when there is room available. If
|
|
10
|
+
* there is room, then this operation will complete immediately.
|
|
11
|
+
* Otherwise, it will return once there is room in the buffer and
|
|
12
|
+
* the task is successfully spawned.
|
|
13
|
+
* `spawn()` operation will not return until the task has actually
|
|
14
|
+
* been spawned.
|
|
15
|
+
*
|
|
16
|
+
* @param op - the operation to spawn in the buffer.
|
|
17
|
+
* @returns the spawned task.
|
|
18
|
+
*/
|
|
19
|
+
spawn<T>(op: () => Operation<T>): Operation<Operation<Task<T>>>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a new `TaskBuffer` attached to the current scope. It will
|
|
23
|
+
* not allow its number of active tasks to exceed `max`.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { run, sleep } from "effection";
|
|
27
|
+
* import { useTaskBuffer } from "@effectionx/task-buffer";
|
|
28
|
+
*
|
|
29
|
+
* await run(function*() {
|
|
30
|
+
* const buffer = yield* useTaskBuffer(2);
|
|
31
|
+
*
|
|
32
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
33
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
34
|
+
* // the next task won't execute until the above two tasks are completed
|
|
35
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
36
|
+
*
|
|
37
|
+
* // will wait for all tasks to be complete
|
|
38
|
+
* yield* buffer;
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @param max - the maximum number of concurrent tasks.
|
|
43
|
+
* @returns the new task buffer.
|
|
44
|
+
*/
|
|
45
|
+
export declare function useTaskBuffer(max: number): Operation<TaskBuffer>;
|
|
46
|
+
//# sourceMappingURL=task-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-buffer.d.ts","sourceRoot":"","sources":["../src/task-buffer.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EAMd,KAAK,IAAI,EAGV,MAAM,WAAW,CAAC;AAEnB;;;;GAIG;AACH,MAAM,WAAW,UAAW,SAAQ,SAAS,CAAC,IAAI,CAAC;IACjD;;;;;;;;;;OAUG;IACH,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACjE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,CAuDhE"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useTaskBuffer = useTaskBuffer;
|
|
4
|
+
const effection_1 = require("effection");
|
|
5
|
+
/**
|
|
6
|
+
* Create a new `TaskBuffer` attached to the current scope. It will
|
|
7
|
+
* not allow its number of active tasks to exceed `max`.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { run, sleep } from "effection";
|
|
11
|
+
* import { useTaskBuffer } from "@effectionx/task-buffer";
|
|
12
|
+
*
|
|
13
|
+
* await run(function*() {
|
|
14
|
+
* const buffer = yield* useTaskBuffer(2);
|
|
15
|
+
*
|
|
16
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
17
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
18
|
+
* // the next task won't execute until the above two tasks are completed
|
|
19
|
+
* yield* buffer.spawn(() => sleep(10));
|
|
20
|
+
*
|
|
21
|
+
* // will wait for all tasks to be complete
|
|
22
|
+
* yield* buffer;
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @param max - the maximum number of concurrent tasks.
|
|
27
|
+
* @returns the new task buffer.
|
|
28
|
+
*/
|
|
29
|
+
function useTaskBuffer(max) {
|
|
30
|
+
return (0, effection_1.resource)(function* (provide) {
|
|
31
|
+
let input = (0, effection_1.createChannel)();
|
|
32
|
+
let output = (0, effection_1.createChannel)();
|
|
33
|
+
let buffer = new Set();
|
|
34
|
+
let scope = yield* (0, effection_1.useScope)();
|
|
35
|
+
let requests = [];
|
|
36
|
+
yield* (0, effection_1.spawn)(function* () {
|
|
37
|
+
while (true) {
|
|
38
|
+
if (requests.length === 0) {
|
|
39
|
+
yield* next(input);
|
|
40
|
+
}
|
|
41
|
+
else if (buffer.size < max) {
|
|
42
|
+
let request = requests.pop();
|
|
43
|
+
let task = yield* scope.spawn(request.operation);
|
|
44
|
+
buffer.add(task);
|
|
45
|
+
yield* (0, effection_1.spawn)(function* () {
|
|
46
|
+
try {
|
|
47
|
+
let result = (0, effection_1.Ok)(yield* task);
|
|
48
|
+
buffer.delete(task);
|
|
49
|
+
yield* output.send(result);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
buffer.delete(task);
|
|
53
|
+
yield* output.send((0, effection_1.Err)(error));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
request.resolve(task);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
yield* next(output);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
yield* provide({
|
|
64
|
+
*[Symbol.iterator]() {
|
|
65
|
+
let outputs = yield* output;
|
|
66
|
+
while (buffer.size > 0 || requests.length > 0) {
|
|
67
|
+
yield* outputs.next();
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
*spawn(fn) {
|
|
71
|
+
let { operation, resolve } = (0, effection_1.withResolvers)();
|
|
72
|
+
requests.unshift({
|
|
73
|
+
operation: fn,
|
|
74
|
+
resolve: resolve,
|
|
75
|
+
});
|
|
76
|
+
yield* input.send();
|
|
77
|
+
return operation;
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function* next(stream) {
|
|
83
|
+
let subscription = yield* stream;
|
|
84
|
+
return yield* subscription.next();
|
|
85
|
+
}
|