Skip to content

Commit 678348a

Browse files
committed
fix(dev): drain connections before force-closing the dev server
1 parent d5c583e commit 678348a

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

‎packages/nuxt-cli/src/dev/listen.ts‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ const ANY_HOSTS = new Set(['', '0.0.0.0', '::'])
6969
constLOOPBACK_HOSTS=newSet(['localhost','127.0.0.1','::1'])
7070
constDEFAULT_PORTS={http: 80,https: 443}asconst
7171

72+
/** How long in-flight requests have to finish before their sockets are killed. */
73+
constCONNECTION_DRAIN_TIMEOUT_MS=1000
74+
7275
/**
7376
* Render a URL for display: the port is omitted when it is the protocol
7477
* default, and percent-encoding is decoded so a non-ASCII `baseURL` is
@@ -270,8 +273,23 @@ export async function listen(handler: RequestListener, options: ListenOptions =
270273
close: async()=>{
271274
awaittunnel?.close()
272275
returnnewPromise<void>((resolve,reject)=>{
273-
server.close(error=>(error ? reject(error) : resolve()))
274-
server.closeAllConnections?.()
276+
letforceClose: NodeJS.Timeout|undefined
277+
server.close((error)=>{
278+
if(forceClose){
279+
clearTimeout(forceClose)
280+
}
281+
if(error){
282+
reject(error)
283+
}
284+
else{
285+
resolve()
286+
}
287+
})
288+
// Sockets waiting on keep-alive are closed at once, so shutdown is only
289+
// delayed while a request is actually being served.
290+
server.closeIdleConnections?.()
291+
forceClose=setTimeout(()=>server.closeAllConnections?.(),CONNECTION_DRAIN_TIMEOUT_MS)
292+
forceClose.unref()
275293
})
276294
},
277295
}

‎packages/nuxt-cli/test/unit/listen.spec.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,35 @@ describe('listen', () => {
202202
})
203203
})
204204

205+
describe('listener.close',()=>{
206+
it('should let an in-flight request finish',async()=>{
207+
letrespond: (()=>void)|undefined
208+
constlistener=awaitlisten((_req,res)=>{
209+
respond=()=>res.end('done')
210+
},{port: 0,hostname: '127.0.0.1',showURL: false})
211+
212+
constresponse=fetch(listener.url)
213+
awaitvi.waitFor(()=>expect(respond).toBeDefined())
214+
215+
constclosed=listener.close()
216+
respond!()
217+
218+
awaitexpect((awaitresponse).text()).resolves.toBe('done')
219+
awaitclosed
220+
})
221+
222+
it('should not wait for idle keep-alive connections',async()=>{
223+
constlistener=awaitlisten((_req,res)=>res.end('ok'),{port: 0,hostname: '127.0.0.1',showURL: false})
224+
225+
awaitfetch(listener.url).then(response=>response.text())
226+
227+
conststart=Date.now()
228+
awaitlistener.close()
229+
230+
expect(Date.now()-start).toBeLessThan(500)
231+
})
232+
})
233+
205234
describe('copyURL',()=>{
206235
afterEach(()=>{
207236
restoreEnvironment()

0 commit comments

Comments
 (0)