blob: a3d2f5416f75160bd4a54e8c4bfe50d437a3b716 [file] [log] [blame]
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {assertFalse} from './logging';
/**
* Adds reference counting to an AsyncDisposable.
*
* Allows you to share a disposable amongst multiple different entities which
* may have differing lifecycles, and only have the resource cleared up once all
* components have called dispose on it.
*
* @example
* ```ts
* {
* // Create a shared disposable around an arbitrary disposable resource
* await using sharedResource = SharedAsyncDisposable.wrap(resource);
*
* // Pass a the shared resource somewhere else (notice we don't await here,
* // which detaches their lifecycle from ours - i.e. we don't know which task
* // will finish first, us or them)
* doStuff(sharedResource);
*
* // Do something with the resource
* await sharedResource.get().doStuff(...);
*
* // Our shard resource is disposed here, but the underlying resource will
* // only be disposed once doStuff() is done with it too
* }
*
* // --cut--
*
* async function doStuff(shared) {
* await using res = shared.clone();
*
* // Do stuff with the resource
* await res.get().doStuff(...);
*
* // res is automatically disposed here
* }
* ```
*/
export class SharedAsyncDisposable<T extends AsyncDisposable>
implements AsyncDisposable
{
// A shared core which is referenced by al instances of this class used to
// store the reference count
private readonly sharedCore: {refCount: number};
// This is our underlying disposable
private readonly disposable: T;
// Record of whether this instance is disposed (not whether the underlying
// instance is disposed)
private _isDisposed = false;
/**
* Create a new shared disposable object from an arbitrary disposable.
*
* @param disposable The disposable object to wrap.
* @returns A new SharedAsyncDisposable object.
*/
static wrap<T extends AsyncDisposable>(
disposable: T,
): SharedAsyncDisposable<T> {
return new SharedAsyncDisposable(disposable);
}
private constructor(disposable: T, sharedCore?: {refCount: number}) {
this.disposable = disposable;
if (!sharedCore) {
this.sharedCore = {refCount: 1};
} else {
this.sharedCore = sharedCore;
}
}
/**
* Check whether this is disposed. If true, clone() and
* [Symbol.asyncDispose]() will return throw. Can be used to check state
* before cloning.
*/
get isDisposed(): boolean {
return this._isDisposed;
}
/**
* @returns The underlying disposable.
*/
get(): T {
return this.disposable;
}
/**
* Create a clone of this object, incrementing the reference count.
*
* @returns A new shared disposable instance.
*/
clone(): SharedAsyncDisposable<T> {
// Cloning again after dispose indicates invalid usage
assertFalse(this._isDisposed);
this.sharedCore.refCount++;
return new SharedAsyncDisposable(this.disposable, this.sharedCore);
}
/**
* Dispose of this object, decrementing the reference count. If the reference
* count drops to 0, the underlying disposable is disposed.
*/
async [Symbol.asyncDispose](): Promise<void> {
// Disposing multiple times indicates invalid usage
assertFalse(this._isDisposed);
this._isDisposed = true;
this.sharedCore.refCount--;
if (this.sharedCore.refCount === 0) {
await this.disposable[Symbol.asyncDispose]();
}
}
}