1
votes

I want to bombard the receiver with say 1M messages, but also that the receiver will get each message as soon as possible. The naive way is to loop 1M times and just send a message to the receiver with postmessage. Doesn't work.

What i get is that the whole 1M messages are queued, and only when the code finishes, the receiver starts processing them.

What i need to happen is that the sender will send 1M messages and as he keeps on sending the messages the receiver simultaneously will process them.

For example, what i have now is something like this:

  1. sender : send m1.
  2. sender : send m2.
  3. sender : send m3.
  4. receiver : received m1.
  5. receiver : received m2.
  6. receiver : received m3.

What i want:

  1. sender : send m1.
  2. receiver : received m1.
  3. sender : send m2.
  4. receiver : received m2.
  5. sender : send m3.
  6. receiver : received m3.

How can i achieve this? I can not make the receiver send acks. My goal is to send as many massages as i can the fastest.

Edit: The code i have now:

Sender:
function sendx(x){
    console.log("start spam");
    for(let i=0; i<200000; i++){
     window.opener.postMessage(x, '*');
    }
    console.log("done");
}


Receiver:
window.addEventListener("message", r_function );
function r_function(event)
{
    let index = event.data;
    let junk = something(index);
    return junk;
}

Where the sender is a new window created by the receiver. What i get in practice is that only when the 'sendx' function ends, the receiver start receiving messages.

1
What you are explaining is not possible. The moment your "sender" sends something, your "receiver" should start processing it. So you're either sending all messages at once or your receiver is waiting for all messages to be received before it starts processing.icecub
Anyway, in order to achieve what you want according to your example, you're going to need Ajax for this. Because the only way to get that done in that exact order is for the client to send the first message and the server to tell the client when its done processing it so the client can send the second message.icecub
I'll clarify - I want the receiver to start processing as soon as possible, but i don't care when he will finish. Basically any other option other than what i have now. The best case for me is like a TCP connection where the bottleneck is in the network rather than in the sender.ogabona
Where is this? If this is for web-workers, multi-threading should take care of it.Avin Kavish
You should probably show us your code so we can give you any meaningfull answer. Obviously don't show us all the messages, just 3 or so should be enough. Show us how it's being send and how your server processes it. Right now there's just not enough information to give you any answer that would apply to your specific situation.icecub

1 Answers

1
votes

What i need to happen is that the sender will send 1M messages and as he keeps on sending the messages the receiver simultaneously will process them.

That's what happens already.

const worker = new Worker(URL.createObjectURL(
  new Blob([worker_script.textContent])
 ));
let logged_first = false;
worker.onmessage = e => {
  if(e.data === "spam") {
    if(!logged_first) {
      console.log('received first message at', new Date().toLocaleString());
      logged_first = true; // ignore next messages
    }
  }
  else {
    console.log(e.data);
  }
}
<script type="text/worker-script" id="worker_script">
  const now = performance.now();
  postMessage("start spamming at " + new Date().toLocaleString());
  while(performance.now() - now < 5000) {
    postMessage('spam');
  }
  postMessage("end spamming at " + new Date().toLocaleString());
</script>

However, for it to work, there is one big condition that needs to be met:
Your two JavaScript instances (sender & receiver) must run on different threads.

That is, if you were doing it using a MessageChannel on the same thread, then it would obviously be unable to treat the messages at the same time it's sending it:

const channel = new MessageChannel();

channel.port1.onmessage = e => {
  console.log('received first message at', new Date().toLocaleString());
  channel.port1.onmessage = null; // ignore next messages
};

const now = performance.now();
console.log("start spamming at ", new Date().toLocaleString());

while(performance.now() - now < 5000) {
  channel.port2.postMessage('spam');
}

console.log("end spamming at ", new Date().toLocaleString());

And if you are dealing with an iframe or an other window, you can not be sure that you'll meet this condition. Browsers all behave differently, here, but they will all run at least some windows on the same process. You have no control as to which process will be used, and hence can't guarantee that you'll run in an other one.

So the best you can do, is to run your loop in a timed-loop, which will let the browser some idle time where it will be able to process other windows event loops correctly.
And the fastest timed-loop we have is actually the one postMessage offers us. So to do what you wish, the best would be to run each iteration of your loop in the message event of a MessageChannel object.

For this, generator function* introduced in ES6 are quite useful:

/***************************/
/* Make Fake Window part   */
/* ONLY for DEMO           */
/***************************/
const fake_win = new MessageChannel();
const win = fake_win.port1; // window.open('your_url', '')
const opener = fake_win.port2; // used in Receiver

/********************/
/* Main window part */
/********************/
const messages = [];
win.onmessage = e => {
  messages.push(e.data);
};
!function log_msg() {
  document.getElementById('log').textContent = messages.length;
  requestAnimationFrame(log_msg);
}();

/*******************/
/* Receiver part   */
/*******************/

// make our loop a Generator function
function* ourLoopGen(i) {
  while(i++ < 1e6) {
    opener.postMessage(i);
    yield i;
  }
}
const ourLoop = ourLoopGen(0);

// here we init our time-loop
const looper = new MessageChannel();
looper.port2.onmessage = e => {
  const result = ourLoop.next();
  if(!result.done)
    looper.port1.postMessage(''); // wait next frame
};
// start our time-loop
looper.port1.postMessage('');
<pre id="log"></pre>

We could also do the same using ES6 async/await syntax, since we can be sure that nothing else in our MessageChannel powered timed-loop will interfere (unlike in a Window's postMessage), we can promisify it:

/***************************/
/* Make Fake Window part   */
/* ONLY for DEMO           */
/***************************/
const fake_win = new MessageChannel();
const win = fake_win.port1; // window.open('your_url', '')
const opener = fake_win.port2; // used in Receiver

/********************/
/* Main window part */
/********************/
const messages = [];
win.onmessage = e => {
  messages.push(e.data);
};
! function log_msg() {
  document.getElementById('log').textContent = messages.length;
  requestAnimationFrame(log_msg);
}();

/*******************/
/* Receiver part   */
/*******************/

const looper = makeLooper();
// our async loop function
async function loop(i) {
  while (i++ < 1e6) {
    opener.postMessage(i);
    await looper.next()
  }
}
loop(0);

// here we init our promisified time-loop
function makeLooper() {
  const engine = new MessageChannel();
  return {
    next() {
      return new Promise((res) => {
        engine.port2.onmessage = e => res();
        engine.port1.postMessage('');
      });
    }
  };
};
<pre id="log"></pre>

But it could obviously also be made entirely ES5 style with callbacks and everything:

/***************************/
/* Make Fake Window part   */
/* ONLY for DEMO           */
/***************************/
var fake_win = new MessageChannel();
var win = fake_win.port1; // window.open('your_url', '')
var opener = fake_win.port2; // used in Receiver

/********************/
/* Main window part */
/********************/
var messages = [];
win.onmessage = function(e) {
  messages.push(e.data);
};
!function log_msg() {
  document.getElementById('log').textContent = messages.length;
  requestAnimationFrame(log_msg);
}();

/*******************/
/* Receiver part   */
/*******************/

var i = 0;
var looper = makeLooper(loop);
// our callback loop function
function loop() {
  if (i++ < 1e6) {
    opener.postMessage(i);
    looper.next(loop);
  }
}
loop(0);

// here we init our promisified time-loop
function makeLooper(callback) {
  var engine = new MessageChannel();
  return {
    next: function() {
      engine.port2.onmessage = function(e) {
        callback();
      }
      engine.port1.postMessage('');
    }
  };
};
<pre id="log"></pre>

But note that browsers will anyway throttle the pages that are not in focus, so you may have slower results than in these snippets.