xref: /aosp_15_r20/external/perfetto/ui/src/base/disposable_stack.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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