Skip to content

Commit

Permalink
Merge pull request #62 from jokester/revise-concurrency
Browse files Browse the repository at this point in the history
ResourcePool: expose `using`-capable borrow()
  • Loading branch information
jokester authored Apr 6, 2024
2 parents 13713cd + f85760a commit 672d5ba
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 22 deletions.
18 changes: 12 additions & 6 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
module.exports = {
preset: 'ts-jest',
preset: 'ts-jest/presets/js-with-ts',
roots: ['src'],
transformIgnorePatterns: ['<rootDir>/node_modules/.*\\.js', '<rootDir>/build/.*\\.js'],
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: {
target: 'es2022',
},
},
],
},
testMatch: ['**/__test__/*\\.(ts|js|tsx|jsx)', '**/*\\.(spec|test)\\.(ts|js|tsx|jsx)'],
collectCoverageFrom: ['src/**/*.(ts|tsx)', '!build/', '!**/node_modules', '!/coverage'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
coverageReporters: ['json', 'lcov', 'text', 'html'],
globals: {
'ts-jest': {
isolatedModules: true,
},
},
setupFiles: ['core-js'],
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@types/node": "^18",
"@types/react": "latest",
"@types/react-dom": "latest",
"core-js": "^3.36.1",
"eventemitter3": "^4",
"fp-ts": "^2",
"gts": "^5.2.0",
Expand Down
3 changes: 2 additions & 1 deletion src/concurrency/lazy-thenable.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ describe(lazyThenable, () => {
await wait(0);
expect(called).toEqual(1);
expect(await converted).toEqual(1);
expect(await lazy1).toEqual(1);
expect(called).toEqual(1);
});

it('run actual action at most once', async () => {
let called = 0;
const lazy2 = lazyThenable(async () => ++called);
const lazy2 = lazyThenable(() => ++called);

expect(await lazy2).toEqual(1);
for (let i = 0; i < 10; i++) {
Expand Down
13 changes: 9 additions & 4 deletions src/concurrency/lazy-thenable.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
export function lazyThenable<T>(action: () => PromiseLike<T>): PromiseLike<T> {
let r: null | Promise<T> = null;
/**
* create a lazy PromiseLike to run {@name io} at most once and only after being awaited
* @param io
* @return
*/
export function lazyThenable<T>(io: () => T): PromiseLike<Awaited<T>> {
let r: null | Promise<Awaited<T>> = null;
return {
then<TResult1, TResult2 = never>(
onfulfilled?: ((value: T) => PromiseLike<TResult1> | TResult1) | undefined | null,
onfulfilled?: ((value: Awaited<T>) => PromiseLike<TResult1> | TResult1) | undefined | null,
onrejected?: ((reason: any) => PromiseLike<TResult2> | TResult2) | undefined | null,
): PromiseLike<TResult1 | TResult2> {
return (r ??= Promise.resolve(action())).then(onfulfilled, onrejected);
return (r ??= Promise.resolve(io())).then(onfulfilled, onrejected);
},
};
}
8 changes: 8 additions & 0 deletions src/concurrency/lease.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* An acquired lock or resource
*/
export interface Lease<T> {
value: T;
dispose(): PromiseLike<void>;
[Symbol.asyncDispose](): PromiseLike<void>;
}
Empty file added src/concurrency/lockable.ts
Empty file.
39 changes: 29 additions & 10 deletions src/concurrency/resource-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* - NOT supported: replace / refresh / timeout of tasks
*/
import { wait } from './timing';
import { Lease } from './lease';
import { lazyThenable } from './lazy-thenable';

export class ResourcePool<T> {
// can be used as a mutex
Expand Down Expand Up @@ -37,17 +39,12 @@ export class ResourcePool<T> {
return this.consumers.length;
}

async use<R>(task: (res: T) => R): Promise<R> {
const r = await this.borrow();
try {
return await task(r);
} finally {
this.resources.push(r);
this.balance();
}
async use<R>(task: (res: T) => R): Promise<Awaited<R>> {
await using lease = await this.borrow();
return /* must not omit 'await' here */ await task(lease.value);
}

tryUse<R>(task: (res: T | null) => R): R | Promise<R> {
tryUse<R>(task: (res: T | null) => R): R | Promise<Awaited<R>> {
if (/** some resource is immediately available */ this.freeCount > 0) {
return this.use(task);
} else {
Expand Down Expand Up @@ -88,13 +85,35 @@ export class ResourcePool<T> {
}
}

private borrow(): Promise<T> {
async borrow(): Promise<Lease<T>> {
// TODO: implement timeout
const v = await this._borrow();

const _return = lazyThenable(() => this._return(v));

return {
value: v,
async dispose(): Promise<void> {
return _return;

Check warning on line 97 in src/concurrency/resource-pool.ts

View check run for this annotation

Codecov / codecov/patch

src/concurrency/resource-pool.ts#L96-L97

Added lines #L96 - L97 were not covered by tests
},
[Symbol.asyncDispose]() {
return _return;
},
};
}

private _borrow(): Promise<T> {
return new Promise<T>((f) => {
this.consumers.push(f);
this.balance();
});
}

private _return(value: T): void {
this.resources.push(value);
this.balance();
}

private balance(): void {
while (this.resources.length && this.consumers.length) {
const r = this.resources.shift()!;

Check warning on line 119 in src/concurrency/resource-pool.ts

View workflow job for this annotation

GitHub Actions / check

Forbidden non-null assertion
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"jsx": "preserve",
"module": "commonjs",
"moduleResolution": "node",
"target": "es2021",
"target": "es2022",
"skipLibCheck": true,
"rootDir": "src",
"outDir": "lib"
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,11 @@ convert-source-map@^2.0.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==

core-js@^3.36.1:
version "3.36.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.36.1.tgz#c97a7160ebd00b2de19e62f4bbd3406ab720e578"
integrity sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==

create-jest@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320"
Expand Down

0 comments on commit 672d5ba

Please sign in to comment.