From 066559a5e1a17b3b89c751dd8df2d4127e37ed0c Mon Sep 17 00:00:00 2001 From: Walter Krivanek Date: Wed, 10 Jul 2024 19:20:33 +0200 Subject: [PATCH 1/5] Makes close asynchronous. Also prevents multiple invocations and closes any WebSocket connections. --- server.js | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/server.js b/server.js index 7c3ab06..8995f76 100644 --- a/server.js +++ b/server.js @@ -790,21 +790,43 @@ class EleventyDevServer { } } - close() { + // Helper for promisifying close methods with callbacks, like http.Server or ws.WebSocketServer. + _closeServer(server) { + return new Promise((resolve, reject) => { + server.close(err => { + if (err) { + reject(err); + } + resolve(); + }); + }); + } + + async close() { + // Prevent multiple invocations. + if (this?._isClosing) { + return; + } + this._isClosing = true; + // TODO would be awesome to set a delayed redirect when port changed to redirect to new _server_ this.sendUpdateNotification({ type: "eleventy.status", status: "disconnected", }); - if(this.server) { - this.server.close(); - } if(this.updateServer) { - this.updateServer.close(); + // Close all existing WS connections. + this.updateServer?.clients.forEach(socket => socket.close()); + await this._closeServer(this.updateServer); } + + if(this._server?.listening) { + await this._closeServer(this.server); + } + if(this._watcher) { - this._watcher.close(); + await this._watcher.close(); delete this._watcher; } } From bd81479711d00ca2a9f2f119c8bebc05c3aa1da0 Mon Sep 17 00:00:00 2001 From: Walter Krivanek Date: Wed, 10 Jul 2024 19:21:47 +0200 Subject: [PATCH 2/5] Returns async server.close() for Cmd to handle. --- cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli.js b/cli.js index 35621eb..a21cdfc 100644 --- a/cli.js +++ b/cli.js @@ -81,7 +81,7 @@ Arguments: close() { if(this.server) { - this.server.close(); + return this.server.close(); } } } From d8a651db4fff53e8aed666cf301b545bd38e41e0 Mon Sep 17 00:00:00 2001 From: Walter Krivanek Date: Wed, 10 Jul 2024 19:22:23 +0200 Subject: [PATCH 3/5] Handles async close and doesn't force premature exit. --- cmd.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd.js b/cmd.js index f808204..695b845 100755 --- a/cmd.js +++ b/cmd.js @@ -67,9 +67,9 @@ try { domDiff: argv.domdiff, }); - process.on("SIGINT", () => { - cli.close(); - process.exit(); + process.on("SIGINT", async () => { + await cli.close(); + process.exitCode = 0; }); } } catch (e) { From 3a37e7adad782342a275d6fedf769758f607578c Mon Sep 17 00:00:00 2001 From: Walter Krivanek Date: Wed, 10 Jul 2024 19:23:31 +0200 Subject: [PATCH 4/5] Tests handle async server.close(). --- test/testServer.js | 36 +++++++++++++++++++----------------- test/testServerRequests.js | 28 ++++++++++++++-------------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/test/testServer.js b/test/testServer.js index 741211c..6489ed0 100644 --- a/test/testServer.js +++ b/test/testServer.js @@ -6,7 +6,7 @@ function testNormalizeFilePath(filepath) { return filepath.split("/").join(path.sep); } -test("Url mappings for resource/index.html", t => { +test("Url mappings for resource/index.html", async (t) => { let server = new EleventyDevServer("test-server", "./test/stubs/"); t.deepEqual(server.mapUrlToFilePath("/route1/"), { @@ -27,11 +27,11 @@ test("Url mappings for resource/index.html", t => { statusCode: 200, filepath: testNormalizeFilePath("test/stubs/route1/index.html") }); - - server.close(); + + await server.close(); }); -test("Url mappings for resource.html", t => { +test("Url mappings for resource.html", async (t) => { let server = new EleventyDevServer("test-server", "./test/stubs/"); t.deepEqual(server.mapUrlToFilePath("/route2/"), { @@ -53,10 +53,10 @@ test("Url mappings for resource.html", t => { filepath: testNormalizeFilePath("test/stubs/route2.html",) }); - server.close(); + await server.close(); }); -test("Url mappings for resource.html and resource/index.html", t => { +test("Url mappings for resource.html and resource/index.html", async (t) => { let server = new EleventyDevServer("test-server", "./test/stubs/"); // Production mismatch warning: Netlify 301 redirects to /route3 here @@ -80,10 +80,10 @@ test("Url mappings for resource.html and resource/index.html", t => { filepath: testNormalizeFilePath("test/stubs/route3.html",) }); - server.close(); + await server.close(); }); -test("Url mappings for missing resource", t => { +test("Url mappings for missing resource", async (t) => { let server = new EleventyDevServer("test-server", "./test/stubs/"); // 404s @@ -91,10 +91,10 @@ test("Url mappings for missing resource", t => { statusCode: 404 }); - server.close(); + await server.close(); }); -test("Url mapping for a filename with a space in it", t => { +test("Url mapping for a filename with a space in it", async (t) => { let server = new EleventyDevServer("test-server", "./test/stubs/"); t.deepEqual(server.mapUrlToFilePath("/route space.html"), { @@ -102,7 +102,7 @@ test("Url mapping for a filename with a space in it", t => { filepath: testNormalizeFilePath("test/stubs/route space.html",) }); - server.close(); + await server.close(); }); test("matchPassthroughAlias", async (t) => { @@ -131,6 +131,8 @@ test("matchPassthroughAlias", async (t) => { // Map entry exists, file exists t.is(server.matchPassthroughAlias("/elsewhere/index.css"), "./test/stubs/with-css/style.css"); + + await server.close(); }); @@ -155,7 +157,7 @@ test("pathPrefix matching", async (t) => { url: '/pathprefix/', }); - server.close(); + await server.close(); }); test("pathPrefix without leading slash", async (t) => { @@ -179,7 +181,7 @@ test("pathPrefix without leading slash", async (t) => { url: '/pathprefix/', }); - server.close(); + await server.close(); }); test("pathPrefix without trailing slash", async (t) => { @@ -203,7 +205,7 @@ test("pathPrefix without trailing slash", async (t) => { url: '/pathprefix/', }); - server.close(); + await server.close(); }); test("pathPrefix without leading or trailing slash", async (t) => { @@ -227,7 +229,7 @@ test("pathPrefix without leading or trailing slash", async (t) => { url: '/pathprefix/', }); - server.close(); + await server.close(); }); test("indexFileName option: serve custom index when provided", async (t) => { @@ -244,7 +246,7 @@ test("indexFileName option: serve custom index when provided", async (t) => { filepath: testNormalizeFilePath("test/stubs/route1/custom-index.html"), }); - server.close(); + await server.close(); }); test("indexFileName option: return 404 when custom index file doesn't exist", async (t) => { @@ -254,5 +256,5 @@ test("indexFileName option: return 404 when custom index file doesn't exist", as statusCode: 404, }); - server.close(); + await server.close(); }); diff --git a/test/testServerRequests.js b/test/testServerRequests.js index 132188c..4be4a34 100644 --- a/test/testServerRequests.js +++ b/test/testServerRequests.js @@ -75,7 +75,7 @@ async function fetchHeadersForRequest(t, server, path, extras) { }) } -test("Standard request", async t => { +test("Standard request", async (t) => { let server = new EleventyDevServer("test-server", "./test/stubs/", getOptions()); server.serve(8100); @@ -83,7 +83,7 @@ test("Standard request", async t => { t.true(data.includes("