1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {assertFalse} from './logging'; 16 17/** 18 * Adds reference counting to an AsyncDisposable. 19 * 20 * Allows you to share a disposable amongst multiple different entities which 21 * may have differing lifecycles, and only have the resource cleared up once all 22 * components have called dispose on it. 23 * 24 * @example 25 * ```ts 26 * { 27 * // Create a shared disposable around an arbitrary disposable resource 28 * await using sharedResource = SharedAsyncDisposable.wrap(resource); 29 * 30 * // Pass a the shared resource somewhere else (notice we don't await here, 31 * // which detaches their lifecycle from ours - i.e. we don't know which task 32 * // will finish first, us or them) 33 * doStuff(sharedResource); 34 * 35 * // Do something with the resource 36 * await sharedResource.get().doStuff(...); 37 * 38 * // Our shard resource is disposed here, but the underlying resource will 39 * // only be disposed once doStuff() is done with it too 40 * } 41 * 42 * // --cut-- 43 * 44 * async function doStuff(shared) { 45 * await using res = shared.clone(); 46 * 47 * // Do stuff with the resource 48 * await res.get().doStuff(...); 49 * 50 * // res is automatically disposed here 51 * } 52 * ``` 53 */ 54export class SharedAsyncDisposable<T extends AsyncDisposable> 55 implements AsyncDisposable 56{ 57 // A shared core which is referenced by al instances of this class used to 58 // store the reference count 59 private readonly sharedCore: {refCount: number}; 60 61 // This is our underlying disposable 62 private readonly disposable: T; 63 64 // Record of whether this instance is disposed (not whether the underlying 65 // instance is disposed) 66 private _isDisposed = false; 67 68 /** 69 * Create a new shared disposable object from an arbitrary disposable. 70 * 71 * @param disposable The disposable object to wrap. 72 * @returns A new SharedAsyncDisposable object. 73 */ 74 static wrap<T extends AsyncDisposable>( 75 disposable: T, 76 ): SharedAsyncDisposable<T> { 77 return new SharedAsyncDisposable(disposable); 78 } 79 80 private constructor(disposable: T, sharedCore?: {refCount: number}) { 81 this.disposable = disposable; 82 if (!sharedCore) { 83 this.sharedCore = {refCount: 1}; 84 } else { 85 this.sharedCore = sharedCore; 86 } 87 } 88 89 /** 90 * Check whether this is disposed. If true, clone() and 91 * [Symbol.asyncDispose]() will return throw. Can be used to check state 92 * before cloning. 93 */ 94 get isDisposed(): boolean { 95 return this._isDisposed; 96 } 97 98 /** 99 * @returns The underlying disposable. 100 */ 101 get(): T { 102 return this.disposable; 103 } 104 105 /** 106 * Create a clone of this object, incrementing the reference count. 107 * 108 * @returns A new shared disposable instance. 109 */ 110 clone(): SharedAsyncDisposable<T> { 111 // Cloning again after dispose indicates invalid usage 112 assertFalse(this._isDisposed); 113 114 this.sharedCore.refCount++; 115 return new SharedAsyncDisposable(this.disposable, this.sharedCore); 116 } 117 118 /** 119 * Dispose of this object, decrementing the reference count. If the reference 120 * count drops to 0, the underlying disposable is disposed. 121 */ 122 async [Symbol.asyncDispose](): Promise<void> { 123 // Disposing multiple times indicates invalid usage 124 assertFalse(this._isDisposed); 125 126 this._isDisposed = true; 127 this.sharedCore.refCount--; 128 129 if (this.sharedCore.refCount === 0) { 130 await this.disposable[Symbol.asyncDispose](); 131 } 132 } 133} 134