JavaScript: Using a queue for network communication - javascript

I'm working on a project in which a client must be able to communicate with a server via WebSockets. Since the application which we develop as to be highly responsive on user input we have decided to let a WebWorker do all the communication over the network so that a slow connection cannot interrupt the GUI. That works fine so far.
Now we thought about optimizations which will be necessary if the network is slow and the amount of messages to send is high. Since most of these messages will be only to synchronize some other clients user interface we can drop some of them if necessary. But to do so we need the possibility to detect the situations when there is congestion.
We came up with the idea of a queue: Each message to be sent is pushed in a queue and the WebWorker does nothing else than permanently iterating over the queue and sending all the messages it finds there. With that we can later let the Worker act differently if there is a certain number of elements in the queue (i.e. messages are sent too slowly). The idea is simple and straightforward, however, the implementation doesn't seem to be.
var ws;
var queue = new Array();
function processMessageQueue() {
while(true)
if(queue.length > 0) ws.send(queue.shift());
}
self.addEventListener('message', function(e) {
var msg = e.data;
switch(msg.type) {
case 'SEND':
queue.push(JSON.stringify(msg));
break;
case 'CREATE_WS':
ws = new WebSocket(msg.wsurl);
ws.onmessage = function(e) {
self.postMessage(JSON.parse(e.data));
}
processMessageQueue();
}
}, false);
As you can see, the worker creates the WebSocket as soon as it received the message to do so. It then executes the function processMessageQueue() which is the loop which permanently empties the queue by sending their data via the WebSocket. Now, my problem is that there seems to be no possibility to push messages into the queue. That should happen when a message of type 'SEND' arrives but it cannot for the Worker is too busy to handle any events. So it can either loop over the queue or push messages, not both.
What I need is a way to somehow push data on this queue. If that is not easily possible I would like to know if anyone can think of another way to find out when messages arrive faster then they are sent. Any hints?
Thanks in advance!

You have to give the worker time to handle the onmessage event. Doing while(true) will take all the processing time and not allow any events to be executed.
You could for instance change it to
function processMessageQueue() {
if(queue.length > 0) ws.send(queue.shift());
setTimeout(processMessageQueue, 50);
}

Related

Only one message is received from SQS ( nodejs aws sdk)

I created an SQS with default settings. I published two messages to it, and I would like to read them back in the same time. I tried it like this:
const sqsClient = new SQSClient({ region: REGION });
const params = {
AttributeNames: ["SentTimestamp"],
MaxNumberOfMessages: 5,
MessageAttributeNames: ["All"],
QueueUrl: queueURL,
WaitTimeSeconds: 5,
};
const data = await sqsClient.send(new ReceiveMessageCommand(params));
const messages = data.Messages ?? [];
console.log(messages.length);
Unfortunately only one message is returned, no matter what I provide in MaxNumberOfMessages. What can cause this? How is it possible to fix this issue?
I was able to find a similar question, but it has only one answer, refering to a 3rd party library.
A ReceiveMessageCommand does not guarantee that you will get exactly the number of messages specified for MaxNumberOfMessages. In fact the documentation says the following:
Short poll is the default behavior where a weighted random set of machines is sampled on a ReceiveMessage call. Thus, only the messages on the sampled machines are returned. If the number of messages in the queue is small (fewer than 1,000), you most likely get fewer messages than you requested per ReceiveMessage call. If the number of messages in the queue is extremely small, you might not receive any messages in a particular ReceiveMessage response. If this happens, repeat the request.
You must use long-polling to receive multiple messages. This is essentially setting the WaitTimeSeconds to a greater value (5 seconds should be enough).
And you must have a larger number of messages in the queue to be able to fetch multiple messages with one call.
To summarize:
SQS is a distributed system, each call will poll one machine only.
Messages are distributes on those machines, if you have a small number of messages, it might happen that you fetch only one message, or none.
Test your code with a larger set of sent messages and put your receiving call in loop.

Better way to schedule cron jobs based on job orders from php script

So I wrote simple video creator script in NodeJS.
It's running on scheduled cron job.
I have a panel written in PHP, user enter details and clicks "Submit new Video Job" Button.
This new job is saving to DB with details, jobId and status="waiting" data.
PHP API is responsible for returning 1 status at a time, checks status="waiting" limits query to 1 then returns data with jobID when asked
Video Creation Script requests every x seconds to that API asks for new job is available.
It has 5 tasks.
available=true.
Check if new job order available (With GET Request in every 20 seconds), if has new job;
available=false
Get details (name, picture url, etc.)
Create video with details.
Upload Video to FTP
Post data to API to update details. And Mark that job as "done"
available=true;
These tasks are async so everytask has to be wait previous task to be done.
Right now, get or post requesting api if new job available in every 20 seconds (Time doesnt mattter) seems bad way to me.
So any way / package / system to accomplish this behavior?
Code Example:
const cron = require('node-cron');
let available=true;
var scheduler = cron.schedule(
'*/20 * * * * *',
() => {
if (available) {
makevideo();
}
},
{
scheduled: false,
timezone: 'Europe/Istanbul',
}
);
let makevideo = async () => {
available = false;
let {data} = await axios.get(
'https://api/checkJob'
);
if (data == 0) {
console.log('No Job');
available = true;
} else {
let jobid = data.id;
await createvideo();
await sendToFTP();
await axios.post('https://api/saveJob', {
id: jobid,
videoPath: 'somevideopath',
});
available = true;
}
};
scheduler.start();
RabbitMQ is also a good queueing system.
Why ?
It's really well documented (examples for many languages including javascript & php).
Tutorials are simple while they're exposing real use cases.
It has a REST API.
It ships with a monitoring UI.
How to use it to solve your problem ?
On the job producer side : send messages (jobs) to a queue by following tutorial 1
To consume jobs with your nodejs process : see RabbitMQ's tutorial 2
Other suggestions :
Use a prefetch value of 1 and publisher confirms so you can ensure that an instance of consumer will not receive messages while there's a job running.
Roadmap for a quick prototype : tutorial 1... then tutorial 2 x). After sending and receiving messages you can explore the options you can set on queues and messages
Nodejs package : http://www.squaremobius.net/amqp.node/
PHP package : https://github.com/php-amqplib/php-amqplib
While it is possible to use the database as a queue, it is commonly known as an anti-pattern (next to using the database for logging), and as you are looking for:
So any way / package / system to accomplish this behavior?
I use the free-form of your question thanks to the placed bounty to suggest: Beanstalk.
Beanstalk is a simple, fast work queue.
Its interface is generic, but was originally designed for reducing the latency of page views in high-volume web applications by running time-consuming tasks asynchronously.
It has client libraries in the languages you mention in your question (and many more), is easy to develop with and to run in production.
What you are doing in a very standard system design paradigm, done with Apache Kafka or any queue based implementation(ex, RabbitMQ). You can check out about Kafka/rabbitmq but basically Not going into details:
There is a central Queue.
When user submits a job the job gets added to the Queue.
The video processor runs indefinitely subscribing to the queue.
You can go ahead and look up : https://www.gentlydownthe.stream/ and you will recognize the similarities on what you are doing.
Here you don't need to poll yourself, you need to subscribe to an event and the other things will be managed by the respective queues.

IBM MQ cmit and rollback with syncpoint

Infra-Overview:
I have a setup where I am reading a set of messages from IBM MQ and processing those messages in k8 cluster env and sending it to the destination host.
Issue:
I observed that sometimes the flow of the messages is huge and before sending it to the destination host our pod gets failed and restarts, by this we are losing all the messages as we are following a read-and-delete approach from ibmmq example
Expected Solution:
I am looking for a solution where, until these messages are sent to the destination host, we don't lose the track of the messages.
What I tried:
We have a concept of unit of work in IBM MQ but since we can't expect a delay in reading and processing, I can't wait for a single message to get processed and then read the another message as it might have a major performance setback.
Code language:
NodeJs
As the comments suggest there are a number of ways to skin this cat, but you will need to use transactions.
As soon as you create the connection with the transaction option, the transaction scope begins. This gets closed and next transaction begins when you either commit or rollback.
So you should handle the messages in batches, that make sense to your application, and commit when the batch is complete. If your application is killed by k8s then all uncommitted read messages will get rolled back, via back out queue process to stop poison messages.
Section added to show sample code, and explanation of backout queues.
In your normal processing, if an app gets stopped before it has had time to process the message, you will want that message returned to the queue. So that the message is still available to be processed.
To enable this rollback you need to or in the MQC.MQPMO_SYNCPOINT into the get message options
gmo.Options |= MQC.MQGMO_SYNCPOINT
Then if all goes well, you can commit.
mq.Cmit(hConn, function(err) {
if (err) {
debug_warn('Error on commit', err);
} else {
debug_info('Commit was successful');
}
});
or rollback
mq.Back(hConn, function(err) {
if (err) {
debug_warn('Error on rollback', err);
} else {
debug_info('rollback was successful');
}
});
If you rollback, the message goes back to the queue. Which means it is also the next message that your app will read. This can generate a poison message loop. So you should also set up a backout queue with pass all context permissions for your app user and a backout threshold.
Say you set the threshold to 5. The message can be read 5 times, with rollback. Your app needs to check the threshold and decide that it is a poison message and move it off the queue.
To check the backout threshold (and the backout queue name) you can use the following code
// Remember to or in the Inquire option on the Open
openOptions |= MQC.MQOO_INQUIRE;
...
attrs = [ new mq.MQAttr(MQC.MQIA_BACKOUT_THRESHOLD),
new mq.MQAttr(MQC.MQCA_BACKOUT_REQ_Q_NAME) ];
mq.Inq(hObj, attrs, (err, selectors) => {
if (err) {
debug_warn('Error retrieving backout threshold', err);
} else {
debug_info('Attributes have been found');
selectors.forEach((s) => {
switch (s.selector) {
case MQC.MQIA_BACKOUT_THRESHOLD:
debug_info('Threshold is ', s.value);
break;
case MQC.MQCA_BACKOUT_REQ_Q_NAME:
debug_info('Backout queue is ', s.value);
break;
}
});
}
});
When getting the message your app can use mqmd.BackoutCount to check how often the message has been rolled back.
if (mqmd.BackoutCount >= threshold) {
...
}
What I have noticed, that if this is in the same application instance that is repeatedly calling rollback on the same message, then at the threshold a MQRC_HOBJ_ERROR error is thrown. Which your app can check for, and then discard the message.
If its a different app instance then it doesn't get the MQRC_HOBJ_ERROR error, so it can check the backout threshold and can discard the message, remembering to commit the discard action.
See https://github.com/ibm-messaging/mq-dev-patterns/tree/master/transactions/JMS/SE for more information.
As an alternative you could use keda - https://keda.sh - which works with k8s
to monitor your queue depth and scale according to the number of messages waiting to be processed, as opposed to CPU / memory consumption. That way you can scale up when there are lots of messages waiting to be processed, and slowly scale down then the queue becomes manageable. Here is a link to getting started - https://github.com/ibm-messaging/mq-dev-patterns/tree/master/Go-K8s - the example is for a Go app, but equally applies to Node.js

How to keep a websocket connection in OPEN readystate after send?

I am new to websockets.
In my setup I have a trivial websocket server written in Go (playground)
I make a WebSocket object, set up its onmessage callback and call its send method to test.
var w = new WebSocket("ws://localhost:12345/echo")
w.onmessage = (msg) => {
console.log(msg.data)
}
w.onopen = () => {
w.send("Hello") // this fires OK
}
What I expect to happen based on the server code is to receive the "Hello" message and to keep sending "yahoo" every 1.5s to the client. What actually happens is "Hello" is sent, but none of the "yahoo"'s make it thru. It seems somewhere along the WebSocket.readystate becomes 3 (CLOSED).
To clarify, the server receives and prints "Hello" then actually fires a "yahoo" message every 1.5s, but the connection is closed by then so the onmessage callback never fires.
Am I missing or misunderstanding anything?
EDIT: Ran across comparison github.com/gorilla vs. golang.org/x/net, claims golang.org/x/net websocket implementation does not support pong. This may be the confirmation of it.
EDIT: Package golang.org/x/net/websocket closes the websocket connection when the handler ServeHTTP function returns. By default a websocket connection is tied to an instance of the handler.
When the handler function returns, in your case EchoServer the socket will be automatically closed by the http framework.
Since you start a go routine for the loop writing the yahooresponse to the client the EchoServer function will terminate (and therefor closing the socket) before it has time to send a response.
The solution is to remove the spawning of the go routine and just do the loop inside the EchoServer.

How can I pick out a particular server message from a websocket connection?

I have a WebSocket connection set up for a basic web chat server.
Now, whenever a message is received, it is sent to a function which outputs it on the screen.
socket.onmessage = function(msg){output(msg);}
However, there are certain technical commands which the user can send to the server through the connection which elicit a technical response, not meant to be output on the screen.
How can I grab the server response which immediately follows one of these technical messages?
Do I put a separate socket.onmessage right after the block of code which sends the technical message? I imagine that would take all future messages. I just want the next one.
Ideas?
WebSockets is asynchronous so trying to get the 'next' message received is not the right solution. There can be multiple message in flight in both directions at the same time. Also, most actions in Javascript are triggered by asynchronous events (timeout firing, or user clicking on something) which means you don't have synchronous control over when sends will happen. Also, the onmessage handler is a persistent setting: once it is set it receives all messages until it is unset. You need to have some sort of way of distinguishing control messages from data messages in the message it self. And if you need to correlate responses with sent messages then you will also need some kind of message sequence number (or other form of unique message ID).
For example, this sends a control message to the server and has a message handler which can distinguish between control and message and other messages:
var nextSeqNum = 0;
...
msg = {id: nextSeqNum, mtype: "control", data: "some data"};
waitForMsg = nextSeqNum;
nextSeqNum += 1;
ws.send(JSON.stringify(msg));
...
ws.onmessage = function (e) {
msg = JSON.parse(e.data);
if (msg.mtype === "control") {
if (msg.id === waitForMsg) {
// We got a response to our message
} else {
// We got an async control message from the server
}
} else {
output(msg.data);
}
};
You can packetise the data, I mean, by a special character/s, form a string like this :
"DataNotToBeShown"+"$$"+"DataToBeShown"; //if $$ is separating character
And then, you can split the string in javascript like this :
var recv=msg.data.split('$$');
So, the data not be shown is in recv[0] and data to be shown, in recv[1]. Then use however you want.

Categories