WebThingsIO/webthing-node

Port stays in use after server.stop()

jaller94 opened this issue · 3 comments

Steps to reproduce

Run the following script with any WebThing in ./webthing.js.

const {
  SingleThing,
  WebThingServer,
} = require('webthing');
const WebThing = require('./webthing.js');

async function test() {
  const PORT = 8000;
  const thing = new WebThing();
  const server = new WebThingServer(new SingleThing(thing), PORT);
  console.log('start 1');
  await server.start();
  server.stop();
  console.log('start 2');
  await server.start();
  server.stop();
}

test().catch(console.error);

Actual

The server fails to start after it got stopped, because the port is still in use. Output:

start 1
start 2
Error [ERR_SERVER_ALREADY_LISTEN]: Listen method has been called more than once without closing.
    at Server.listen (net.js:1372:11)
    at ipPromise.then (/home/jaller94/Git/webthings/webthing-add/node_modules/webthing/lib/server.js:765:19)
    at process._tickCallback (internal/process/next_tick.js:68:7)

Expected

The server starts and stops two times without a problem.

It seems like I was still using 0.8.0.
I have not been able to reproduce this with 0.8.1. 👍

Sorry for the report of a solved issue.

dnssd has an advertisement stop process which "takes ~1s".
https://github.com/DeMille/dnssd.js#user-content-advertisement-stop

When testing with the WebThingServer in Jest or other applications that quickly restart the server, there is no way of telling, whether dnssd is still running after calling .stop() on an WebThingServer object.

As it has been introduced to the .start(), I recommend to return a Promise on closing the server. This Promise should resolve as soon as both servers (express and dnssd) have freed their ports and callbacks.
https://github.com/mozilla-iot/webthing-node/blob/71d90f545b31a87cd8cbd4c13ab73bba9fa724ea/lib/server.js#L776

While the original code does not fail anymore I still have an issue with dnssd not shutting down. I will open a different issue for that.

Source suggestion for WebThingServer.stop():

/**
   * Stop listening.
   *
   * @param {boolean} forceImmediately Clean stop advertisement or force stop
   * @returns {Promise} Promise which resolves once the server is started.
   */
  stop(forceImmediately = false) {
    return new Promise((resolve, reject) => {
      if (this.mdns) {
        this.mdns.stop(forceImmediately, (error) => {
          error ? reject(error) : resolve();
        });
      }
      this.server.close();
    });
  }