xref: /aosp_15_r20/external/perfetto/ui/src/base/async_limiter.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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 {Deferred, defer} from './deferred';
16
17type Callback = () => Promise<void>;
18
19interface Task {
20  deferred: Deferred<void>;
21  work: Callback;
22}
23
24/**
25 * A tiny task queue management utility that ensures async tasks are not
26 * executed concurrently.
27 *
28 * If a task is run while a previous one is still running, it is enqueued and
29 * run after the first task completes.
30 *
31 * If multiple tasks are enqueued, only the latest task is run.
32 */
33export class AsyncLimiter {
34  private readonly taskQueue: Task[] = [];
35  private isRunning: boolean = false;
36
37  /**
38   * Schedule a task to be run.
39   *
40   * @param work An async function to schedule.
41   * @returns A promise that resolves when either the task has finished
42   * executing, or after the task has silently been discarded because a newer
43   * task was scheduled.
44   */
45  schedule(work: Callback): Promise<void> {
46    const deferred = defer<void>();
47    this.taskQueue.push({work, deferred});
48
49    if (!this.isRunning) {
50      this.isRunning = true;
51      this.runTaskQueue().finally(() => (this.isRunning = false));
52    }
53
54    return deferred;
55  }
56
57  private async runTaskQueue(): Promise<void> {
58    let task: Task | undefined;
59
60    while ((task = this.taskQueue.shift())) {
61      if (this.taskQueue.length > 0) {
62        task.deferred.resolve();
63      } else {
64        try {
65          await task.work();
66          task.deferred.resolve();
67        } catch (e) {
68          task.deferred.reject(e);
69        }
70      }
71    }
72  }
73}
74