Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process SIGINT, SIGTERM, SIGQUIT signal events for iCubTelemVizServer Node.js process. #40

Open
16 of 17 tasks
Tracked by #34
nunoguedelha opened this issue Oct 13, 2021 · 5 comments
Open
16 of 17 tasks
Tracked by #34
Assignees
Labels
enhancement New feature or request Epic

Comments

@nunoguedelha
Copy link
Collaborator

nunoguedelha commented Oct 13, 2021

Processing the SIGINT event: closeServers(signal) function

The function closeServers(signal) implements the following ordered sequence of closure actions:

non-ticked points just need to be integrated into the Promise/resolve pattern.



  • Control Console closure #78 (NOT TREATED IN THIS EPIC)
    • Stop the Control Console HTTP server consoleServer from accepting new RPC commands from the control console web client.
    • Stop an eventual ongoing "ping" process.
    • Execute any command required to safely stop any ongoing operation (e.g. "pause walking" if the robot was on a walking cycle).
    • Complete the unfinished processing or responses to the pending commands.
    • Close the respective YARP RPC port connections and RPC read ports instantiated by the main Node.js process.

Originally posted by @nunoguedelha in #34 (comment)

@nunoguedelha
Copy link
Collaborator Author

References

References on Node.js

#34 (comment)

References on Express and Express-ws, routing etc

#34 (comment)

@nunoguedelha
Copy link
Collaborator Author

nunoguedelha commented Oct 20, 2021

General Approach

We group the actions in subsets. When a subset A has to be completed before the process exits or before a subset B starts, we use the promise Javascript pattern. Refer to:
https://www.w3schools.com/js/js_async.asp
https://www.w3schools.com/js/js_promise.asp
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

A promise is a JavaScript object that contains producing code (takes time to execute) and calls (asynchronously) consuming code once the producing code is complete: in our case, the subset A is the producing code and the subset B is the consuming one. If the tasks in the subset A can run in parallel, we can use the Promise.all() method.

The Promise.all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will resolve when all of the input's promises have resolved.

A.1 - Stop the child Node.js process running the OpenMCT static server.
A.2 - Stop the Telemetry HTTP server telemServer from accepting new connections.
||
V
B - Stop listening to client requests ("subscribe"/"unsubscribe" events) on the existing connections.
||
V
C.1- Stop reading the YARP ports associated to the telemetry entries in portInConfig.
C.2 - Close the respective YARP port connections and YARP ports instantiated by the main Node.js process.
C.3 - Wait for the processing or responses to the pending requests to be completed.
||
V
D.1 - Stop sending "real-time telemetry data" messages (notifications) to subscribers.

The pattern...

function subsetA () {
    const A1 = new Promise(functionA1(resolve,reject) {
        if (<condition>) {
            <processing after success>;
            resolve(values);
        } else {
            <processing after failure>;
            reject(error);
        }
    });
    const A2 = new Promise(functionA2(resolve,reject) {
        if (<condition>) {
            <processing after success>;
            resolve(values);
        } else {
            <processing after failure>;
            reject(error);
        }
    });
    Promise.all([A1,A2]).then(
        <resolve definition: call subsetB()>,
        <reject definition>
    );
}

function subsetB () {
    <same pattern as subsetA>;
}

@nunoguedelha
Copy link
Collaborator Author

nunoguedelha commented Oct 20, 2021

First subset A1, A2, A3

A.1 - Stop the child Node.js process running the OpenMCT static server.
A.2 - Stop the Telemetry HTTP server telemServer from accepting new connections.
A.3 - Stop the Control Console HTTP server consoleServer from accepting new connections.

(We added already A.3 as it obviously will be in the first subset)

A.1 Stop the child Node.js process running the OpenMCT static server

=> #41 & #50 - dabe06f

A.2 & A.3 Stop the HTTP servers from accepting new connections

In the same Promise/Resolve cycle, close the servers.

  • Use the http.Server.close(callback) method to stop the HTTP server from accepting new connections.
    https://nodejs.org/api/net.html#serverclosecallback
    Stops the server from accepting new connections and keeps existing connections. This function is asynchronous, the server is finally closed when all connections are ended and the server emits a 'close' event. The optional callback will be called once the 'close' event occurs. Unlike that event, it will be called with an Error as its only argument if the server was not open when it was closed.

=> Moved to #60 .
=> Fixed by #50 in 3776407

@nunoguedelha
Copy link
Collaborator Author

nunoguedelha commented Oct 25, 2021

B - Stop listening to client requests ("subscribe"/"unsubscribe" events) on the existing connections

Used net.socket.pause() (https://nodejs.org/api/net.html#socketpause) to stop reading request messages on the sockets. This seemed to be the simplest approach.

Indeed, net.socket.pause() is the implementation of websocket.pause(), and it...

pauses the reading of data. That is, 'data' events will not be emitted upon arrival of incoming packets (https://nodejs.org/api/net.html#socketpause).

The actual core implementation of pause is probably somewhere deep into engine.io and hard to find, but I believe it does the job. Tested it on the framework:

  • we replace the server closure steps in handleTermination() by telemServerTracker.pauseAll(); which calls pause() on the tracked sockets of the telemetry server.
    WebsocketTracker.prototype.pauseAll = function() {
        this.sockets.forEach((value,key) => {
            key.pause();
        });
    }
    function handleTermination(signal) {
        console.log('Received '+signal+' ...');
    
        telemServerTracker.pauseAll();
    }
  • if after opening the visualizer tab and clicking on a telemetry entry (e.g. "IMU measurements"), we hit <CTRL+C>, and then click on any new telemetry entry, the realtime data won't be updated, just the historic.
  • The historic requests were still working because we didn't close the server, which would keep it from listening to new connections (the history request always creates a new connection).

=> #52

Moved to #53 .

@nunoguedelha
Copy link
Collaborator Author

nunoguedelha commented Oct 27, 2021

Note

async/await (implementation based on Promises) are not supported on Node.js v4.2.2:

https://node.green/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Epic
Projects
None yet
Development

No branches or pull requests

1 participant