Skip to content
Closed
Changes from all commits
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
35 changes: 24 additions & 11 deletions playwright-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
// @ts-check

const fs = require('fs');
const https = require('https');
const path = require('path');

const { program } = require('playwright-core/lib/tools/cli-client/program');
Expand Down Expand Up @@ -56,20 +57,32 @@ async function checkForUpdates() {
}

async function fetchLatestVersion() {
const agent = new https.Agent({ keepAlive: false });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why a new agent?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

i felt this was clearer than relying on the default/global agent implicitly having keepAlive: false

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

here's specifically how the crash happens:

  1. the fetch below completes, but Undici can still have delayed work queued on Node’s worker task scheduler
  2. --version will then immediately calls process.exit(0)
  3. Node starts shutdown and closes the scheduler’s uv_async_t handle
  4. the queued fetch work races with shutdown and calls uv_async_send() on that closing handle
  5. Windows libuv hits assert(!(handle->flags & UV_HANDLE_CLOSING)) and aborts with 0xC0000409

this is why the version appears before the crash

NO_UPDATE_NOTIFIER=1 avoids fetch(), so there is no delayed work to race with process.exit()

technically it would probably be better to replace some/all of the process.exit(0) in playwright-core to allow for a more graceful shutdown

but this is also arguably correct since there's no need to keep the connection alive any longer

note there is an upstream fix for this nodejs/node#61999 but it has not been put into any release yet and i think this affects as far back as Node 23

try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 1500);
try {
const res = await fetch(`https://registry.npmjs.org/${packageJson.name}/latest`, { signal: controller.signal });
if (!res.ok)
return undefined;
const json = await res.json();
return typeof json.version === 'string' ? json.version : undefined;
} finally {
clearTimeout(timeout);
}
const body = await new Promise((resolve, reject) => {
const request = https.get(`https://registry.npmjs.org/${packageJson.name}/latest`, { agent }, response => {
const statusCode = response.statusCode || 0;
if (statusCode < 200 || statusCode >= 300) {
response.resume();
reject(new Error(`Unexpected status code ${statusCode}`));
return;
}
let body = '';
response.setEncoding('utf8');
response.on('data', chunk => body += chunk);
response.on('error', reject);
response.on('end', () => resolve(body));
});
const timeout = setTimeout(() => request.destroy(new Error('Request timed out')), 1500);
request.on('error', reject);
request.on('close', () => clearTimeout(timeout));
});
const json = JSON.parse(body);
return typeof json.version === 'string' ? json.version : undefined;
} catch {
return undefined;
} finally {
agent.destroy();
}
}

Expand Down