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