Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions ui/src/base/async_queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (C) 2024 The Android Open Source Project
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2025?

//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {Deferred, defer} from './deferred';

type Callback<T> = () => Promise<T>;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface Task<T = any> {
deferred: Deferred<T>;
work: Callback<T>;
}

/**
* A tiny task queue management utility that ensures async tasks are not
* executed concurrently.
*
* If a task is run while a previous one is still running, it is enqueued and
* run after the first task completes.
*/
export class AsyncQueue {
private readonly taskQueue: Task[] = [];
private isRunning: boolean = false;

/**
* Schedule a task to be run.
*
* @param work An async function to schedule.
* @returns A promise that resolves when either the task has finished
* executing, or after the task has silently been discarded because a newer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the whole point is not to discard any task?

* task was scheduled.
*/
schedule<T>(work: Callback<T>): Promise<T> {
const deferred = defer<T>();
this.taskQueue.push({work, deferred});

if (!this.isRunning) {
this.isRunning = true;
this.runTaskQueue().finally(() => (this.isRunning = false));
}

return deferred;
}

private async runTaskQueue(): Promise<void> {
let task: Task | undefined;

while ((task = this.taskQueue.shift())) {
try {
const result = await task.work();
task.deferred.resolve(result);
} catch (e) {
task.deferred.reject(e);
}
}
}
}
15 changes: 4 additions & 11 deletions ui/src/core/app_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {AsyncLimiter} from '../base/async_limiter';
import {AsyncQueue} from '../base/async_queue';
import {defer} from '../base/deferred';
import {EvtSource} from '../base/events';
import {assertExists, assertIsInstance, assertTrue} from '../base/logging';
Expand Down Expand Up @@ -103,7 +103,7 @@ export class AppContext {
readonly initArgs: AppInitArgs;
readonly embeddedMode: boolean;
readonly testingMode: boolean;
readonly openTraceAsyncLimiter = new AsyncLimiter();
readonly openTraceAsyncQueue = new AsyncQueue();
readonly settingsManager: SettingsManagerImpl;

// This is normally empty and is injected with extra google-internal packages
Expand Down Expand Up @@ -451,15 +451,13 @@ export class AppImpl implements App {
}
}

const result = defer<TraceImpl>();

// Rationale for asyncLimiter: openTrace takes several seconds and involves
// a long sequence of async tasks (e.g. invoking plugins' onLoad()). These
// tasks cannot overlap if the user opens traces in rapid succession, as
// they will mess up the state of registries. So once we start, we must
// complete trace loading (we don't bother supporting cancellations. If the
// user is too bothered, they can reload the tab).
await this.appCtx.openTraceAsyncLimiter.schedule(async () => {
return this.appCtx.openTraceAsyncQueue.schedule(async () => {
// Wait for extras parsing descriptors to be loaded
// via is_internal_user.js. This prevents a race condition where
// trace loading would otherwise begin before this data is available.
Expand All @@ -483,17 +481,12 @@ export class AppImpl implements App {
// loadTrace to be finished before setting it because some internal
// implementation details of loadTrace() rely on that trace to be current
// to work properly (mainly the router hash uuid).

result.resolve(trace);
} catch (error) {
result.reject(error);
return trace;
} finally {
this.appCtx.setTraceLoading(src, false);
raf.scheduleFullRedraw();
}
});

return result;
}

// Called by trace_loader.ts soon after it has created a new TraceImpl.
Expand Down