Skip to content

Commit

Permalink
fix: prevent adding resource after aggregate disposal
Browse files Browse the repository at this point in the history
  • Loading branch information
mhofman committed Nov 1, 2021
1 parent ee337f7 commit 616025d
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 5 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export const AsyncDisposable: AggregateAsyncDisposableConstructor;

### `using` helper: add resources for tracking

The `AggregateDisposable` and `AggregateAsyncDisposable` objects expose a `using` helper on their instance which can be used to add resources for tracking after construction of the aggregate object. The `using` helper can be detached from the aggregate object (it's bound at construction). It passes through its value for chaining, or assignment at acquisition time. `using` accepts a dispose callback function as an optional argument. This can be used to implement disposal for values which do not implement the disposables interfaces. In that case the value is passed as `this` context to the dispose callback
The `AggregateDisposable` and `AggregateAsyncDisposable` objects expose a `using` helper on their instance which can be used to add resources for tracking after construction of the aggregate object. The `using` helper can be detached from the aggregate object (it's bound at construction). Calling `using` after the aggregate has been disposed throws an error. It passes through its value for chaining, or assignment at acquisition time. `using` accepts a dispose callback function as an optional argument. This can be used to implement disposal for values which do not implement the disposables interfaces. In that case the value is passed as `this` context to the dispose callback

#### Aggregate `Disposable`

Expand Down Expand Up @@ -239,8 +239,6 @@ interface AggregateAsyncDisposableUsing {
}
```

The `Disposable`'s `using` helper function can be used to track any _disposable like_ resource. It captures the _disposable_ and its dispose method, or the dispose callback, then passes through the value. Additionally `using` can be called with an `onDispose` callback as second argument, which will be called with the value as `this` context. When the aggregate object is disposed of, the tracked resources are disposed of in reverse order to which they were added.

The `AsyncDisposable`'s `using` helper function can be used to track any _disposable_ or _async disposable like_ resource. It captures the _disposable_ and its dispose method, the _async disposable_ and its async dispose method, or the async dispose callback, then passes through the value. Additionally `using` can be called with an `onDispose` async callback as second argument, which will be called with the value as `this` context. When the aggregate async object is disposed of, the tracked resources are disposed of in reverse order to which they were added.

The disposal of an _async disposable like_ resource is awaited before moving to the next resource. The disposal of a _disposable_ resource is not awaited. The aggregate disposal step is always awaited even if all tracked resources are _disposable_ which are disposed of synchronously.
Expand Down
18 changes: 17 additions & 1 deletion async-disposable.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,12 +346,28 @@ export const AsyncDisposable = /** @type {DisposableConstructor} */ (
* @param {DisposeMethod} [onDispose]
*/
using(value, onDispose) {
const stack = this.#resourceStack;
const isDisposed = this.#state === "disposed";

const stack = !isDisposed ? this.#resourceStack : [];

typeof onDispose === "function"
? addDisposable(onDispose, value, stack)
: addDisposable(value, value, stack);

if (isDisposed) {
/** @type {unknown} */
let err = new TypeError(
"Can't add resource, AsyncDisposable has already been disposed"
);
try {
// For an "async" resource this will result in an unhandled rejection
disposeRecord(/** @type {DisposableResourceRecord} */ (stack.pop()));
} catch (disposeError) {
err = mergeCause(disposeError, err);
}
throw err;
}

return value;
}

Expand Down
17 changes: 16 additions & 1 deletion disposable.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,12 +316,27 @@ export const Disposable = /** @type {DisposableConstructor} */ (
* @param {DisposeMethod} [onDispose]
*/
using(value, onDispose) {
const stack = this.#resourceStack;
const isDisposed = this.#state === "disposed";

const stack = !isDisposed ? this.#resourceStack : [];

typeof onDispose === "function"
? addDisposable(onDispose, value, stack)
: addDisposable(value, value, stack);

if (isDisposed) {
/** @type {unknown} */
let err = new TypeError(
"Can't add resource, Disposable has already been disposed"
);
try {
disposeRecord(/** @type {DisposableResourceRecord} */ (stack.pop()));
} catch (disposeError) {
err = mergeCause(disposeError, err);
}
throw err;
}

return value;
}

Expand Down

0 comments on commit 616025d

Please sign in to comment.