Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Promise.all() not resolving when running server - otherwise works fine

I've written a small tool that returns a promise after calling several other promises. This tool works great when I test it solo, it takes about 10 seconds in the example below. However, when I try to run it along with a http server instance it, takes in the order of several minutes to return, if at all!

I'm fairly sure I'm just misunderstanding something here, as I'm not extremely proficient in Node. If anyone can spot an issue, or suggest an alternative to using promises for handling asynchronous methods, please let me know!

Just to clarify, it's the Promise.all returned by the traceRoute function which is hanging. The sub-promises are all resolving as expected.

Edit: As suggested in the comments, I have also tried a recursive version with no call to Promise.all; same issue.

This is a working standalone version being called without any http server instance running:

const dns = require('dns');
const ping = require('net-ping');

var traceRoute = (host, ttl, interval, duration) => {

    var session = ping.createSession({
        ttl:ttl,
        timeout: 5000
    });

    var times = new Array(ttl);
    for (var i=0; i<ttl; i++){
        times[i] = {'ttl': null, 'ipv4': null, 'hostnames': [], 'times': []}
    };

    var feedCb = (error, target, ttl, sent, rcvd) => {
        var ms = rcvd - sent;
        if (error) {
            if (error instanceof ping.TimeExceededError) {
                times[ttl-1].ttl = ttl;
                times[ttl-1].ipv4 = error.source;
                times[ttl-1].times.push(ms)
            } else {
                console.log(target + ": " +
                error.toString () +
                " (ttl=" + ttl + " ms=" + ms +")");
            }
        } else {
            console.log(target + ": " +
            target + " (ttl=" + ttl + " ms=" + ms +")");
        }
    }

    var proms = new Array();
    var complete = 0

    while(complete < duration){
        proms.push(
            new Promise((res, rej) => {
                setTimeout(function(){
                    session.traceRoute(
                        host,
                        { maxHopTimeouts: 5 },
                        feedCb,
                        function(e,t){
                            console.log('traceroute done: resolving promise')
                            res();  // resolve inner promise
                        }
                    );
                }, complete);
            })
        )
        complete += interval;
    }

    return Promise.all(proms)
    .then(() => {
        console.log('resolving traceroute');
        return times.filter((t)=> t.ttl != null);
    });
}


traceRoute('195.146.144.8', 20, 500, 5000)
.then( (times) => console.log(times) )

Below, is the same logic being called from inside the server instance, this is not working as it should. See the inline comment for where exactly it hangs.

const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({server: server, path: "/wss"});
const dns = require('dns');
const ping = require('net-ping');

var traceRoute = (host, ttl, interval, duration) => {

    var session = ping.createSession({
        ttl:ttl,
        timeout: 5000
    });

    var times = new Array(ttl);
    for (var i=0; i<ttl; i++){
        times[i] = {'ttl': null, 'ipv4': null, 'hostnames': [], 'times': []}
    };

    var feedCb = (error, target, ttl, sent, rcvd) => {
        var ms = rcvd - sent;
        if (error) {
            if (error instanceof ping.TimeExceededError) {
                times[ttl-1].ttl = ttl;
                times[ttl-1].ipv4 = error.source;
                times[ttl-1].times.push(ms)
            } else {
                console.log(target + ": " + 
                error.toString () + " (ttl=" + ttl + " ms=" + ms +")");
            }
        } else {
            console.log(target + ": " + target + 
            " (ttl=" + ttl + " ms=" + ms +")");
        }
    }

    var proms = new Array();
    var complete = 0

    while(complete < duration){
        proms.push(
            new Promise((res, rej) => {
                setTimeout(function(){
                    session.traceRoute(
                        host,
                        { maxHopTimeouts: 5 },
                        feedCb,
                        function(e,t){
                            console.log('traceroute done: resolving promise')
                            res();  // resolve inner promise
                        }
                    );
                }, complete);
            })
        )
        complete += interval;
    }

    console.log('Promise all:', proms);

    // #####################
    // Hangs on this promise
    // i.e. console.log('resolving traceroute') is not called for several minutes.
    // #####################
    return Promise.all(proms)
    .then(() => {
        console.log('resolving traceroute')
        return times.filter((t)=> t.ttl != null)
    });
}

wss.on('connection', function connection(ws, req) {

    traceRoute('195.146.144.8', 20, 500, 5000)
    .then((data) => ws.send(data));

});

app.use('/tools/static', express.static('./public/static'));
app.use('/tools/templates', express.static('./public/templates'));

app.get('*', function (req, res) {
    res.sendFile(__dirname + '/public/templates/index.html');
});

server.listen(8081);

Note: I have tried calling it before the server.listen, after server.listen, from inside wss.on('connection', .... None of which makes a difference. Calling it anywhere, while the server is listening, causes it to behave in a non-deterministic manner.

like image 509
Richard Dunn Avatar asked Aug 04 '17 15:08

Richard Dunn


2 Answers

I'm not going to accept this answer as it's only a workaround; it was just too long to put in the comments...

None of the promises, including the Promise.all, are throwing exceptions. However, Node seems to be parking the call to Promise.all. I accidentally discovered that if I keep a timeout loop running while waiting for the promise.all to resolve, then it will in fact resolve as and when expected.

I'd love if someone could explain exactly what is happening here as I don't really understand.

var holdDoor = true
var ps = () => {
    setTimeout(function(){
        console.log('status:', proms);
        if (holdDoor) ps();
    }, 500);
}
ps();

return Promise.all(proms)
.then(() => {
    holdDoor = false
    console.log('Resolving all!')
    return times.filter((t)=> t.ttl != null)
});
like image 93
Richard Dunn Avatar answered Nov 15 '22 16:11

Richard Dunn


Your code is working perfectly fine!

To reproduce this I've created a Dockerfile with a working version. You can find it in this git repository, or you can pull it with docker pull luxferresum/promise-all-problem.

You can run the docker image with docker run -ti -p 8081:8081 luxferresum/promise-all-problem. This will expose the webserver on localhost:8081.

You can also just run the problematic.js with node problematic.js and then opening localhost:8081 in the web browser.

The web socket will be opened by const ws = new WebSocket('ws://localhost:8081/wss'); which then triggers the code to run.

Its just very important to actually open the web socket, without that the code will not run.

like image 28
Lux Avatar answered Nov 15 '22 14:11

Lux