summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Matthew Phillips <matthew@skypack.dev> 2023-12-14 08:39:06 -0500
committerGravatar GitHub <noreply@github.com> 2023-12-14 08:39:06 -0500
commite1a5a2d36ac3637f5c94a27b69128a121541bae8 (patch)
tree33a651587269f2897c86c8ffc9e736c8d883c97f
parent8c9fe0092a4181bc5b54a0f58c366a1d768ecd7d (diff)
downloadastro-e1a5a2d36ac3637f5c94a27b69128a121541bae8.tar.gz
astro-e1a5a2d36ac3637f5c94a27b69128a121541bae8.tar.zst
astro-e1a5a2d36ac3637f5c94a27b69128a121541bae8.zip
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>
-rw-r--r--.changeset/curvy-lobsters-crash.md5
-rw-r--r--packages/astro/src/core/errors/errors-data.ts9
-rw-r--r--packages/astro/src/vite-plugin-astro-server/error.ts33
-rw-r--r--packages/astro/src/vite-plugin-astro-server/plugin.ts39
-rw-r--r--packages/astro/src/vite-plugin-astro-server/request.ts22
5 files changed, 82 insertions, 26 deletions
diff --git a/.changeset/curvy-lobsters-crash.md b/.changeset/curvy-lobsters-crash.md
new file mode 100644
index 000000000..f3ccdb436
--- /dev/null
+++ b/.changeset/curvy-lobsters-crash.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Prevents dev server from crashing on unhandled rejections, and adds a helpful error message
diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts
index 5c30495d6..8e8fb3911 100644
--- a/packages/astro/src/core/errors/errors-data.ts
+++ b/packages/astro/src/core/errors/errors-data.ts
@@ -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.'
+}
diff --git a/packages/astro/src/vite-plugin-astro-server/error.ts b/packages/astro/src/vite-plugin-astro-server/error.ts
new file mode 100644
index 000000000..d29647f98
--- /dev/null
+++ b/packages/astro/src/vite-plugin-astro-server/error.ts
@@ -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
+ };
+}
diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts
index f0df0e3eb..6969184f2 100644
--- a/packages/astro/src/vite-plugin-astro-server/plugin.ts
+++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts
@@ -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,
+ });
});
});
};
diff --git a/packages/astro/src/vite-plugin-astro-server/request.ts b/packages/astro/src/vite-plugin-astro-server/request.ts
index aa9124fb4..f0c6b3de0 100644
--- a/packages/astro/src/vite-plugin-astro-server/request.ts
+++ b/packages/astro/src/vite-plugin-astro-server/request.ts
@@ -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;
},
});
}