You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The onFileAddUnlink function is async and returns a Promise, but the 'add' and 'unlink' watcher event handlers were calling it without handling the returned Promise:
// Beforewatcher.on('add',(file)=>{onFileAddUnlink(file,false)// Promise is silently dropped})watcher.on('unlink',(file)=>{onFileAddUnlink(file,true)// Promise is silently dropped})
This has three consequences:
Unhandled Promise Rejection — if any of the internal await calls inside onFileAddUnlink (e.g. pluginContainer.watchChange, clientModuleGraph.getModuleByUrl, or onHMRUpdate) throw an error, the rejection is never caught. In Node.js 15+, an unhandled rejection crashes the process, killing the dev server.
Race condition — since the 'change' handler is async and properly awaits its operations, but 'add'/'unlink' are not, concurrent file events can cause operations to run out of order (e.g. moduleGraph invalidation racing with publicFiles update).
Silent HMR failure — onHMRUpdate('create'/'delete', file) at the end of onFileAddUnlink may not complete before a subsequent event runs, causing HMR notifications to be missed. This is why users sometimes need to manually restart the dev server after adding or deleting files.
Compare with the 'change' handler, which correctly uses async/await:
Unit tests: 57 test files, 766 tests pass. The fix only affects the error path that was previously silently swallowed — no behavioural change under normal conditions.
Added tests for the watchChange throwing case on both 'add' and 'unlink' events in packages/vite/src/node/__tests__/plugins/hooks.spec.ts.
Regarding hotUpdate and handleHotUpdate: after tracing through the call chain, errors thrown by those hooks are caught internally within handleHMRUpdate and never propagate back up to onFileAddUnlink. Specifically:
hotUpdate errors are caught in a try/catch inside handleHMRUpdate, stored temporarily, and then re-thrown inside the hmr() closure where they're caught again and forwarded to the client as an HMR error payload via environment.hot.send({ type: 'error', ... }).
handleHotUpdate is only invoked when type === 'update', so it is never called from the 'add'/'unlink' paths at all.
So the only hook whose rejection can actually escape onFileAddUnlink and trigger an unhandled promise rejection is watchChange. The two added tests cover that path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
feat: devdev serverp2-edge-caseBug, but has workaround or limited in scope (priority)
3 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this PR solving?
The
onFileAddUnlinkfunction isasyncand returns aPromise, but the'add'and'unlink'watcher event handlers were calling it without handling the returned Promise:This has three consequences:
Unhandled Promise Rejection — if any of the internal
awaitcalls insideonFileAddUnlink(e.g.pluginContainer.watchChange,clientModuleGraph.getModuleByUrl, oronHMRUpdate) throw an error, the rejection is never caught. In Node.js 15+, an unhandled rejection crashes the process, killing the dev server.Race condition — since the
'change'handler isasyncand properlyawaits its operations, but'add'/'unlink'are not, concurrent file events can cause operations to run out of order (e.g.moduleGraphinvalidation racing withpublicFilesupdate).Silent HMR failure —
onHMRUpdate('create'/'delete', file)at the end ofonFileAddUnlinkmay not complete before a subsequent event runs, causing HMR notifications to be missed. This is why users sometimes need to manually restart the dev server after adding or deleting files.Compare with the
'change'handler, which correctly usesasync/await:Fix
Added
.catch()on the returned Promise so errors surface through the logger instead of crashing or disappearing silently:Tests
Unit tests: 57 test files, 766 tests pass. The fix only affects the error path that was previously silently swallowed — no behavioural change under normal conditions.