0

I have a Node-Webkit based application, which does some heavy WebGL work (enough to stress out a high-end GPU), a fairly significant amount of JS processing, and I'm sending OSC data on the order of ~4kb/s with the node dgram module to an scsynth child process to control audio.

The OSC data is contained in one bundle every 10 frames of animation and the audio is fairly tolerant of some latency or irregularity in the messages it receives, but not on the scale I'm experiencing.

Unfortunately I find that significantly often, there is a large delay between calling socket.send(...) and the data actually being sent. It seems that under certain circumstances the scheduler places such a low priority on actually sending the data that each new packet becomes stalled almost indefinitely, before being suddenly released in large uncontrolled bursts that overflow the scsynth command queue.

I cannot put the udp code into a WebWorker as node.js objects do not work in that context. I'm considering attempting setting up a separate window (and as such, process) solely responsible for forwarding data received via window.postMessage to UDP (and vice-versa), but since postMessage itself is also asynchronous, and the other window itself will probably have low priority if it's not visible, I wonder if this is likely to provide much benefit.

I'm pretty sure that the main problem lies in the scheduling of work in Javascript rather than anywhere else in the process; I see no particular implication of trouble at the receiving end, although perhaps this could be assessed more closely.

Here's a brief snippet showing how the socket is setup and used (including gathering some basic stats on the send callback).

udp = require('dgram').createSocket('udp4');
//...
var udpStats = {lastSendDelay: 0, minSendDelay:Number.MAX_VALUE, maxSendDelay:-1, meanSendDelay:undefined};
var udpSend = function(buf) {
    var t = new Date();
    var wasSent = function(timeOfRequest) {
        return function(err) {
            if ((err)) sclog("UDP send Error: " + err);
            var t2 = new Date();
            var dt = t2 - timeOfRequest;
            udpStats.lastSendDelay = dt;
            udpStats.minSendDelay = Math.min(dt, udpStats.minSendDelay);
            udpStats.maxSendDelay = Math.max(dt, udpStats.maxSendDelay);
            udpStats.meanSendDelay = udpStats.meanSendDelay === undefined ? dt : (udpStats.meanSendDelay+dt)/2;
        };
    };

    udp.send(buf, 0, buf.length, UDP_PORT, 'localhost', wasSent(t));
};
PeterT
  • 1,454
  • 1
  • 12
  • 22
  • If you can provide a runnable case reproducing this, I'll try to fix it. – Roger Wang Sep 11 '13 at 04:56
  • Thanks. The actual project is fairly big and hairy, but I might be able to force it to be reproduced in something simpler, which would also help eliminate parts of the system I have been assuming to be unrelated. Not sure how long this will take me, I'm just going to have to resort to static audio files for the actual project for now, but would still like to get the architecture working. I've tried using the net module with tcp but that's not working; maybe the subject for a different question. – PeterT Sep 11 '13 at 06:51
  • I've just realised it's you I'm talking to, Roger. Thanks again for offering to help. Does my reasoning about the lower level of socket.send being considered low priority and therefore not scheduled ring true? – PeterT Sep 11 '13 at 06:57
  • I don't think it's scheduled in low priority. btw, what's your OS? – Roger Wang Sep 12 '13 at 03:04
  • The machines I had the main problems with were Windows 7 64bit. – PeterT Sep 12 '13 at 11:20

1 Answers1

1

In case it helps anybody else, we resolved this by replacing calls to requestAnimationFrame to calls with a timeout of 1ms and requestAnimationFrame. That permitted other things (such as udp send/receive processing) to squeeze in on the thread in the 1ms dealy, without significantly impacting overall fps.

~~~

p.s. Using nw 8.5 I found that deferring requestAnimationFrame with setTimeout worked best, giving typical OSC roundtrip times of 2ms. Deferring with process.setTick gave OSC roundtrip times of around 20ms. Calling requestAnimationFrame directly caused the almost complete block of OSC messages.

Using nw 12.1 there was no issue, and all three variants behaved almost exactly the same with typical roundtrip times of 2ms.

We have been constrained to use nw 8.5 for some time as our webGL code would not work with ANGLE and later versions of nw would not work with -use-gl=desktop. With the latest versions of nw, ANGLE is working better and -use-gl=desktop is also working again.

Stephen Todd
  • 365
  • 3
  • 12
  • What about `process.nextTick()`? – Gabriel Tomitsuka May 27 '15 at 10:08
  • As pointed out by Peter T, 'other things' is more specifically ' events queued in the Node context'. Thanks for the suggestion, Gabriel. I will try process.nextTick(). As our application is GPU bound with relatively little javascript cpu cost I suspect it won't make much difference, but always good to learn a bit extra. nextTick may now even work. – Stephen Todd May 30 '15 at 07:45
  • stackoverflow did not let me finish editing that last comment ... nextTick() may not even work. The documentation says: "_This is important in developing APIs where you want to give the user the chance to assign event handlers after an object has been constructed, but before any I/O has occurred._" The reason for the setTimeout() was to make sure I/O **does** occur. – Stephen Todd May 30 '15 at 07:55