Skip to content

Commit 277c207

Browse files
committed
chore: remove Progress.raceWithCleanup
Now that legacy timeout mechanism is gone, we can replace all kinds of cleanups with an explicit try/catch.
1 parent b56d472 commit 277c207

File tree

15 files changed

+154
-104
lines changed

15 files changed

+154
-104
lines changed

packages/playwright-core/src/server/android/android.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,7 @@ export class Android extends SdkObject {
7979
newSerials.add(d.serial);
8080
if (this._devices.has(d.serial))
8181
continue;
82-
const device = await progress.raceWithCleanup(AndroidDevice.create(this, d, options), device => device.close());
83-
this._devices.set(d.serial, device);
82+
await progress.race(AndroidDevice.create(this, d, options).then(device => this._devices.set(d.serial, device)));
8483
}
8584
for (const d of this._devices.keys()) {
8685
if (!newSerials.has(d))
@@ -152,7 +151,7 @@ export class AndroidDevice extends SdkObject {
152151
}
153152

154153
async open(progress: Progress, command: string): Promise<SocketBackend> {
155-
return await progress.raceWithCleanup(this._backend.open(`${command}`), socket => socket.close());
154+
return await this._open(progress, command);
156155
}
157156

158157
async screenshot(): Promise<Buffer> {
@@ -216,7 +215,7 @@ export class AndroidDevice extends SdkObject {
216215
debug('pw:android')(`Polling the socket localabstract:${socketName}`);
217216
while (!socket) {
218217
try {
219-
socket = await progress.raceWithCleanup(this._backend.open(`localabstract:${socketName}`), socket => socket.close());
218+
socket = await this._open(progress, `localabstract:${socketName}`);
220219
} catch (e) {
221220
if (isAbortError(e))
222221
throw e;
@@ -354,14 +353,29 @@ export class AndroidDevice extends SdkObject {
354353
return defaultContext;
355354
}
356355

356+
private async _open(progress: Progress, command: string): Promise<SocketBackend> {
357+
let aborted = false;
358+
try {
359+
return await progress.race(this._backend.open(command).then(socket => {
360+
// Make sure to close the opened socket if progress was aborted before that.
361+
if (aborted)
362+
socket.close();
363+
return socket;
364+
}));
365+
} catch (error) {
366+
aborted = true;
367+
throw error;
368+
}
369+
}
370+
357371
webViews(): channels.AndroidWebView[] {
358372
return [...this._webViews.values()];
359373
}
360374

361375
async installApk(progress: Progress, content: Buffer, options?: { args?: string[] }): Promise<void> {
362376
const args = options && options.args ? options.args : ['-r', '-t', '-S'];
363377
debug('pw:android')('Opening install socket');
364-
const installSocket = await progress.raceWithCleanup(this._backend.open(`shell:cmd package install ${args.join(' ')} ${content.length}`), socket => socket.close());
378+
const installSocket = await this._open(progress, `shell:cmd package install ${args.join(' ')} ${content.length}`);
365379
debug('pw:android')('Writing driver bytes: ' + content.length);
366380
await progress.race(installSocket.write(content));
367381
const success = await progress.race(new Promise(f => installSocket.on('data', f)));
@@ -370,7 +384,7 @@ export class AndroidDevice extends SdkObject {
370384
}
371385

372386
async push(progress: Progress, content: Buffer, path: string, mode = 0o644): Promise<void> {
373-
const socket = await progress.raceWithCleanup(this._backend.open(`sync:`), socket => socket.close());
387+
const socket = await this._open(progress, `sync:`);
374388
const sendHeader = async (command: string, length: number) => {
375389
const buffer = Buffer.alloc(command.length + 4);
376390
buffer.write(command, 0);

packages/playwright-core/src/server/bidi/bidiChromium.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export class BidiChromium extends BrowserType {
8282
}
8383

8484
override attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void {
85+
// Note that it's fine to reuse the transport, since our connection ignores kBrowserCloseMessageId.
8586
const bidiTransport = (transport as any)[kBidiOverCdpWrapper];
8687
if (bidiTransport)
8788
transport = bidiTransport;

packages/playwright-core/src/server/bidi/bidiFirefox.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class BidiFirefox extends BrowserType {
7878
}
7979

8080
override attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void {
81+
// Note that it's fine to reuse the transport, since our connection ignores kBrowserCloseMessageId.
8182
transport.send({ method: 'browser.close', params: {}, id: kBrowserCloseMessageId });
8283
}
8384

packages/playwright-core/src/server/browser.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,27 @@ export abstract class Browser extends SdkObject {
100100
async newContext(progress: Progress, options: types.BrowserContextOptions): Promise<BrowserContext> {
101101
validateBrowserContextOptions(options, this.options);
102102
let clientCertificatesProxy: ClientCertificatesProxy | undefined;
103-
if (options.clientCertificates?.length) {
104-
clientCertificatesProxy = await progress.raceWithCleanup(ClientCertificatesProxy.create(options), proxy => proxy.close());
105-
options = { ...options };
106-
options.proxyOverride = clientCertificatesProxy.proxySettings();
107-
options.internalIgnoreHTTPSErrors = true;
103+
let context: BrowserContext | undefined;
104+
try {
105+
if (options.clientCertificates?.length) {
106+
clientCertificatesProxy = await ClientCertificatesProxy.create(progress, options);
107+
options = { ...options };
108+
options.proxyOverride = clientCertificatesProxy.proxySettings();
109+
options.internalIgnoreHTTPSErrors = true;
110+
}
111+
context = await progress.race(this.doCreateNewContext(options));
112+
context._clientCertificatesProxy = clientCertificatesProxy;
113+
if ((options as any).__testHookBeforeSetStorageState)
114+
await progress.race((options as any).__testHookBeforeSetStorageState());
115+
if (options.storageState)
116+
await context.setStorageState(progress, options.storageState);
117+
this.emit(Browser.Events.Context, context);
118+
return context;
119+
} catch (error) {
120+
await context?.close({ reason: 'Failed to create context' }).catch(() => {});
121+
await clientCertificatesProxy?.close().catch(() => {});
122+
throw error;
108123
}
109-
const context = await progress.raceWithCleanup(this.doCreateNewContext(options), context => context.close({ reason: 'Failed to create context' }));
110-
context._clientCertificatesProxy = clientCertificatesProxy;
111-
if ((options as any).__testHookBeforeSetStorageState)
112-
await progress.race((options as any).__testHookBeforeSetStorageState());
113-
if (options.storageState)
114-
await context.setStorageState(progress, options.storageState);
115-
this.emit(Browser.Events.Context, context);
116-
return context;
117124
}
118125

119126
async newContextForReuse(progress: Progress, params: channels.BrowserNewContextForReuseParams): Promise<BrowserContext> {

packages/playwright-core/src/server/browserContext.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -528,14 +528,20 @@ export abstract class BrowserContext extends SdkObject {
528528
}
529529

530530
async newPage(progress: Progress, isServerSide: boolean): Promise<Page> {
531-
const page = await progress.raceWithCleanup(this.doCreateNewPage(isServerSide), page => page.close());
532-
const pageOrError = await progress.race(page.waitForInitializedOrError());
533-
if (pageOrError instanceof Page) {
534-
if (pageOrError.isClosed())
535-
throw new Error('Page has been closed.');
536-
return pageOrError;
531+
let page: Page | undefined;
532+
try {
533+
page = await progress.race(this.doCreateNewPage(isServerSide));
534+
const pageOrError = await progress.race(page.waitForInitializedOrError());
535+
if (pageOrError instanceof Page) {
536+
if (pageOrError.isClosed())
537+
throw new Error('Page has been closed.');
538+
return pageOrError;
539+
}
540+
throw pageOrError;
541+
} catch (error) {
542+
await page?.close({ reason: 'Failed to create page' }).catch(() => {});
543+
throw error;
537544
}
538-
throw pageOrError;
539545
}
540546

541547
addVisitedOrigin(origin: string) {

packages/playwright-core/src/server/browserType.ts

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { debugMode } from './utils/debug';
2323
import { assert } from '../utils/isomorphic/assert';
2424
import { ManualPromise } from '../utils/isomorphic/manualPromise';
2525
import { DEFAULT_PLAYWRIGHT_TIMEOUT } from '../utils/isomorphic/time';
26-
import { existsAsync, removeFolders } from './utils/fileUtils';
26+
import { existsAsync } from './utils/fileUtils';
2727
import { helper } from './helper';
2828
import { SdkObject } from './instrumentation';
2929
import { PipeTransport } from './pipeTransport';
@@ -78,14 +78,19 @@ export abstract class BrowserType extends SdkObject {
7878
// Note: Any initial TLS requests will fail since we rely on the Page/Frames initialize which sets ignoreHTTPSErrors.
7979
let clientCertificatesProxy: ClientCertificatesProxy | undefined;
8080
if (options.clientCertificates?.length) {
81-
clientCertificatesProxy = await progress.raceWithCleanup(ClientCertificatesProxy.create(options), proxy => proxy.close());
81+
clientCertificatesProxy = await ClientCertificatesProxy.create(progress, options);
8282
launchOptions.proxyOverride = clientCertificatesProxy.proxySettings();
8383
options = { ...options };
8484
options.internalIgnoreHTTPSErrors = true;
8585
}
86-
const browser = await this._innerLaunchWithRetries(progress, launchOptions, options, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupLog(e); });
87-
browser._defaultContext!._clientCertificatesProxy = clientCertificatesProxy;
88-
return browser._defaultContext!;
86+
try {
87+
const browser = await this._innerLaunchWithRetries(progress, launchOptions, options, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupLog(e); });
88+
browser._defaultContext!._clientCertificatesProxy = clientCertificatesProxy;
89+
return browser._defaultContext!;
90+
} catch (error) {
91+
await clientCertificatesProxy?.close().catch(() => {});
92+
throw error;
93+
}
8994
}
9095

9196
async _innerLaunchWithRetries(progress: Progress, options: types.LaunchOptions, persistent: types.BrowserContextOptions | undefined, protocolLogger: types.ProtocolLogger, userDataDir?: string): Promise<Browser> {
@@ -106,35 +111,40 @@ export abstract class BrowserType extends SdkObject {
106111
options.proxy = options.proxy ? normalizeProxySettings(options.proxy) : undefined;
107112
const browserLogsCollector = new RecentLogsCollector();
108113
const { browserProcess, userDataDir, artifactsDir, transport } = await this._launchProcess(progress, options, !!persistent, browserLogsCollector, maybeUserDataDir);
109-
if ((options as any).__testHookBeforeCreateBrowser)
110-
await progress.race((options as any).__testHookBeforeCreateBrowser());
111-
const browserOptions: BrowserOptions = {
112-
name: this._name,
113-
isChromium: this._name === 'chromium',
114-
channel: options.channel,
115-
slowMo: options.slowMo,
116-
persistent,
117-
headful: !options.headless,
118-
artifactsDir,
119-
downloadsPath: (options.downloadsPath || artifactsDir)!,
120-
tracesDir: (options.tracesDir || artifactsDir)!,
121-
browserProcess,
122-
customExecutablePath: options.executablePath,
123-
proxy: options.proxy,
124-
protocolLogger,
125-
browserLogsCollector,
126-
wsEndpoint: transport instanceof WebSocketTransport ? transport.wsEndpoint : undefined,
127-
originalLaunchOptions: options,
128-
};
129-
if (persistent)
130-
validateBrowserContextOptions(persistent, browserOptions);
131-
copyTestHooks(options, browserOptions);
132-
const browser = await progress.race(this.connectToTransport(transport, browserOptions, browserLogsCollector));
133-
(browser as any)._userDataDirForTest = userDataDir;
134-
// We assume no control when using custom arguments, and do not prepare the default context in that case.
135-
if (persistent && !options.ignoreAllDefaultArgs)
136-
await browser._defaultContext!._loadDefaultContext(progress);
137-
return browser;
114+
try {
115+
if ((options as any).__testHookBeforeCreateBrowser)
116+
await progress.race((options as any).__testHookBeforeCreateBrowser());
117+
const browserOptions: BrowserOptions = {
118+
name: this._name,
119+
isChromium: this._name === 'chromium',
120+
channel: options.channel,
121+
slowMo: options.slowMo,
122+
persistent,
123+
headful: !options.headless,
124+
artifactsDir,
125+
downloadsPath: (options.downloadsPath || artifactsDir)!,
126+
tracesDir: (options.tracesDir || artifactsDir)!,
127+
browserProcess,
128+
customExecutablePath: options.executablePath,
129+
proxy: options.proxy,
130+
protocolLogger,
131+
browserLogsCollector,
132+
wsEndpoint: transport instanceof WebSocketTransport ? transport.wsEndpoint : undefined,
133+
originalLaunchOptions: options,
134+
};
135+
if (persistent)
136+
validateBrowserContextOptions(persistent, browserOptions);
137+
copyTestHooks(options, browserOptions);
138+
const browser = await progress.race(this.connectToTransport(transport, browserOptions, browserLogsCollector));
139+
(browser as any)._userDataDirForTest = userDataDir;
140+
// We assume no control when using custom arguments, and do not prepare the default context in that case.
141+
if (persistent && !options.ignoreAllDefaultArgs)
142+
await browser._defaultContext!._loadDefaultContext(progress);
143+
return browser;
144+
} catch (error) {
145+
await browserProcess.close().catch(() => {});
146+
throw error;
147+
}
138148
}
139149

140150
private async _prepareToLaunch(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string | undefined) {
@@ -194,7 +204,6 @@ export abstract class BrowserType extends SdkObject {
194204

195205
const env = options.env ? envArrayToObject(options.env) : process.env;
196206
const prepared = await progress.race(this._prepareToLaunch(options, isPersistent, userDataDir));
197-
progress.cleanupWhenAborted(() => removeFolders(prepared.tempDirectories));
198207

199208
// Note: it is important to define these variables before launchProcess, so that we don't get
200209
// "Cannot access 'browserServer' before initialization" if something went wrong.
@@ -217,10 +226,12 @@ export abstract class BrowserType extends SdkObject {
217226
attemptToGracefullyClose: async () => {
218227
if ((options as any).__testHookGracefullyClose)
219228
await (options as any).__testHookGracefullyClose();
220-
// We try to gracefully close to prevent crash reporting and core dumps.
221-
// Note that it's fine to reuse the pipe transport, since
222-
// our connection ignores kBrowserCloseMessageId.
223-
this.attemptToGracefullyCloseBrowser(transport!);
229+
if (transport) {
230+
// We try to gracefully close to prevent crash reporting and core dumps.
231+
this.attemptToGracefullyCloseBrowser(transport);
232+
} else {
233+
throw new Error('Force-killing the browser because no transport is available to gracefully close it.');
234+
}
224235
},
225236
onExit: (exitCode, signal) => {
226237
// Unblock launch when browser prematurely exits.
@@ -249,19 +260,22 @@ export abstract class BrowserType extends SdkObject {
249260
close: () => closeOrKill((options as any).__testHookBrowserCloseTimeout || DEFAULT_PLAYWRIGHT_TIMEOUT),
250261
kill
251262
};
252-
progress.cleanupWhenAborted(() => closeOrKill(DEFAULT_PLAYWRIGHT_TIMEOUT));
253-
const { wsEndpoint } = await progress.race([
254-
this.waitForReadyState(options, browserLogsCollector),
255-
exitPromise.then(() => ({ wsEndpoint: undefined })),
256-
]);
257-
if (options.cdpPort !== undefined || !this.supportsPipeTransport()) {
258-
transport = await WebSocketTransport.connect(progress, wsEndpoint!);
259-
} else {
260-
const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream];
261-
transport = new PipeTransport(stdio[3], stdio[4]);
263+
try {
264+
const { wsEndpoint } = await progress.race([
265+
this.waitForReadyState(options, browserLogsCollector),
266+
exitPromise.then(() => ({ wsEndpoint: undefined })),
267+
]);
268+
if (options.cdpPort !== undefined || !this.supportsPipeTransport()) {
269+
transport = await WebSocketTransport.connect(progress, wsEndpoint!);
270+
} else {
271+
const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream];
272+
transport = new PipeTransport(stdio[3], stdio[4]);
273+
}
274+
return { browserProcess, artifactsDir: prepared.artifactsDir, userDataDir: prepared.userDataDir, transport };
275+
} catch (error) {
276+
await closeOrKill(DEFAULT_PLAYWRIGHT_TIMEOUT).catch(() => {});
277+
throw error;
262278
}
263-
progress.cleanupWhenAborted(() => transport.close());
264-
return { browserProcess, artifactsDir: prepared.artifactsDir, userDataDir: prepared.userDataDir, transport };
265279
}
266280

267281
async _createArtifactDirs(options: types.LaunchOptions): Promise<void> {

packages/playwright-core/src/server/chromium/chromium.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export class Chromium extends BrowserType {
163163
}
164164

165165
override attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void {
166+
// Note that it's fine to reuse the transport, since our connection ignores kBrowserCloseMessageId.
166167
const message: ProtocolRequest = { method: 'Browser.close', id: kBrowserCloseMessageId, params: {} };
167168
transport.send(message);
168169
}

packages/playwright-core/src/server/firefox/firefox.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export class Firefox extends BrowserType {
6565
}
6666

6767
override attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void {
68+
// Note that it's fine to reuse the transport, since our connection ignores kBrowserCloseMessageId.
6869
const message = { method: 'Browser.close', params: {}, id: kBrowserCloseMessageId };
6970
transport.send(message);
7071
}

packages/playwright-core/src/server/frames.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ export class Frame extends SdkObject {
761761
return null;
762762
return continuePolling;
763763
}
764-
const result = await progress.raceWithCleanup(resolved.injected.evaluateHandle((injected, { info, root }) => {
764+
const result = await progress.race(resolved.injected.evaluateHandle((injected, { info, root }) => {
765765
if (root && !root.isConnected)
766766
throw injected.createStacklessError('Element is not attached to the DOM');
767767
const elements = injected.querySelectorAll(info.parsed, root || document);
@@ -776,7 +776,7 @@ export class Frame extends SdkObject {
776776
log = ` locator resolved to ${visible ? 'visible' : 'hidden'} ${injected.previewNode(element)}`;
777777
}
778778
return { log, element, visible, attached: !!element };
779-
}, { info: resolved.info, root: resolved.frame === this ? scope : undefined }), handle => handle.dispose());
779+
}, { info: resolved.info, root: resolved.frame === this ? scope : undefined }));
780780
const { log, visible, attached } = await progress.race(result.evaluate(r => ({ log: r.log, visible: r.visible, attached: r.attached })));
781781
if (log)
782782
progress.log(log);
@@ -1085,7 +1085,7 @@ export class Frame extends SdkObject {
10851085
const resolved = await progress.race(this.selectors.resolveInjectedForSelector(selector, { strict }));
10861086
if (!resolved)
10871087
return continuePolling;
1088-
const result = await progress.raceWithCleanup(resolved.injected.evaluateHandle((injected, { info, callId }) => {
1088+
const result = await progress.race(resolved.injected.evaluateHandle((injected, { info, callId }) => {
10891089
const elements = injected.querySelectorAll(info.parsed, document);
10901090
if (callId)
10911091
injected.markTargetElements(new Set(elements), callId);
@@ -1099,7 +1099,7 @@ export class Frame extends SdkObject {
10991099
log = ` locator resolved to ${injected.previewNode(element)}`;
11001100
}
11011101
return { log, success: !!element, element };
1102-
}, { info: resolved.info, callId: progress.metadata.id }), handle => handle.dispose());
1102+
}, { info: resolved.info, callId: progress.metadata.id }));
11031103
const { log, success } = await progress.race(result.evaluate(r => ({ log: r.log, success: r.success })));
11041104
if (log)
11051105
progress.log(log);

0 commit comments

Comments
 (0)