mirror of
https://github.com/withastro/astro.git
synced 2025-01-27 22:19:04 -05:00
fix(node): handle offshoot promise rejections (#10454)
* fix(node): handle offshoot promise rejections * add test * add changeset * Update packages/integrations/node/test/errors.test.js
This commit is contained in:
parent
4c1edd0af5
commit
83f9105cd5
4 changed files with 51 additions and 5 deletions
5
.changeset/sharp-flowers-ring.md
Normal file
5
.changeset/sharp-flowers-ring.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@astrojs/node": patch
|
||||
---
|
||||
|
||||
Prevents crashes caused by rejections of offshoot promises.
|
|
@ -1,3 +1,4 @@
|
|||
import { AsyncLocalStorage } from 'node:async_hooks';
|
||||
import { NodeApp } from 'astro/app/node';
|
||||
import type { RequestHandler } from './types.js';
|
||||
|
||||
|
@ -7,8 +8,20 @@ import type { RequestHandler } from './types.js';
|
|||
* Intended to be used in both standalone and middleware mode.
|
||||
*/
|
||||
export function createAppHandler(app: NodeApp): RequestHandler {
|
||||
/**
|
||||
* Keep track of the current request path using AsyncLocalStorage.
|
||||
* Used to log unhandled rejections with a helpful message.
|
||||
*/
|
||||
const als = new AsyncLocalStorage<string>();
|
||||
const logger = app.getAdapterLogger();
|
||||
process.on('unhandledRejection', reason => {
|
||||
const requestUrl = als.getStore();
|
||||
logger.error(`Unhandled rejection while rendering ${requestUrl}`);
|
||||
console.error(reason);
|
||||
});
|
||||
|
||||
return async (req, res, next, locals) => {
|
||||
let request;
|
||||
let request: Request;
|
||||
try {
|
||||
request = NodeApp.createRequest(req);
|
||||
} catch (err) {
|
||||
|
@ -19,11 +32,11 @@ export function createAppHandler(app: NodeApp): RequestHandler {
|
|||
|
||||
const routeData = app.match(request);
|
||||
if (routeData) {
|
||||
const response = await app.render(request, {
|
||||
const response = await als.run(request.url, () => app.render(request, {
|
||||
addCookieHeader: true,
|
||||
locals,
|
||||
routeData,
|
||||
});
|
||||
}));
|
||||
await NodeApp.writeResponse(response, res);
|
||||
} else if (next) {
|
||||
return next();
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { spawn } from 'node:child_process';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
import assert from 'node:assert/strict';
|
||||
import { after, before, describe, it } from 'node:test';
|
||||
import * as cheerio from 'cheerio';
|
||||
import nodejs from '../dist/index.js';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
describe('Errors', () => {
|
||||
/** @type {import('./test-utils.js').Fixture} */
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/errors/',
|
||||
|
@ -17,10 +22,31 @@ describe('Errors', () => {
|
|||
let devPreview;
|
||||
|
||||
before(async () => {
|
||||
devPreview = await fixture.preview();
|
||||
// The two tests that need the server to run are skipped
|
||||
// devPreview = await fixture.preview();
|
||||
});
|
||||
after(async () => {
|
||||
await devPreview.stop();
|
||||
await devPreview?.stop();
|
||||
});
|
||||
|
||||
it('stays alive after offshoot promise rejections', async () => {
|
||||
// this test needs to happen in a worker because node test runner adds a listener for unhandled rejections in the main thread
|
||||
const worker = new Worker('./test/fixtures/errors/dist/server/entry.mjs', {
|
||||
type: 'module',
|
||||
env: { ASTRO_NODE_LOGGING: 'enabled' }
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
worker.stdout.on('data', data => {
|
||||
setTimeout(() => reject("Server took too long to start"), 1000);
|
||||
if (data.toString().includes('Server listening on http://localhost:4321')) resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await fetch("http://localhost:4321/offshoot-promise-rejection");
|
||||
|
||||
// if there was a crash, it becomes an error here
|
||||
await worker.terminate();
|
||||
});
|
||||
|
||||
it(
|
||||
|
|
2
packages/integrations/node/test/fixtures/errors/src/pages/offshoot-promise-rejection.astro
vendored
Normal file
2
packages/integrations/node/test/fixtures/errors/src/pages/offshoot-promise-rejection.astro
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
{new Promise(async _ => (await {}, Astro.props.undefined.alsoAPropertyOfUndefined))}
|
||||
{Astro.props.undefined.propertyOfUndefined}
|
Loading…
Add table
Reference in a new issue