0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-24 23:21:57 -05:00

Handle unhandledrejections in the dev server (#9424)

* Handle unhandledrejections in the dev server

* Adding changeset

* Update .changeset/curvy-lobsters-crash.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Use AsyncLocalStorage

* Return errorWithMetadata

* Send the error to the browser

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Matthew Phillips 2023-12-14 08:39:06 -05:00 committed by GitHub
parent 8c9fe0092a
commit e1a5a2d36a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 82 additions and 26 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Prevents dev server from crashing on unhandled rejections, and adds a helpful error message

View file

@ -1292,3 +1292,12 @@ export const CantRenderPage = {
// Generic catch-all - Only use this in extreme cases, like if there was a cosmic ray bit flip
export const UnknownError = { name: 'UnknownError', title: 'Unknown Error.' } satisfies ErrorData;
export const UnhandledRejection = {
name: 'UnhandledRejection',
title: 'Unhandled rejection',
message: (stack: string) => {
return `Astro detected an unhandled rejection. Here's the stack trace:\n${stack}`;
},
hint: 'Make sure your promises all have an `await` or a `.catch()` handler.'
}

View file

@ -0,0 +1,33 @@
import type { ModuleLoader } from '../core/module-loader/index.js'
import type { AstroConfig } from '../@types/astro.js';
import type DevPipeline from './devPipeline.js';
import { collectErrorMetadata } from '../core/errors/dev/index.js';
import { createSafeError } from '../core/errors/index.js';
import { formatErrorMessage } from '../core/messages.js';
import { eventError, telemetry } from '../events/index.js';
export function recordServerError(loader: ModuleLoader, config: AstroConfig, pipeline: DevPipeline, _err: unknown) {
const err = createSafeError(_err);
// This could be a runtime error from Vite's SSR module, so try to fix it here
try {
loader.fixStacktrace(err);
} catch {}
// This is our last line of defense regarding errors where we still might have some information about the request
// Our error should already be complete, but let's try to add a bit more through some guesswork
const errorWithMetadata = collectErrorMetadata(err, config.root);
telemetry.record(eventError({ cmd: 'dev', err: errorWithMetadata, isFatal: false }));
pipeline.logger.error(
null,
formatErrorMessage(errorWithMetadata, pipeline.logger.level() === 'debug')
);
return {
error: err,
errorWithMetadata
};
}

View file

@ -10,6 +10,12 @@ import { baseMiddleware } from './base.js';
import { createController } from './controller.js';
import DevPipeline from './devPipeline.js';
import { handleRequest } from './request.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { getViteErrorPayload } from '../core/errors/dev/index.js';
import { AsyncLocalStorage } from 'node:async_hooks';
import { IncomingMessage } from 'node:http';
import { setRouteError } from './server-state.js';
import { recordServerError } from './error.js';
export interface AstroPluginOptions {
settings: AstroSettings;
@ -30,6 +36,7 @@ export default function createVitePluginAstroServer({
const pipeline = new DevPipeline({ logger, manifest, settings, loader });
let manifestData: ManifestData = createRouteManifest({ settings, fsMod }, logger);
const controller = createController({ loader });
const localStorage = new AsyncLocalStorage();
/** rebuild the route cache + manifest, as needed. */
function rebuildManifest(needsManifestRebuild: boolean) {
@ -43,6 +50,22 @@ export default function createVitePluginAstroServer({
viteServer.watcher.on('unlink', rebuildManifest.bind(null, true));
viteServer.watcher.on('change', rebuildManifest.bind(null, false));
function handleUnhandledRejection(rejection: any) {
const error = new AstroError({
...AstroErrorData.UnhandledRejection,
message: AstroErrorData.UnhandledRejection.message(rejection?.stack || rejection)
});
const store = localStorage.getStore();
if(store instanceof IncomingMessage) {
const request = store;
setRouteError(controller.state, request.url!, error);
}
const { errorWithMetadata } = recordServerError(loader, settings.config, pipeline, error);
setTimeout(async () => loader.webSocketSend(await getViteErrorPayload(errorWithMetadata)), 200)
}
process.on('unhandledRejection', handleUnhandledRejection);
return () => {
// Push this middleware to the front of the stack so that it can intercept responses.
// fix(#6067): always inject this to ensure zombie base handling is killed after restarts
@ -57,13 +80,15 @@ export default function createVitePluginAstroServer({
response.end();
return;
}
handleRequest({
pipeline,
manifestData,
controller,
incomingRequest: request,
incomingResponse: response,
manifest,
localStorage.run(request, () => {
handleRequest({
pipeline,
manifestData,
controller,
incomingRequest: request,
incomingResponse: response,
manifest,
});
});
});
};

View file

@ -11,6 +11,7 @@ import { runWithErrorHandling } from './controller.js';
import type DevPipeline from './devPipeline.js';
import { handle500Response } from './response.js';
import { handleRoute, matchRoute } from './route.js';
import { recordServerError } from './error.js';
type HandleRequest = {
pipeline: DevPipeline;
@ -89,26 +90,9 @@ export async function handleRequest({
});
},
onError(_err) {
const err = createSafeError(_err);
// This could be a runtime error from Vite's SSR module, so try to fix it here
try {
moduleLoader.fixStacktrace(err);
} catch {}
// This is our last line of defense regarding errors where we still might have some information about the request
// Our error should already be complete, but let's try to add a bit more through some guesswork
const errorWithMetadata = collectErrorMetadata(err, config.root);
telemetry.record(eventError({ cmd: 'dev', err: errorWithMetadata, isFatal: false }));
pipeline.logger.error(
null,
formatErrorMessage(errorWithMetadata, pipeline.logger.level() === 'debug')
);
const { error, errorWithMetadata } = recordServerError(moduleLoader, config, pipeline, _err);
handle500Response(moduleLoader, incomingResponse, errorWithMetadata);
return err;
return error;
},
});
}