fix(logs): streaming logs from InjectedScriptPoll without exception (#2712)

We used to get undefined messages, because we were mistakenly
fulfilling the logs multiple times.
This commit is contained in:
Dmitry Gozman 2020-06-25 13:13:10 -07:00 committed by GitHub
parent 807dc1f324
commit 5d5cf26a0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 28 additions and 27 deletions

View File

@ -697,20 +697,17 @@ export class InjectedScriptPollHandler<T> {
// - no unnecessary work in the page;
// - no possible side effects after progress promsie rejects.
this._progress.cleanupWhenAborted(() => this.cancel());
this._streamLogs(poll.evaluateHandle(poll => poll.logs));
this._streamLogs();
}
private _streamLogs(logsPromise: Promise<js.JSHandle<types.InjectedScriptLogs>>) {
// We continuously get a chunk of logs, stream them to the progress and wait for the next chunk.
logsPromise.catch(e => null).then(logs => {
if (!logs || !this._poll || !this._progress.isRunning())
private async _streamLogs() {
while (this._poll && this._progress.isRunning()) {
const messages = await this._poll.evaluate(poll => poll.takeNextLogs()).catch(e => [] as string[]);
if (!this._poll || !this._progress.isRunning())
return;
logs.evaluate(logs => logs.current).catch(e => [] as string[]).then(messages => {
for (const message of messages)
this._progress.logger.info(message);
});
this._streamLogs(logs.evaluateHandle(logs => logs.next));
});
for (const message of messages)
this._progress.logger.info(message);
}
}
async finishHandle(): Promise<js.SmartHandle<T>> {

View File

@ -158,14 +158,20 @@ export default class InjectedScript {
}
private _runAbortableTask<T>(task: (progess: types.InjectedScriptProgress) => Promise<T>): types.InjectedScriptPoll<T> {
let currentLogs: string[] = [];
let logReady = () => {};
const createLogsPromise = () => new Promise<types.InjectedScriptLogs>(fulfill => {
logReady = () => {
const current = currentLogs;
currentLogs = [];
fulfill({ current, next: createLogsPromise() });
};
let unsentLogs: string[] = [];
let takeNextLogsCallback: ((logs: string[]) => void) | undefined;
const logReady = () => {
if (!takeNextLogsCallback)
return;
takeNextLogsCallback(unsentLogs);
unsentLogs = [];
takeNextLogsCallback = undefined;
};
const takeNextLogs = () => new Promise<string[]>(fulfill => {
takeNextLogsCallback = fulfill;
if (unsentLogs.length)
logReady();
});
let lastLog = '';
@ -173,7 +179,7 @@ export default class InjectedScript {
aborted: false,
log: (message: string) => {
lastLog = message;
currentLogs.push(message);
unsentLogs.push(message);
logReady();
},
logRepeating: (message: string) => {
@ -182,14 +188,11 @@ export default class InjectedScript {
},
};
// It is important to create logs promise before running the poll to capture logs from the first run.
const logs = createLogsPromise();
return {
logs,
takeNextLogs,
result: task(progress),
cancel: () => { progress.aborted = true; },
takeLastLogs: () => currentLogs,
takeLastLogs: () => unsentLogs,
};
}

View File

@ -143,10 +143,11 @@ export type InjectedScriptProgress = {
logRepeating: (message: string) => void,
};
export type InjectedScriptLogs = { current: string[], next: Promise<InjectedScriptLogs> };
export type InjectedScriptPoll<T> = {
result: Promise<T>,
logs: Promise<InjectedScriptLogs>,
// Takes more logs, waiting until at least one message is available.
takeNextLogs: () => Promise<string[]>,
// Takes all current logs without waiting.
takeLastLogs: () => string[],
cancel: () => void,
};