Skip to content

Commit 0464398

Browse files
authoredJun 8, 2023
fix: await requests to before server restart (#13262)
1 parent 43cbbcf commit 0464398

File tree

6 files changed

+109
-24
lines changed

6 files changed

+109
-24
lines changed
 

‎packages/vite/src/node/plugins/importAnalysis.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
shouldExternalizeForSSR,
5555
} from '../ssr/ssrExternal'
5656
import { getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer'
57+
import { ERR_CLOSED_SERVER } from '../server/pluginContainer'
5758
import { checkPublicFile } from './asset'
5859
import {
5960
ERR_OUTDATED_OPTIMIZED_DEP,
@@ -255,10 +256,10 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
255256
// have been loaded so its entry is guaranteed in the module graph.
256257
const importerModule = moduleGraph.getModuleById(importer)!
257258
if (!importerModule) {
258-
// When the server is restarted, the module graph is cleared, so we
259-
// return without transforming. This request is no longer valid, a full reload
260-
// is going to request this id again. Throwing an outdated error so we
261-
// properly finish the request with a 504 sent to the browser.
259+
// This request is no longer valid. It could happen for optimized deps
260+
// requests. A full reload is going to request this id again.
261+
// Throwing an outdated error so we properly finish the request with a
262+
// 504 sent to the browser.
262263
throwOutdatedRequest(importer)
263264
}
264265

@@ -650,8 +651,11 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
650651
// by the deps optimizer
651652
const url = removeImportQuery(hmrUrl)
652653
server.transformRequest(url, { ssr }).catch((e) => {
653-
if (e?.code === ERR_OUTDATED_OPTIMIZED_DEP) {
654-
// This are expected errors
654+
if (
655+
e?.code === ERR_OUTDATED_OPTIMIZED_DEP ||
656+
e?.code === ERR_CLOSED_SERVER
657+
) {
658+
// these are expected errors
655659
return
656660
}
657661
// Unexpected error, log the issue but avoid an unhandled exception

‎packages/vite/src/node/server/index.ts

+11
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,17 @@ export async function _createServer(
467467
getDepsOptimizer(server.config, true)?.close(),
468468
closeHttpServer(),
469469
])
470+
// Await pending requests. We throw early in transformRequest
471+
// and in hooks if the server is closing, so the import analysis
472+
// plugin stops pre-transforming static imports and this block
473+
// is resolved sooner.
474+
while (server._pendingRequests.size > 0) {
475+
await Promise.allSettled(
476+
[...server._pendingRequests.values()].map(
477+
(pending) => pending.request,
478+
),
479+
)
480+
}
470481
server.resolvedUrls = null
471482
},
472483
printUrls() {

‎packages/vite/src/node/server/middlewares/indexHtml.ts

+9
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import {
3636
unwrapId,
3737
wrapId,
3838
} from '../../utils'
39+
import { ERR_CLOSED_SERVER } from '../pluginContainer'
40+
import { ERR_OUTDATED_OPTIMIZED_DEP } from '../../plugins/optimizedDeps'
3941
import { isCSSRequest } from '../../plugins/css'
4042
import { checkPublicFile } from '../../plugins/asset'
4143
import { getCodeWithSourcemap, injectSourcesContent } from '../sourcemap'
@@ -349,6 +351,13 @@ function preTransformRequest(server: ViteDevServer, url: string, base: string) {
349351

350352
// transform all url as non-ssr as html includes client-side assets only
351353
server.transformRequest(url).catch((e) => {
354+
if (
355+
e?.code === ERR_OUTDATED_OPTIMIZED_DEP ||
356+
e?.code === ERR_CLOSED_SERVER
357+
) {
358+
// these are expected errors
359+
return
360+
}
352361
// Unexpected error, log the issue but avoid an unhandled exception
353362
server.config.logger.error(e.message)
354363
})

‎packages/vite/src/node/server/middlewares/transform.ts

+16
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
ERR_OPTIMIZE_DEPS_PROCESSING_ERROR,
3636
ERR_OUTDATED_OPTIMIZED_DEP,
3737
} from '../../plugins/optimizedDeps'
38+
import { ERR_CLOSED_SERVER } from '../pluginContainer'
3839
import { getDepsOptimizer } from '../../optimizer'
3940

4041
const debugCache = createDebugger('vite:cache')
@@ -234,6 +235,21 @@ export function transformMiddleware(
234235
// error but a normal part of the missing deps discovery flow
235236
return
236237
}
238+
if (e?.code === ERR_CLOSED_SERVER) {
239+
// Skip if response has already been sent
240+
if (!res.writableEnded) {
241+
res.statusCode = 504 // status code request timeout
242+
res.statusMessage = 'Outdated Request'
243+
res.end()
244+
}
245+
// We don't need to log an error in this case, the request
246+
// is outdated because new dependencies were discovered and
247+
// the new pre-bundle dependencies have changed.
248+
// A full-page reload has been issued, and these old requests
249+
// can't be properly fulfilled. This isn't an unexpected
250+
// error but a normal part of the missing deps discovery flow
251+
return
252+
}
237253
if (e?.code === ERR_LOAD_URL) {
238254
// Let other middleware handle if we can't load the url via transformRequest
239255
return next()

‎packages/vite/src/node/server/pluginContainer.ts

+54-16
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,18 @@ import { createPluginHookUtils } from '../plugins'
8484
import { buildErrorMessage } from './middlewares/error'
8585
import type { ModuleGraph } from './moduleGraph'
8686

87+
export const ERR_CLOSED_SERVER = 'ERR_CLOSED_SERVER'
88+
89+
export function throwClosedServerError(): never {
90+
const err: any = new Error(
91+
'The server is being restarted or closed. Request is outdated',
92+
)
93+
err.code = ERR_CLOSED_SERVER
94+
// This error will be caught by the transform middleware that will
95+
// send a 504 status code request timeout
96+
throw err
97+
}
98+
8799
export interface PluginContainerOptions {
88100
cwd?: string
89101
output?: OutputOptions
@@ -195,6 +207,7 @@ export async function createPluginContainer(
195207
): Promise<void> {
196208
const parallelPromises: Promise<unknown>[] = []
197209
for (const plugin of getSortedPlugins(hookName)) {
210+
// Don't throw here if closed, so buildEnd and closeBundle hooks can finish running
198211
const hook = plugin[hookName]
199212
if (!hook) continue
200213
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -571,12 +584,26 @@ export async function createPluginContainer(
571584
}
572585

573586
let closed = false
587+
const processesing = new Set<Promise<any>>()
588+
// keeps track of hook promises so that we can wait for them all to finish upon closing the server
589+
function handleHookPromise<T>(maybePromise: undefined | T | Promise<T>) {
590+
if (!(maybePromise as any)?.then) {
591+
return maybePromise
592+
}
593+
const promise = maybePromise as Promise<T>
594+
processesing.add(promise)
595+
return promise.finally(() => processesing.delete(promise))
596+
}
574597

575598
const container: PluginContainer = {
576599
options: await (async () => {
577600
let options = rollupOptions
578601
for (const optionsHook of getSortedPluginHooks('options')) {
579-
options = (await optionsHook.call(minimalContext, options)) || options
602+
if (closed) throwClosedServerError()
603+
options =
604+
(await handleHookPromise(
605+
optionsHook.call(minimalContext, options),
606+
)) || options
580607
}
581608
if (options.acornInjectPlugins) {
582609
parser = acorn.Parser.extend(
@@ -593,10 +620,12 @@ export async function createPluginContainer(
593620
getModuleInfo,
594621

595622
async buildStart() {
596-
await hookParallel(
597-
'buildStart',
598-
(plugin) => new Context(plugin),
599-
() => [container.options as NormalizedInputOptions],
623+
await handleHookPromise(
624+
hookParallel(
625+
'buildStart',
626+
(plugin) => new Context(plugin),
627+
() => [container.options as NormalizedInputOptions],
628+
),
600629
)
601630
},
602631

@@ -609,10 +638,10 @@ export async function createPluginContainer(
609638
ctx._scan = scan
610639
ctx._resolveSkips = skip
611640
const resolveStart = debugResolve ? performance.now() : 0
612-
613641
let id: string | null = null
614642
const partial: Partial<PartialResolvedId> = {}
615643
for (const plugin of getSortedPlugins('resolveId')) {
644+
if (closed) throwClosedServerError()
616645
if (!plugin.resolveId) continue
617646
if (skip?.has(plugin)) continue
618647

@@ -623,13 +652,15 @@ export async function createPluginContainer(
623652
'handler' in plugin.resolveId
624653
? plugin.resolveId.handler
625654
: plugin.resolveId
626-
const result = await handler.call(ctx as any, rawId, importer, {
627-
assertions: options?.assertions ?? {},
628-
custom: options?.custom,
629-
isEntry: !!options?.isEntry,
630-
ssr,
631-
scan,
632-
})
655+
const result = await handleHookPromise(
656+
handler.call(ctx as any, rawId, importer, {
657+
assertions: options?.assertions ?? {},
658+
custom: options?.custom,
659+
isEntry: !!options?.isEntry,
660+
ssr,
661+
scan,
662+
}),
663+
)
633664
if (!result) continue
634665

635666
if (typeof result === 'string') {
@@ -675,11 +706,14 @@ export async function createPluginContainer(
675706
const ctx = new Context()
676707
ctx.ssr = !!ssr
677708
for (const plugin of getSortedPlugins('load')) {
709+
if (closed) throwClosedServerError()
678710
if (!plugin.load) continue
679711
ctx._activePlugin = plugin
680712
const handler =
681713
'handler' in plugin.load ? plugin.load.handler : plugin.load
682-
const result = await handler.call(ctx as any, id, { ssr })
714+
const result = await handleHookPromise(
715+
handler.call(ctx as any, id, { ssr }),
716+
)
683717
if (result != null) {
684718
if (isObject(result)) {
685719
updateModuleInfo(id, result)
@@ -696,6 +730,7 @@ export async function createPluginContainer(
696730
const ctx = new TransformContext(id, code, inMap as SourceMap)
697731
ctx.ssr = !!ssr
698732
for (const plugin of getSortedPlugins('transform')) {
733+
if (closed) throwClosedServerError()
699734
if (!plugin.transform) continue
700735
ctx._activePlugin = plugin
701736
ctx._activeId = id
@@ -707,7 +742,9 @@ export async function createPluginContainer(
707742
? plugin.transform.handler
708743
: plugin.transform
709744
try {
710-
result = await handler.call(ctx as any, code, id, { ssr })
745+
result = await handleHookPromise(
746+
handler.call(ctx as any, code, id, { ssr }),
747+
)
711748
} catch (e) {
712749
ctx.error(e)
713750
}
@@ -741,6 +778,8 @@ export async function createPluginContainer(
741778

742779
async close() {
743780
if (closed) return
781+
closed = true
782+
await Promise.allSettled(Array.from(processesing))
744783
const ctx = new Context()
745784
await hookParallel(
746785
'buildEnd',
@@ -752,7 +791,6 @@ export async function createPluginContainer(
752791
() => ctx,
753792
() => [],
754793
)
755-
closed = true
756794
},
757795
}
758796

‎packages/vite/src/node/server/transformRequest.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { checkPublicFile } from '../plugins/asset'
2020
import { getDepsOptimizer } from '../optimizer'
2121
import { applySourcemapIgnoreList, injectSourcesContent } from './sourcemap'
2222
import { isFileServingAllowed } from './middlewares/static'
23+
import { throwClosedServerError } from './pluginContainer'
2324

2425
export const ERR_LOAD_URL = 'ERR_LOAD_URL'
2526
export const ERR_LOAD_PUBLIC_URL = 'ERR_LOAD_PUBLIC_URL'
@@ -46,6 +47,8 @@ export function transformRequest(
4647
server: ViteDevServer,
4748
options: TransformOptions = {},
4849
): Promise<TransformResult | null> {
50+
if (server._restartPromise) throwClosedServerError()
51+
4952
const cacheKey = (options.ssr ? 'ssr:' : options.html ? 'html:' : '') + url
5053

5154
// This module may get invalidated while we are processing it. For example
@@ -108,9 +111,8 @@ export function transformRequest(
108111
timestamp,
109112
abort: clearCache,
110113
})
111-
request.then(clearCache, clearCache)
112114

113-
return request
115+
return request.finally(clearCache)
114116
}
115117

116118
async function doTransform(
@@ -253,6 +255,9 @@ async function loadAndTransform(
253255
err.code = isPublicFile ? ERR_LOAD_PUBLIC_URL : ERR_LOAD_URL
254256
throw err
255257
}
258+
259+
if (server._restartPromise) throwClosedServerError()
260+
256261
// ensure module in graph after successful load
257262
mod ??= await moduleGraph._ensureEntryFromUrl(url, ssr, undefined, resolved)
258263
ensureWatchedFile(watcher, mod.file, root)
@@ -314,6 +319,8 @@ async function loadAndTransform(
314319
}
315320
}
316321

322+
if (server._restartPromise) throwClosedServerError()
323+
317324
const result =
318325
ssr && !server.config.experimental.skipSsrTransform
319326
? await server.ssrTransform(code, map as SourceMap, url, originalCode)

0 commit comments

Comments
 (0)
Please sign in to comment.