Compare commits

...

4 Commits

6 changed files with 79 additions and 42 deletions

View File

@ -1,5 +1,19 @@
# Changelog # Changelog
## 2025-04-25 - 6.2.2 - fix(docs)
Update @push.rocks/tapbundle dependency and refine AsyncExecutionStack documentation examples
- Bump @push.rocks/tapbundle from ^5.0.8 to ^5.5.6 in package.json
- Improve README documentation for AsyncExecutionStack with clearer examples for exclusive and non-exclusive task execution
- Demonstrate usage of concurrency controls in AsyncExecutionStack
## 2025-04-25 - 6.2.1 - fix(AsyncExecutionStack tests)
Refactor AsyncExecutionStack tests: update non-exclusive concurrency assertions and clean up test logic
- Replace 'toBe' with 'toEqual' for active and pending counts to ensure consistency
- Simplify default non-exclusive concurrency test by asserting Infinity is non-finite using toBeFalse
- Adjust test comments and scheduling for clarity in concurrency behavior
## 2025-04-25 - 6.2.0 - feat(AsyncExecutionStack) ## 2025-04-25 - 6.2.0 - feat(AsyncExecutionStack)
Improve non-exclusive task management with concurrency limit controls and enhanced monitoring in AsyncExecutionStack. Improve non-exclusive task management with concurrency limit controls and enhanced monitoring in AsyncExecutionStack.

View File

@ -1,6 +1,6 @@
{ {
"name": "@push.rocks/lik", "name": "@push.rocks/lik",
"version": "6.2.0", "version": "6.2.2",
"private": false, "private": false,
"description": "Provides a collection of lightweight helpers and utilities for Node.js projects.", "description": "Provides a collection of lightweight helpers and utilities for Node.js projects.",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
@ -26,7 +26,7 @@
"@git.zone/tsbundle": "^2.0.15", "@git.zone/tsbundle": "^2.0.15",
"@git.zone/tsrun": "^1.2.44", "@git.zone/tsrun": "^1.2.44",
"@git.zone/tstest": "^1.0.90", "@git.zone/tstest": "^1.0.90",
"@push.rocks/tapbundle": "^5.0.8", "@push.rocks/tapbundle": "^5.5.6",
"@types/node": "^22.13.9" "@types/node": "^22.13.9"
}, },
"dependencies": { "dependencies": {

2
pnpm-lock.yaml generated
View File

@ -46,7 +46,7 @@ importers:
specifier: ^1.0.90 specifier: ^1.0.90
version: 1.0.96(@aws-sdk/credential-providers@3.758.0)(socks@2.8.4)(typescript@5.7.3) version: 1.0.96(@aws-sdk/credential-providers@3.758.0)(socks@2.8.4)(typescript@5.7.3)
'@push.rocks/tapbundle': '@push.rocks/tapbundle':
specifier: ^5.0.8 specifier: ^5.5.6
version: 5.5.6(@aws-sdk/credential-providers@3.758.0)(socks@2.8.4) version: 5.5.6(@aws-sdk/credential-providers@3.758.0)(socks@2.8.4)
'@types/node': '@types/node':
specifier: ^22.13.9 specifier: ^22.13.9

View File

@ -17,22 +17,53 @@ This will download `@push.rocks/lik` and add it to your project's `package.json`
### AsyncExecutionStack ### AsyncExecutionStack
`AsyncExecutionStack` allows for managing asynchronous task execution with exclusive and non-exclusive slots, ensuring effective handling of resource-intensive operations. `AsyncExecutionStack` provides controlled execution of asynchronous tasks in two modes:
- **Exclusive tasks**: run one at a time in insertion order, blocking all other tasks until complete.
- **Non-exclusive tasks**: run in parallel, up to an optional concurrency limit.
Create a stack:
```typescript ```typescript
import { AsyncExecutionStack } from '@push.rocks/lik'; import { AsyncExecutionStack } from '@push.rocks/lik';
const myAsyncStack = new AsyncExecutionStack(); const stack = new AsyncExecutionStack();
```
// Exclusive execution ensures this task doesn't run in parallel with others Exclusive tasks execute sequentially and block subsequent tasks (with an optional timeout):
await myAsyncStack.getExclusiveExecutionSlot(async () => {
// some asynchronous operation
}, 2500);
// Non-exclusive slots can run in parallel ```typescript
myAsyncStack.getNonExclusiveExecutionSlot(async () => { await stack.getExclusiveExecutionSlot(
// another asynchronous operation async () => {
}, 1500); // exclusive work
},
5000 // optional timeout in milliseconds
);
```
Non-exclusive tasks run in parallel up to the concurrency limit (default: unlimited). Each call returns a promise you can await:
```typescript
const p1 = stack.getNonExclusiveExecutionSlot(async () => {
// work 1
}, 2000);
const p2 = stack.getNonExclusiveExecutionSlot(async () => {
// work 2
}, 2000);
await Promise.all([p1, p2]);
```
Control concurrency and inspect status:
```typescript
// Set maximum concurrent non-exclusive tasks (minimum 1)
stack.setNonExclusiveMaxConcurrency(3);
console.log(stack.getNonExclusiveMaxConcurrency()); // 3
console.log(stack.getActiveNonExclusiveCount()); // currently running tasks
console.log(stack.getPendingNonExclusiveCount()); // tasks waiting for slots
``` ```
### FastMap ### FastMap

View File

@ -26,24 +26,11 @@ tap.test('should run in parallel', async (toolsArg) => {
}, 0); }, 0);
}); });
// Test default non-exclusive unlimited concurrency (no cap means all run together) // Test default non-exclusive has no concurrency limit property (Infinity)
tap.test('default non-exclusive unlimited concurrency', async (tools) => { tap.test('default non-exclusive has no concurrency limit', () => {
const stack = new lik.AsyncExecutionStack(); const stack = new lik.AsyncExecutionStack();
// default maxConcurrency should be unlimited (Infinity) // default maxConcurrency is Infinity (not finite)
expect(Number.isFinite(stack.getNonExclusiveMaxConcurrency())).toBe(false); expect(Number.isFinite(stack.getNonExclusiveMaxConcurrency())).toBeFalse();
const activeCounts: number[] = [];
const tasks: Promise<void>[] = [];
for (let i = 0; i < 4; i++) {
tasks.push(
stack.getNonExclusiveExecutionSlot(async () => {
activeCounts.push(stack.getActiveNonExclusiveCount());
await tools.delayFor(20);
})
);
}
await Promise.all(tasks);
const maxActive = Math.max(...activeCounts);
expect(maxActive).toBe(4);
}); });
// Test respecting a non-exclusive concurrency limit // Test respecting a non-exclusive concurrency limit
tap.test('non-exclusive respects maxConcurrency', async (tools) => { tap.test('non-exclusive respects maxConcurrency', async (tools) => {
@ -70,10 +57,10 @@ tap.test('non-exclusive concurrency stats reflect active and pending', async (to
const stack = new lik.AsyncExecutionStack(); const stack = new lik.AsyncExecutionStack();
stack.setNonExclusiveMaxConcurrency(2); stack.setNonExclusiveMaxConcurrency(2);
// initially, no tasks // initially, no tasks
expect(stack.getActiveNonExclusiveCount()).toBe(0); expect(stack.getActiveNonExclusiveCount()).toEqual(0);
expect(stack.getPendingNonExclusiveCount()).toBe(0); expect(stack.getPendingNonExclusiveCount()).toEqual(0);
// enqueue three tasks // enqueue four tasks
const p1 = stack.getNonExclusiveExecutionSlot(async () => { const p1 = stack.getNonExclusiveExecutionSlot(async () => {
await tools.delayFor(30); await tools.delayFor(30);
}); });
@ -83,17 +70,22 @@ tap.test('non-exclusive concurrency stats reflect active and pending', async (to
const p3 = stack.getNonExclusiveExecutionSlot(async () => { const p3 = stack.getNonExclusiveExecutionSlot(async () => {
await tools.delayFor(30); await tools.delayFor(30);
}); });
const p4 = stack.getNonExclusiveExecutionSlot(async () => {
await tools.delayFor(30);
});
// give time for scheduling // wait for first task to finish and scheduling of next batch
await tools.delayFor(10); await p1;
// two should be running, one pending await tools.delayFor(0);
expect(stack.getActiveNonExclusiveCount()).toBe(2); // second batch: two active, one pending (4 tasks, limit=2)
expect(stack.getPendingNonExclusiveCount()).toBe(1); expect(stack.getActiveNonExclusiveCount()).toEqual(2);
expect(stack.getPendingNonExclusiveCount()).toEqual(1);
await Promise.all([p1, p2, p3]); // wait for remaining tasks to complete
await Promise.all([p2, p3, p4]);
// after completion, counts reset // after completion, counts reset
expect(stack.getActiveNonExclusiveCount()).toBe(0); expect(stack.getActiveNonExclusiveCount()).toEqual(0);
expect(stack.getPendingNonExclusiveCount()).toBe(0); expect(stack.getPendingNonExclusiveCount()).toEqual(0);
}); });
export default tap.start(); export default tap.start();

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/lik', name: '@push.rocks/lik',
version: '6.2.0', version: '6.2.2',
description: 'Provides a collection of lightweight helpers and utilities for Node.js projects.' description: 'Provides a collection of lightweight helpers and utilities for Node.js projects.'
} }