latysheff / node-sctp

SCTP userspace sockets for Node.js
MIT License
59 stars 10 forks source link

Error [ERR_MULTIPLE_CALLBACK]: Callback called multiple times #15

Open ibc opened 4 years ago

ibc commented 4 years ago

I'm getting the following fatal error. I don't know yet when/why it happens (on it). However, it's a fatal error because it's an internal unhandled "error" event, so it terminates the Node process.

Error [ERR_MULTIPLE_CALLBACK]: Callback called multiple times
    at onwrite (_stream_writable.js:465:11)
    at /Users/ibc/xxxxxxx/node_modules/sctp/lib/sockets.js:134:9
    at /Users/ibc/xxxxxxx/node_modules/sctp/lib/sockets.js:174:11
    at /Users/ibc/xxxxxxx/node_modules/sctp/lib/association.js:1658:11
    at /Users/ibc/xxxxxxx/node_modules/sctp/lib/association.js:1335:13
    at Array.forEach (<anonymous>)
    at /Users/ibc/xxxxxxx/node_modules/sctp/lib/association.js:1332:19
    at processTicksAndRejections (internal/process/task_queues.js:80:21)

Note that I'm setting an "error" listener in the node-sctp Socket instance, so the problem is that such a "error" exception is not being caught internally.

ibc commented 4 years ago

The data being sent via the SCTP stream is as follows:

data = '{"event":"bomberman-start-game","payload":{"id":"ibc@mycompany","mapName":"cold_map","layerInfo":{"name":"Blocks","width":28,"height":18,"x":0,"y":0,"data":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,3,3,3,3,2,1,2,3,2,3,2,3,2,3,2,3,2,3,1,2,3,3,3,2,3,1,1,3,2,1,1,3,2,2,3,2,3,2,3,2,3,2,3,2,3,2,1,3,3,2,2,2,3,1,1,2,3,3,3,3,3,2,3,1,1,1,1,3,1,1,1,1,2,3,2,3,3,1,1,1,2,1,1,1,1,1,2,2,2,2,3,3,3,2,3,3,3,3,3,2,3,3,1,2,3,3,3,3,3,1,1,3,3,3,2,3,3,3,3,2,2,3,3,2,1,1,3,3,2,3,1,2,3,3,2,3,2,1,1,2,2,3,1,1,1,1,2,2,1,1,1,1,1,1,2,2,1,1,3,2,1,1,1,2,1,1,1,2,2,3,2,3,3,1,3,3,1,1,3,3,2,2,3,3,2,2,3,3,2,1,3,3,3,1,1,1,1,1,2,3,3,1,3,2,3,3,2,3,3,3,3,3,3,3,3,3,1,1,3,2,2,1,1,3,3,3,3,3,3,1,3,2,2,2,2,3,1,1,1,1,1,2,3,3,1,2,3,2,1,1,1,3,2,1,1,2,3,1,3,3,3,3,3,3,1,3,3,3,3,2,3,2,3,2,3,2,2,1,1,1,2,2,2,2,3,1,1,1,2,2,2,1,1,2,3,2,3,2,3,2,3,2,2,3,3,1,1,3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,2,3,2,1,1,1,1,1,2,3,3,1,1,1,1,2,2,1,1,2,2,2,3,3,2,2,2,3,3,3,3,2,3,2,3,1,1,1,1,1,1,2,3,3,3,3,3,3,3,1,2,3,1,1,1,1,2,2,3,1,2,2,3,2,2,2,2,1,1,3,2,2,1,2,3,2,3,1,3,2,3,2,3,2,3,2,3,2,3,1,1,3,3,3,3,1,1,3,3,2,3,2,1,2,3,1,3,3,2,3,2,3,2,3,3,1,3,3,3,3,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"opacity":1,"type":"tilelayer","visible":true,"properties":{"max_players":16,"collisionTiles":[1,2],"empty":3,"balk":2,"wall":1,"spawns":[{"col":6,"row":3},{"col":8,"row":14},{"col":20,"row":15},{"col":22,"row":1},{"col":5,"row":5},{"col":3,"row":12},{"col":26,"row":7},{"col":19,"row":4},{"col":13,"row":4},{"col":16,"row":13},{"col":18,"row":10},{"col":2,"row":5},{"col":8,"row":8},{"col":20,"row":8},{"col":5,"row":9},{"col":25,"row":11}]}},"players":{"ijqwgukkizuKNHS5AAAA":{"id":"ijqwgukkizuKNHS5AAAA","spawnGroup":0,"spawn":{"x":192,"y":96},"position":{"x":192,"y":96},"spawnOnGrid":{"col":6,"row":3},"name":"Iñaki","isAlive":true,"power":1}},"playerSpawnsGroups":[[{"col":8,"row":14},{"col":20,"row":15},{"col":22,"row":1}],[{"col":5,"row":5},{"col":3,"row":12},{"col":26,"row":7},{"col":19,"row":4}],[{"col":13,"row":4},{"col":16,"row":13},{"col":18,"row":10},{"col":2,"row":5}],[{"col":8,"row":8},{"col":20,"row":8},{"col":5,"row":9},{"col":25,"row":11}]],"shadowMap":[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,2,0,0,0,0,2,1,2,0,2,0,2,0,2,0,2,0,2,0,1,2,0,0,0,2,0,1],[1,0,2,1,1,0,2,2,0,2,0,2,0,2,0,2,0,2,0,2,1,0,0,2,2,2,0,1],[1,2,0,0,0,0,0,2,0,1,1,1,1,0,1,1,1,1,2,0,2,0,0,1,1,1,2,1],[1,1,1,1,2,2,2,2,0,0,0,2,0,0,0,0,0,2,0,0,1,2,0,0,0,0,0,1],[1,0,0,0,2,0,0,0,0,2,2,0,0,2,1,1,0,0,2,0,1,2,0,0,2,0,2,1],[1,2,2,0,1,1,1,1,2,2,1,1,1,1,1,1,2,2,1,1,0,2,1,1,1,2,1,1],[1,2,2,0,2,0,0,1,0,0,1,1,0,0,2,2,0,0,2,2,0,0,2,1,0,0,0,1],[1,1,1,1,2,0,0,1,0,2,0,0,2,0,0,0,0,0,0,0,0,0,1,1,0,2,2,1],[1,0,0,0,0,0,0,1,0,2,2,2,2,0,1,1,1,1,1,2,0,0,1,2,0,2,1,1],[1,0,2,1,1,2,0,1,0,0,0,0,0,0,1,0,0,0,0,2,0,2,0,2,0,2,2,1],[1,1,2,2,2,2,0,1,1,1,2,2,2,1,1,2,0,2,0,2,0,2,0,2,2,0,0,1],[1,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,2,0,2,1,1,1,1,1,2,0,0,1],[1,1,1,2,2,1,1,2,2,2,0,0,2,2,2,0,0,0,0,2,0,2,0,1,1,1,1,1],[1,2,0,0,0,0,0,0,0,1,2,0,1,1,1,1,2,2,0,1,2,2,0,2,2,2,2,1],[1,0,2,2,1,2,0,2,0,1,0,2,0,2,0,2,0,2,0,2,0,1,1,0,0,0,0,1],[1,0,0,2,0,2,1,2,0,1,0,0,2,0,2,0,2,0,0,1,0,0,0,0,2,2,2,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],"spoils":{},"bombs":{}}}'

...which is 3214 bytes long.

And I'm using it as follows:

const sctp = require('sctp');
const dgram = require('dgram');

// Set node-sctp default PMTU to 1200. <----- UPPS ?
sctp.defaults({ PMTU: 1200 });

const udpSocket = dgram.createSocket({ type: 'udp4' });

// Connect the udpSocket and so on
// [...]

const sctpSocket = sctp.connect({
      localPort: 5000, // Required for SCTP over plain UDP in mediasoup.
      port: 5000, // Required for SCTP over plain UDP in mediasoup.
      OS: 128,
      MIS: 128,
      unordered: false,
      udpTransport: udpSocket, // previously created
      udpPeer: {
        address: remoteUdpIp,
        port: remoteUdpPort,
      },
    });

// Wait for the SCTP socket to be connected (SCTP association done)
// [...]

// Send data above
const buffer = Buffer.from(JSON.stringify(data));

// Set ppid of type WebRTC DataChannel string.
buffer.ppid = sctp.PPID.WEBRTC_STRING;

sctpStream.write(buffer);

BTW super confirmed that the error happens due the size of data (3214 bytes). It does not happen with smaller data. It also happens if I remove the sctp.defaults({ PMTU: 1200 }); line.

latysheff commented 4 years ago

Thanks for the snippet. I'll look into it.

ibc commented 4 years ago

NOTE: in order to install mediasoup you need this common requirements in your host. And then: npm install mediasoup@3

latysheff commented 4 years ago

If one need to control message boundaries (so that they have same SSN), we again need to hijack Buffer object, though it can be done internally, I hope. Currently, socket API splits chunks at about RWND size before feeding into SCTP API. This was done quite ugly, and must be rewritten.

Proper buffering with highWaterMark should be done in sockets, and SCTP API should allow arbitrary buffer size in SEND()

ibc commented 4 years ago

Currently, socket API splits chunks at about RWND size before feeding into SCTP API. This was done quite ugly, and must be rewritten.

Yep, a message given to write() must not be splitted into N SCTP messages because the remote application is supposed to receive a single "message" rather than 2 messages (that probably will be invalid).

ibc commented 4 years ago

Well, I've written a gist with a more complete test script and failing examples:

https://gist.github.com/ibc/17ed95cd2f08b84222c8820b034cd391

latysheff commented 4 years ago

Made comment on gist. Please use new highWaterMark (bytes) value in sctp.connect() for larger buffers. Also pushed version 0.19 of sctp package, addressing multiple callbacks.

Let me know your thoughts.