1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2024 The Android Open Source Project 2*6dbdd20aSAndroid Build Coastguard Worker// 3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*6dbdd20aSAndroid Build Coastguard Worker// 7*6dbdd20aSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*6dbdd20aSAndroid Build Coastguard Worker// 9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License. 14*6dbdd20aSAndroid Build Coastguard Worker 15*6dbdd20aSAndroid Build Coastguard Worker/** 16*6dbdd20aSAndroid Build Coastguard Worker * Implementations of DisposableStack and AsyncDisposableStack. 17*6dbdd20aSAndroid Build Coastguard Worker * 18*6dbdd20aSAndroid Build Coastguard Worker * These are defined in the "ECMAScript Explicit Resource Management" proposal 19*6dbdd20aSAndroid Build Coastguard Worker * which is currently at stage 3, which means "No changes to the proposal are 20*6dbdd20aSAndroid Build Coastguard Worker * expected, but some necessary changes may still occur due to web 21*6dbdd20aSAndroid Build Coastguard Worker * incompatibilities or feedback from production-grade implementations." 22*6dbdd20aSAndroid Build Coastguard Worker * 23*6dbdd20aSAndroid Build Coastguard Worker * Reference 24*6dbdd20aSAndroid Build Coastguard Worker * - https://github.com/tc39/proposal-explicit-resource-management 25*6dbdd20aSAndroid Build Coastguard Worker * - https://tc39.es/process-document/ 26*6dbdd20aSAndroid Build Coastguard Worker * 27*6dbdd20aSAndroid Build Coastguard Worker * These classes are purposely not polyfilled to avoid confusion and aid 28*6dbdd20aSAndroid Build Coastguard Worker * debug-ability and traceability. 29*6dbdd20aSAndroid Build Coastguard Worker */ 30*6dbdd20aSAndroid Build Coastguard Worker 31*6dbdd20aSAndroid Build Coastguard Workerexport class DisposableStack implements Disposable { 32*6dbdd20aSAndroid Build Coastguard Worker private readonly resources: Disposable[]; 33*6dbdd20aSAndroid Build Coastguard Worker private isDisposed = false; 34*6dbdd20aSAndroid Build Coastguard Worker 35*6dbdd20aSAndroid Build Coastguard Worker constructor() { 36*6dbdd20aSAndroid Build Coastguard Worker this.resources = []; 37*6dbdd20aSAndroid Build Coastguard Worker } 38*6dbdd20aSAndroid Build Coastguard Worker 39*6dbdd20aSAndroid Build Coastguard Worker use<T extends Disposable | null | undefined>(res: T): T { 40*6dbdd20aSAndroid Build Coastguard Worker if (res == null) return res; 41*6dbdd20aSAndroid Build Coastguard Worker this.resources.push(res); 42*6dbdd20aSAndroid Build Coastguard Worker return res; 43*6dbdd20aSAndroid Build Coastguard Worker } 44*6dbdd20aSAndroid Build Coastguard Worker 45*6dbdd20aSAndroid Build Coastguard Worker defer(onDispose: () => void) { 46*6dbdd20aSAndroid Build Coastguard Worker this.resources.push({ 47*6dbdd20aSAndroid Build Coastguard Worker [Symbol.dispose]: onDispose, 48*6dbdd20aSAndroid Build Coastguard Worker }); 49*6dbdd20aSAndroid Build Coastguard Worker } 50*6dbdd20aSAndroid Build Coastguard Worker 51*6dbdd20aSAndroid Build Coastguard Worker // TODO(stevegolton): Handle error suppression properly 52*6dbdd20aSAndroid Build Coastguard Worker // https://github.com/tc39/proposal-explicit-resource-management?tab=readme-ov-file#aggregation 53*6dbdd20aSAndroid Build Coastguard Worker [Symbol.dispose](): void { 54*6dbdd20aSAndroid Build Coastguard Worker this.isDisposed = true; 55*6dbdd20aSAndroid Build Coastguard Worker while (true) { 56*6dbdd20aSAndroid Build Coastguard Worker const res = this.resources.pop(); 57*6dbdd20aSAndroid Build Coastguard Worker if (res === undefined) { 58*6dbdd20aSAndroid Build Coastguard Worker break; 59*6dbdd20aSAndroid Build Coastguard Worker } 60*6dbdd20aSAndroid Build Coastguard Worker res[Symbol.dispose](); 61*6dbdd20aSAndroid Build Coastguard Worker } 62*6dbdd20aSAndroid Build Coastguard Worker } 63*6dbdd20aSAndroid Build Coastguard Worker 64*6dbdd20aSAndroid Build Coastguard Worker dispose(): void { 65*6dbdd20aSAndroid Build Coastguard Worker this[Symbol.dispose](); 66*6dbdd20aSAndroid Build Coastguard Worker } 67*6dbdd20aSAndroid Build Coastguard Worker 68*6dbdd20aSAndroid Build Coastguard Worker adopt<T>(value: T, onDispose: (value: T) => void): T { 69*6dbdd20aSAndroid Build Coastguard Worker this.resources.push({ 70*6dbdd20aSAndroid Build Coastguard Worker [Symbol.dispose]: () => onDispose(value), 71*6dbdd20aSAndroid Build Coastguard Worker }); 72*6dbdd20aSAndroid Build Coastguard Worker return value; 73*6dbdd20aSAndroid Build Coastguard Worker } 74*6dbdd20aSAndroid Build Coastguard Worker 75*6dbdd20aSAndroid Build Coastguard Worker move(): DisposableStack { 76*6dbdd20aSAndroid Build Coastguard Worker const other = new DisposableStack(); 77*6dbdd20aSAndroid Build Coastguard Worker for (const res of this.resources) { 78*6dbdd20aSAndroid Build Coastguard Worker other.resources.push(res); 79*6dbdd20aSAndroid Build Coastguard Worker } 80*6dbdd20aSAndroid Build Coastguard Worker this.resources.length = 0; 81*6dbdd20aSAndroid Build Coastguard Worker return other; 82*6dbdd20aSAndroid Build Coastguard Worker } 83*6dbdd20aSAndroid Build Coastguard Worker 84*6dbdd20aSAndroid Build Coastguard Worker readonly [Symbol.toStringTag]: string = 'DisposableStack'; 85*6dbdd20aSAndroid Build Coastguard Worker 86*6dbdd20aSAndroid Build Coastguard Worker get disposed(): boolean { 87*6dbdd20aSAndroid Build Coastguard Worker return this.isDisposed; 88*6dbdd20aSAndroid Build Coastguard Worker } 89*6dbdd20aSAndroid Build Coastguard Worker} 90*6dbdd20aSAndroid Build Coastguard Worker 91*6dbdd20aSAndroid Build Coastguard Workerexport class AsyncDisposableStack implements AsyncDisposable { 92*6dbdd20aSAndroid Build Coastguard Worker private readonly resources: AsyncDisposable[]; 93*6dbdd20aSAndroid Build Coastguard Worker private isDisposed = false; 94*6dbdd20aSAndroid Build Coastguard Worker 95*6dbdd20aSAndroid Build Coastguard Worker constructor() { 96*6dbdd20aSAndroid Build Coastguard Worker this.resources = []; 97*6dbdd20aSAndroid Build Coastguard Worker } 98*6dbdd20aSAndroid Build Coastguard Worker 99*6dbdd20aSAndroid Build Coastguard Worker use<T extends Disposable | AsyncDisposable | null | undefined>(res: T): T { 100*6dbdd20aSAndroid Build Coastguard Worker if (res == null) return res; 101*6dbdd20aSAndroid Build Coastguard Worker 102*6dbdd20aSAndroid Build Coastguard Worker if (Symbol.asyncDispose in res) { 103*6dbdd20aSAndroid Build Coastguard Worker this.resources.push(res); 104*6dbdd20aSAndroid Build Coastguard Worker } else if (Symbol.dispose in res) { 105*6dbdd20aSAndroid Build Coastguard Worker this.resources.push({ 106*6dbdd20aSAndroid Build Coastguard Worker [Symbol.asyncDispose]: async () => { 107*6dbdd20aSAndroid Build Coastguard Worker res[Symbol.dispose](); 108*6dbdd20aSAndroid Build Coastguard Worker }, 109*6dbdd20aSAndroid Build Coastguard Worker }); 110*6dbdd20aSAndroid Build Coastguard Worker } 111*6dbdd20aSAndroid Build Coastguard Worker 112*6dbdd20aSAndroid Build Coastguard Worker return res; 113*6dbdd20aSAndroid Build Coastguard Worker } 114*6dbdd20aSAndroid Build Coastguard Worker 115*6dbdd20aSAndroid Build Coastguard Worker defer(onDispose: () => Promise<void>) { 116*6dbdd20aSAndroid Build Coastguard Worker this.resources.push({ 117*6dbdd20aSAndroid Build Coastguard Worker [Symbol.asyncDispose]: onDispose, 118*6dbdd20aSAndroid Build Coastguard Worker }); 119*6dbdd20aSAndroid Build Coastguard Worker } 120*6dbdd20aSAndroid Build Coastguard Worker 121*6dbdd20aSAndroid Build Coastguard Worker // TODO(stevegolton): Handle error suppression properly 122*6dbdd20aSAndroid Build Coastguard Worker // https://github.com/tc39/proposal-explicit-resource-management?tab=readme-ov-file#aggregation 123*6dbdd20aSAndroid Build Coastguard Worker async [Symbol.asyncDispose](): Promise<void> { 124*6dbdd20aSAndroid Build Coastguard Worker this.isDisposed = true; 125*6dbdd20aSAndroid Build Coastguard Worker while (true) { 126*6dbdd20aSAndroid Build Coastguard Worker const res = this.resources.pop(); 127*6dbdd20aSAndroid Build Coastguard Worker if (res === undefined) { 128*6dbdd20aSAndroid Build Coastguard Worker break; 129*6dbdd20aSAndroid Build Coastguard Worker } 130*6dbdd20aSAndroid Build Coastguard Worker const timerId = setTimeout(() => { 131*6dbdd20aSAndroid Build Coastguard Worker throw new Error( 132*6dbdd20aSAndroid Build Coastguard Worker 'asyncDispose timed out. This might be due to a Disposable ' + 133*6dbdd20aSAndroid Build Coastguard Worker 'resource trying to issue cleanup queries on trace unload, ' + 134*6dbdd20aSAndroid Build Coastguard Worker 'while the Wasm module was already destroyed ', 135*6dbdd20aSAndroid Build Coastguard Worker ); 136*6dbdd20aSAndroid Build Coastguard Worker }, 10_000); 137*6dbdd20aSAndroid Build Coastguard Worker await res[Symbol.asyncDispose](); 138*6dbdd20aSAndroid Build Coastguard Worker clearTimeout(timerId); 139*6dbdd20aSAndroid Build Coastguard Worker } 140*6dbdd20aSAndroid Build Coastguard Worker } 141*6dbdd20aSAndroid Build Coastguard Worker 142*6dbdd20aSAndroid Build Coastguard Worker asyncDispose(): Promise<void> { 143*6dbdd20aSAndroid Build Coastguard Worker return this[Symbol.asyncDispose](); 144*6dbdd20aSAndroid Build Coastguard Worker } 145*6dbdd20aSAndroid Build Coastguard Worker 146*6dbdd20aSAndroid Build Coastguard Worker adopt<T>(value: T, onDispose: (value: T) => Promise<void>): T { 147*6dbdd20aSAndroid Build Coastguard Worker this.resources.push({ 148*6dbdd20aSAndroid Build Coastguard Worker [Symbol.asyncDispose]: async () => onDispose(value), 149*6dbdd20aSAndroid Build Coastguard Worker }); 150*6dbdd20aSAndroid Build Coastguard Worker return value; 151*6dbdd20aSAndroid Build Coastguard Worker } 152*6dbdd20aSAndroid Build Coastguard Worker 153*6dbdd20aSAndroid Build Coastguard Worker move(): AsyncDisposableStack { 154*6dbdd20aSAndroid Build Coastguard Worker const other = new AsyncDisposableStack(); 155*6dbdd20aSAndroid Build Coastguard Worker for (const res of this.resources) { 156*6dbdd20aSAndroid Build Coastguard Worker other.resources.push(res); 157*6dbdd20aSAndroid Build Coastguard Worker } 158*6dbdd20aSAndroid Build Coastguard Worker this.resources.length = 0; 159*6dbdd20aSAndroid Build Coastguard Worker return other; 160*6dbdd20aSAndroid Build Coastguard Worker } 161*6dbdd20aSAndroid Build Coastguard Worker 162*6dbdd20aSAndroid Build Coastguard Worker readonly [Symbol.toStringTag]: string = 'AsyncDisposableStack'; 163*6dbdd20aSAndroid Build Coastguard Worker 164*6dbdd20aSAndroid Build Coastguard Worker get disposed(): boolean { 165*6dbdd20aSAndroid Build Coastguard Worker return this.isDisposed; 166*6dbdd20aSAndroid Build Coastguard Worker } 167*6dbdd20aSAndroid Build Coastguard Worker} 168