0

I have a problem with double emits when executing express (v4) route.

Lets for example run bellow code. Request (pipe) and mkdir will start immediately (race condition) and there will be error, about that the folder does not exist (mkdir can't create it fast enough, before request is started and start to pipe the data). Cause of this error user is redirected to page X where he need to refill the form. NOW (after redirection with form refilling) everything is working (cause folder do exist on the time that data arrives, because it was created before - cause async operations MUST complete? - route execute the code at the "back" even if user is already redirected) BUT the emits are done twice - once for OLD data, and once for NEW data (duplicating part of the HTML page structure).

route in x.js file:

router.post('/Y', function(req, res) {
    request(opt, function(err, resp, body) {
        if (resp.statusCode === 200) {
            res.render('Y'); // so the emits can target something (they build html structure on page 'Y' with proper data)

            function socCon()
            {
                var room = '';
                return new Promise(function(resolve, reject)
                {
                    req.io.on('connection', function(socket)
                    {
                        room = 'room-' + socket.id;
                        socket.join(room);

                        if (socket.join(room)) {
                            resolve(room);
                        }
                        else {
                            reject(console.log('error'));
                        }
                    });
                });
            }
            socCon().then(data).catch(function(error)
            {
                console.log(error);
            });

            function data(room) {

                var myFolder = 'myFolder/';

                fs.mkdir(myFolder, {recursive : true}, function(err) {
                    if (err) {console.log(err);}
                });

                var file = fs.createWriteStream(myFolder + 'data.html', 'utf8');

                sio.emit('msg', {emit : emit}); // socket.io emits - with use of express route POST on page 'X'

            });
        }
        else {
            res.render('X', {ERRORS : ERRORS});
            res.end();
        }
    }).on('response', function(response) {
    }).pipe(file).on('error', function(err) {
        res.render('X', {ERRORS : ERRORS}); // we inform the user that he put wrong data in input field or that there is internal server error (whatever)
        res.end();
        // so we end 'Y' page connection here (but socket.io somehow collect all the data, emit it later on - when same route is hit again) and socket.sendBuffer does not work)
    });
)};

Is there any way to stop the old emits? I have tried socket.sendBuffer = []; (@jfreind00 suggestion at - socket.io stop re-emitting event after x seconds/first failed attempt to get a response) to no avail. Ideal would be to... stop whole request when there is redirection. But you can't stop async code from executing (or I just don't know how).

Client page Y:

<!DOCTYPE html>
<html>
<head>
    <title>TITLE</title>
</head>
<body>
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
    socket.on('connect', function() {
        socket.sendBuffer = []; // does not work...
    });
    socket.on('msg', function(msg) {
        // some HTML mutation here
    });
</script>
</body>
</html>

Of course code is much more complicated, but this is enough (I think) to show the problem. This "application" is probably a bad design, but I don't know how to fix it.

index.js file (main "app" file):

var x = require('./routes/x.js');
var app = express();
app.use('/', x);
app.use(function(req, res, next) {
    req.io = io;
    next();
});
var io = require('socket.io')(server);

io.on('connection', function (soc) {
    soc.on('disconnect', function(reason) {
        console.log(reason);
    });
    // etc.
});
b4rtekb
  • 136
  • 7
  • I don't actually understand which particular problem you're asking about here. Please outline the exact steps that happen and what you want to happen. Does it start with browser goes to `http://somedomain.com/Y`, then what exactly happens? It's not clear who you're trying to `.emit()` to since you can't `.emit()` to a page that is in the process of being loaded by the browser. – jfriend00 Jul 01 '20 at 15:32
  • Also, what's the reason behind the `request()` and `.pipe()? What are you trying to do with that file you create? – jfriend00 Jul 01 '20 at 15:34
  • http://somedomain.com/X is a start domain with an input field. If user fill it, the route take him to http://somedomain.com/Y. At Y when he is browsing the page, results from request (in a route POST Y) are send and update the page. BUT sometimes (for example when folder is not created on time - user is redirected to page X where he need to fill the input again) emits are send twice. Request check and download the data, and I want to store the file (with requested data), so I can compare it with a new one later on. – b4rtekb Jul 01 '20 at 15:50
  • So, why don't you start by fixing the `mkdir()` problem that you apparently already know you have? Who gets double emits? I don't understand that part yet. – jfriend00 Jul 01 '20 at 15:58
  • Yes it is easily fixable by asyns/await, I just want to show the problem - but still the emits should not be doubled. User get double emits (old data, probably cause async code is runing at the "back" + new data that he pass just a moment ago). Also it happens when server for example give us error - when checking the same data again, user do get doubled/tripled etc. emits. Socket.io somehow know that this id is connected to this user (agent?) and even when every new socket io connection use new ID, it manage to connect old user ID with a new one (?) and emit the data. – b4rtekb Jul 01 '20 at 16:03
  • Do you realize that `sio.emit('msg', {emit : emit}); ` broadcasts to ALL connected users regardless of whether they are part of this transaction? – jfriend00 Jul 01 '20 at 16:21
  • @jfriend00 Can I send you some more complex data on email to not trash this post here? You can email me with some fake email (they last 10 days or so - b4rtekb@gmail.com) if you want. – b4rtekb Jul 01 '20 at 16:22
  • Also, do you realize that calling `res.render('Y')` immediately followed by `sio.emit('msg', {emit : emit}); ` will NOT emit to the page you just rendered because it's not yet fully initialized yet and doesn't yet have a socket.io connection. – jfriend00 Jul 01 '20 at 16:22
  • I keep all stackoverflow communication on stackoverflow, not in email. If it's relevant to the question here, put it in your question. It's not trashing the question to add relevant stuff to the question. – jfriend00 Jul 01 '20 at 16:23
  • I emit msg only to partical users (I use a room with an id). Hmmm I don't want to sound rude (I'm newbie) but it it working as if they emit exactly to the page I just rendered. – b4rtekb Jul 01 '20 at 16:24
  • Is the POST to the `/Y` route a browser post (no Javascript involved) or an Ajax post? In other words, is the page actually being reloaded when the `/Y` route is hit or not? There's still lots of stuff in this scenario that is not clear yet. – jfriend00 Jul 01 '20 at 16:26
  • All `res.render()` does is send a block of HTML. Before that page can actually receive socket.io messages it has to be parsed by the browser, then the browser has to fetch the resources that the page needs, then the browser has to run the Javascript, then the Javascript has to make a socket.io connection. None of that is done before you call `sio.emit(...)` on the next line of code. So, there's no way you're emitting to the HTML you just sent. If you're seeing an `emit()` in the client, it's to a page that already existed before the `res.render()` was called. – jfriend00 Jul 01 '20 at 16:28
  • It is normal post from the form - on action the is "/Y" as a target. I did add every piece of code from the modules it should be more clear now. – b4rtekb Jul 01 '20 at 16:58

1 Answers1

1

This line of code:

req.io.on('connection', function(socket) {...});

Just adds a new duplicate event handler every time you run this route. So, after running the route twice, you have two event handlers for the connection event. After running that route three times, you have three event handlers for the connection event and so on. They just accumulate and never go away.

So, that explains the duplicate processing. You have duplicate event handlers so the same connection event is processed multiple times.

I can't really suggest a fix because I don't follow what you're trying to accomplish and this whole scheme of trying to catch the next connection event and assuming that belongs to a specific page is flawed (full of race condition issues) - not to mention that it adds a never ending set of duplicate event handlers. You will need one listener for the connection event that is outside any route handler and then use that incoming event to do whatever you want to do.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • I wanted to connect with the user with socket.io and send the data only for him - without the connection and custom rooms I can only target ALL the users. And cause of disconnection of socket.io when moving to a new page (redirection) I did try to use route for this (save data from page X when moving to Y). I do have connection at index.js - but this is not working as I want it to work - user is disconnected when moving to '/Y' and data that need to be checked/downloaded is lost. I cannot maintain a socket.io connection when moving between pages. – b4rtekb Jul 01 '20 at 17:53
  • I still don't quite understand how socket.io emits target the same user, when running this route twice (cause there is different connection ID each time, user are automatically unbound from rooms when scoket.io connection is lost - how can old connection with void ID work?). When I move from page '/Y' to page '/X' (when no errors) and fill form again - it is working fine (same route). When I refresh page '/Y' it's working fine (again same route is used). – b4rtekb Jul 01 '20 at 17:54
  • Only when there is error (user is automatically redirected to page '/X') and use form (route) again there is emit duplication. Why does socket.sendBuffer = []; do not work? Sorry for being slow here it's all new for me. Thanks for your help. – b4rtekb Jul 01 '20 at 17:54
  • @b4rtekb - Do you understand how every time you run `req.io.on('connection', function(socket) {...});`, it add a new global socket.io handler for the `connection` event and since you never remove those event handlers, they accumulate so after hitting your route 5 times, you then have 5 copies of that event handler installed? So, every time the `connection` event occurs after that, you run the event handler code 5 times? Do you understand that fundamental flaw in how this is structured. This explains the duplicate messages you're seeing. – jfriend00 Jul 01 '20 at 19:58
  • @b4rtekb - You've never really described from first principles what you're trying to do here, but it appears to me, you need a completely different approach to solving that because you have multiple flawed assumptions and implementations in what you have here (race conditions, communication to web pages that aren't initialized yet, etc...). You simply can't do it this way. – jfriend00 Jul 01 '20 at 19:59
  • "req.io.on('connection', function(socket) {...});, it add a new global socket.io handler for the connection event and since you never remove those event handlers" when you redirect (move to other page in range of same domain), the socket.io connection is disconnected - so the event disappear, right? I understand that this way of doing thing is wrong. I try to figure out the correct setup that gives me the possibilities to build the "application" how I want AND without any major flaws. – b4rtekb Jul 01 '20 at 22:17
  • I have found two another topic that touch the subject (with you in a main role - again :) https://stackoverflow.com/questions/28773807/release-event-handlers-on-disconnection-of-socket-io-client https://stackoverflow.com/questions/33304856/how-to-remove-io-onconnection-listener – b4rtekb Jul 01 '20 at 22:17
  • What I try to achieve is to connect with the client (not all the clients) and send some data, get data from other servers, get some results from client, store mutated data in database. When move to another page where connection should be reused (reconnect) and socket should know that this is a particular user that previously received THAT data and application can resume previous task. – b4rtekb Jul 01 '20 at 22:18
  • Socket.io connections are NEVER reused when moving to a new page. The browser simply does not work that way. If you want to keep track of a given client as they go from one page to another, you probably need to use a cookie or a session so when they reconnect you can see which user they are from the cookie or session. There are lots of posts here about using express-session and then connecting that to your socket.io connection so you can access the session from socket.io events. – jfriend00 Jul 01 '20 at 22:22