socket.io: Using await within emit causes payload to be sent to all connected clients
You want to:
- report a bug
- request a feature
Current behaviour
Using async/await to construct payloads results in a race condition and messages potentially being broadcast to all connections.
Steps to reproduce
Fiddle: https://github.com/tommoor/socket.io-fiddle/tree/async-await-critical-issue
TLDR: Using await within the payload construction for emit will cause the message to be sent to all connections instead of the specific room. eg:
io.to('my-private-room').emit('event', {
data: await myDatabaseQuery()
});
Expected behaviour
Data should be sent to the correct room and not leaked to all clients.
Setup
- OS: All
- browser: All
- socket.io version: 2.2.0
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 8
- Comments: 21 (3 by maintainers)
@tommoor Not sure why this issue was highjacked but I’ve been dealing with an issue like this for the past few days.
The problem isn’t with
async/awaitdirectly. Usingasync/awaitwill work fine as long as you ensure.emit(..)or.clients(..)isn’t called elsewhere before the Promise resolves. This is because.to(..)doesn’t return a new instance, and.emit(..)and.clients(..)resetNamespace.prototype.rooms, which is set by.to(..).In your example:
The call order is:
After the first Promise resolves,
io.emit(..)sends toroomIdand clearsrooms. So subsequents emits send to every socket in the namespace.The simple fix for this is to move the
awaitbeforeio.to(..)like such:This yields a call order of:
Hope this helps
Sometime my app has issue :
Cannot read property 'emit' of undefinedA part of my code :What can I do to resolve this problem ? Thank you !
This was fixed by https://github.com/socketio/socket.io/commit/ac9e8ca6c71e00d4af45ee03f590fe56f3951186 and included in
socket.io@4.0.0Calling
io.to()(or any other broadcast modifier) will now return an immutable instance:Documentation: https://socket.io/docs/v3/migrating-from-3-x-to-4-0/#io-to-is-now-immutable
Hey, I see two possible problems with your code:
To solve this problem you’ll simply need to define/grab strangerSocket1 somewhere 😃. Hope this helped!
Thank you ! I think it’s a best answer ! Have a nice day 😄
I’m assuming
data.tois the socketId of the socket you want to emit to. If that’s the case, the correct way to emit from server would be:io.to(data.to).emit('typing', data)(so strangerSocket1 can be completely removed)I’m here because there is apparently a big bug but after reading I see absolutely no bug at all
What I do see is a very big misunderstanding of a fundamental and simple JavaScript evaluation order. If that evaluation order is taken into consideration the perceived bug turns out to live in the implementer code not socket.io
To be concise, we are talking about ‘method chaining’. i.e.
obj.chain_func1().chain_func2()where:objisiochain_func1istochain_func2isemitThe example provided and discussed is
io.to('my-private-room').emit('event', data), and the evaluation is as expected:.to()will run, and it return an instance toiowhich then;.emit()chained and given the input ofdatathat is expressed as a dictionary with anawaitfunction as a value - so the await functionsleep(100)executed 2nd and it’s return value is stored to the dictionary key.emit()with the value it was given and had executed in the 2nd orderI understand many who claim there is a bug have a perceived race-condition, and having
io.toimmutable is required to fix that. This evaluation order is extremely obvious to me, and not a race-condition bug. There is nothing that any JavaScript library can do about evaluation order of chaining and inputs.Maybe they could remove the ability for chaining? That would make the evaluation order of chaining a non-issue - but it seems so obvious (chaining evaluation is basic JavaScript syntax) that I am surprised this issue exists.
I really did not mean this to read like a mad burn to the people who claim there is a bug, I really genuinely intend this explanation to educate and help readers to see that there is no bug here at all.
Tip: you don’t understand chaining yet? or the evaluation order confuses you, then it is a good idea to simply avoid chaining and the so-called
work aroundis actually not a work-around in reality, it’s just the more explicit version of using socket.io that avoids chainingEDIT: the change in
ac9e8caand included insocket.io@4.0.0added a very nice feature, which does not actually change the evaluation order I describe above and there is still a perceived race-condition in the provided example. i.e.sleepwill still evaluate afterio.toand beforeio.emitso I’d still not rely on the value ofiopassed from.toto.emitto be synchronous in sequence… that’s not how JavaScript event loop works. So now that we haveio.toimmutable, we are giving.emita newioobject and in practice we have a non-standard approach that looks like chaining, but the instance ofiowas forked from the originalioinstance at.toand is no longer the same instance that was chained to.tobecause.tonow returns a fork of itSorry, I have some mistake when upload my code . I have been update it . And
strangerSocket1has been define belet strangerSocket1 = io.sockets.sockets[data.to];In detail : It’s is socket of free member from array of object .My issue
Cannot read property 'emit' of undefinedis sometimes happen not always. What can I do ? Thanks for your reponse .@darrachequesne I saw your fix for this issue with ac9e8ca and just wanted to mention that, as you already pointed out yourself, it is indeed not backwards compatible. In a codebase of mine I used a fragment like this:
which will break. I can imagine that I’m not the only one doing something like this, so I think this has to go into a semver major.
The behavior is indeed quite surprising and should be fixed IMO. I’m not sure if we could fix this in a backward-compatible way though.
Until then, I added a note here: https://socket.io/docs/v3/rooms/#Usage-with-asynchronous-code
To get around this for my case, I store socket object in-memory in a map of immutablejs with the roomID as they key. And then I emit directly to the sockets.
Yep, this seems right and how we resolved it. The bug is really bad though and leaked production data to the wrong user in my circumstance so felt it worth filing as a warning to others