Async Send and Listen with amqp.node - amqp

I am trying to understand why when using amqp.node (https://github.com/squaremo/amqp.node), it seems like I queue all my messages before I ever dequeue or handle these messages. I would expect the sending and receiving to be interleaved.
send-receive-send-receive
But I get
send-send-receive-receive
While inspecting my processes I see that I will first go through all of the sends before i get to any of the receives. I have written a simple demonstration of the problem I'm having
var amqp = require('amqplib');
var prettyHrtime = require('pretty-hrtime');
var when = require('when');
var firstMesssage = false;
var time, diff, totaltime;
var received = 0;
amqp.connect('amqp://localhost', { noDelay: false } ).then(function(conn) {
process.once('SIGINT', function() { conn.close(); });
return conn.createChannel().then(function(ch) {
var ok = ch.assertQueue('hello', {durable: false});
ok = ok.then(function(_qok) {
return ch.consume('hello', function(msg) {
if(!firstMesssage){
time = process.hrtime();
firstMesssage = true;
}
received++;
console.log(new Date().getTime());
console.log(" [x] Received '%s' %s", msg.content.toString(), received);
if(received >= 10000){
diff = process.hrtime(time);
var words = prettyHrtime(diff);
console.log(words);
}
}, {noAck: true});
});
return ok.then(function(_consumeOk) {
console.log(' [*] Waiting for messages. To exit press CTRL+C');
});
});
}).then(null, console.warn);
var numberToSend = 10000;
var numberDone = 0;
amqp.connect('amqp://localhost', { noDelay: false } ).then(function(conn) {
return when(conn.createChannel().then(function(ch) {
var q = 'hello';
var msg = 'Hello World!';
var ok = ch.assertQueue(q, {durable: false});
function sendMsg(){
ch.sendToQueue(q, new Buffer(msg));
console.log(" [x] Sent '%s'", msg, new Date().getTime());
numberDone++;
}
return ok.then(function(_qok) {
while(numberDone < numberToSend){
sendMsg();
}
console.log('*** done');
return ch.close();
});
})).ensure(function() { conn.close(); });
}).then(null, console.warn);

Node is single threaded so it can't process any of the incoming messages until your while loop that does all the sending has finished.
Try wrapping your sendMsg in a setImmediate call and see how that changes things (it'll have to be converted to a promise and chained so that the channel and connect don't close before everything sends):
function sendMsg(resolve){
setImmediate(function() {
ch.sendToQueue(q, new Buffer(msg));
console.log(" [x] Sent '%s'", msg, new Date().getTime());
resolve();
});
}
return ok.then(function(_qok) {
var sendOk = when.promise(sendMsg);
while(numberDone < numberToSend){
sendOk = sendOk.then(function() {
return when.promise(sendMsg);
});
numberDone++;
}
return sendOk.finally(function() {
console.log('*** done');
ch.close();
});
});
You can see the difference in even just 5 messages:
[x] Sent 'Hello World!' 1428467734962
[*] Waiting for messages. To exit press CTRL+C
[x] Sent 'Hello World!' 1428467734963
1428467734965
[x] Received 'Hello World!' 1
[x] Sent 'Hello World!' 1428467734965
[x] Sent 'Hello World!' 1428467734965
[x] Sent 'Hello World!' 1428467734966
[x] Sent 'Hello World!' 1428467734966
*** done
1428467735004
[x] Received 'Hello World!' 2
1428467735005
[x] Received 'Hello World!' 3
1428467735005
[x] Received 'Hello World!' 4
1428467735005
[x] Received 'Hello World!' 5

Related

Debug queue AMQP that loses messages using low prefetch

My ampq system seems loosing messages, so I'd like a way to see if messages are effectively queued before being consumed.
I have several MicroServices communicating by amqp messages on NodeJs, using CloudAmqp. One of this microservice MS[B] generates .pdf, the process it's pretty heavy and requires about a minute for each request. So I send the .pdf asyncronously, triggering a webhook once finished, and generate once per time using a PreFetch = 1
So one MS[A] collects all the requests from the user, answers back to them saying "ok, request received, listen on the webhook" and in parallel it asks to the MS[B] to generate pdfs. MS[B] has prefetch=1, so consumes just one request per time. Once finished, sends the response to the callback queue of MS[A], which triggers the user webhook saying "the pdf, it's ready".
The problem is that MS[B] misses all the messages while busy:
it consumes one request from MS[A]
starts generating the .pdf
while generating, it discards all the other messages that MS[A] sends, as if there would be not any queue
it finishes the .pdf, sending ACK to MS[A]
then it starts again accepting messages, taking the last one received after being idle, losing all the previous ones.
Why? How can I find the problem, what could I monitor?
Communications between other MSs works well, with messages correctly ordered in queues. Just this one, with prefetch=1, loses messages.
I am NOT using the NO-ACK rule. I don't know what try, what test and what monitor to find the problem.
How can I see (if) messages are correctly queued before being consumed, ora just lost?
Below, the implementation of the messaging system
Channel Creation
/*
Starting Point of a connection to the CLOUDAMQP_URL server, then exec the callback
*/
start(callback) {
var self = this;
// if the connection is closed or fails to be established at all, we will reconnect
amqp.connect(process.env.CLOUDAMQP_URL + "?heartbeat=60")
.then(
function (conn) {
// create queues and consume mechanism
self.amqpConn = conn;
setTimeout(() => {
startPublisher();
}, 200);
setTimeout(() => {
createCallbackQueue();
}, 1000);
setTimeout(() => {
callback();
}, 2000);
});
// create publisher channels
function startPublisher() {
self.amqpConn.createConfirmChannel()
.then(function (ch) {
self.pubChannel = ch;
logger.debug("🗣️ pubChannel ready");
while (true) {
var m = self.offlinePubQueue.shift();
if (!m) break;
self.publish(m[0], // exchange
m[1], // routingKey
m[2], // content,
undefined // correlationId
);
}
});
}
// create callback channel
function createCallbackQueue() {
self.amqpConn.createChannel()
.then(function (channel) {
channel.assertQueue(self.CALLBACK_QUEUE_NAME, {
durable: true,
exclusive: true, // callback are exclusive
})
.then(function (q) {
logger.debug(" 👂 Waiting for RPC RESP in " + self.CALLBACK_QUEUE_NAME);
channel.consume(q.queue,
processCallback, {
noAck: false
}
);
});
// process messages of the callback
function processCallback(msg) {
var correlationId = msg.properties.correlationId;
}
//callback received
if (self.correlationIds_map[correlationId]) {
delete self.correlationIds_map[correlationId];
var content = JSON.parse(msg.content.toString());
self.eventEmitter.emit(correlationId, content);
}
}
});
}
return deferred.promise;
}
Consuming Messages
/*
#worker_queue - the name of the queue
*/
// Consume message from 'worker_queue', A worker that acks messages only if processed succesfully
startWorker(worker_queue, routes) {
var self = this;
logger.debug("startWorker " + self.CALLBACK_QUEUE_NAME);
var channel;
worker_queue = self.MICROSERVICE_NAME + worker_queue;
self.amqpConn.createChannel()
.then(
function (ch) {
channel = ch;
ch.prefetch(self.opt.prefetch); // = 1 for MS[B] generating pdf
channel.assertQueue(worker_queue, {
durable: true,
exclusive: true
})
.then(function (q) {
channel.consume(worker_queue, processMsg, {
noAck: false
});
});
});
// call the 'function from interface' passing params, and send the ACK
function processMsg(msg) {
work(msg)
.then(function (data) {
channel.ack(msg, false); // allUpTo = false
})
.catch(function (err) {
channel.ack(msg, false);
// channel.reject(msg, false); // requeue = false
// this.closeOnErr(e);
});
}
// execute the command, and queue back a response, checking if it's an error or not
function work(msg) {
var deferred = Q.defer();
var correlationId;
try {
correlationId = msg.properties.correlationId;
} catch (err) {}
work_function(msg.content, correlationId)
.then(function (resp) {
var content = {
data: resp
};
content = Buffer.from(JSON.stringify(content));
channel.sendToQueue(msg.properties.replyTo,
content, {
correlationId: correlationId,
content_type: 'application/json'
}
);
deferred.resolve(resp);
});
return deferred.promise;
}
}
Publish Messages
publish(exchange, routingKey, content, correlationId) {
var self = this;
var deferred = Q.defer();
self.correlationIds_map[correlationId] = true;
self.pubChannel.publish(exchange, routingKey, content,
{
replyTo: self.CALLBACK_QUEUE_NAME,
content_type : 'application/json',
correlationId: correlationId,
persistent : true
},
function(err, ok) {
if (err)
{
self.offlinePubQueue.push([exchange, routingKey, content]); // try again
self.pubChannel.connection.close();
deferred.resolve('requeued');
}
else
{
deferred.resolve(ok);
}
});
return deferred.promise;
}

How to wait for async function response in outlook addin

I am working on developing outlook addin using Javascript (newbie to JS).
After collecting all sendmail details, we have post the details to server and wait for the result.
Based on the result, the sendmail should be allowed / blocked.
getAllMailDetails() is the callback for ItemSend event in addin manifest file and outlook is calling my addin function when a new mail is sent.
But allowBlockMail() is immediately called without waiting for the async.
Since Javascript is single threaded (https://www.freecodecamp.org/news/async-await-javascript-tutorial/), how do I wait for the response to be included ?
let serverresp = "empty client";
function getAllMailDetails(event) {
runfetch(event);
allowBlockMail(event);
}
function allowBlockMail(event) {
item = Office.context.mailbox.item;
item.subject.getAsync(
{ asyncContext: event },
function (asyncResult) {
item.notificationMessages.addAsync('NoSend', { type: 'errorMessage', message: serverresp });
asyncResult.asyncContext.completed({ allowEvent: false });
return;
}
)
}
async function runfetch(event) {
let user = {
name: 'Addin Fetch 1234',
surname: 'Smith'
};
serverresp = "before POST";
try{
let response = await fetch('https://localhost:4431/something.aspx', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
},
body: JSON.stringify(user)
});
if (response.ok) { // if HTTP-status is 200-299
// get the response body (the method explained below)
serverresp = await response.text();
} else {
serverresp = "http-error";
}
}
catch(err) {
serverresp = "caught exception";
}
}
One of the simplest ways is in the getAllMailDetails function you just need to call the single function:
function getAllMailDetails(event) {
runfetch(event);
}
And when the fetch is done you may call the other one:
if (response.ok) { // if HTTP-status is 200-299
// get the response body (the method explained below)
serverresp = await response.text();
// your processing results
allowBlockMail(event);
} else {
serverresp = "http-error";
}

How to add custom success message in Mocha tests?

I have a loop inside a test case where I repeat a part of the test for each data sample.
How can I add a custom sucess message indicating that specific data, representing a test case, was sucessful?
For instance, my code is like:
it('Should not allow saving bad data: ', async function () {
let badData = TestDataProvider.allData.invalid;
for (let i = 0; i < badData.length; i++) {
let d = badData[i];
let res = await request.post('/data').send(d);
let object = null;
try {
object = JSON.parse(res.text);
} catch (e) {
object = {};
}
expect(res.statusCode).to.equal(206);
expect(object).not.to.contain.a.property('_id');
console.log('Verified case: ' + d.testDesc);
}
});
I want the "Verified case..." message to appear as successful test runs and not console messages in reports.
The testDesc attribute holds test description like: "Missing field a", "Invalid property b", "Missing field c".
I solved the problem creating dynamic tests:
const allData = JSON.parse(fs.readFileSync('data.json'), 'utf8'));
allData.invalid.forEach((badData) => {
it('Should not allow saving with: ' + badData.testDesc, async () => {
let res = await request.post('/data').send(badData);
let d = null;
try {
d = JSON.parse(res.text);
} catch (e) {
d = {};
}
expect(res.statusCode).to.equal(206);
expect(d).not.to.contain.a.property('_id');
});
});

Timeout fails to change flag used in while loop

I'm trying to emulate a synchronous ajax call by blocking for a short period of time and then flipping a flag in the AJAX return. But the flags don't get updated, even though the time limit is reached. Instead the browser just hangs. I've tested in Firefox and Safari with the same results.
Broken design:
function syncAjax(args, timeLimit) {
var obj = {},
outOfTime = false,
timeout, k,
response = {returned: false, failed: false, responseText: null};
timeout = window.setTimeout(function() {
outOfTime = true;
}, timeLimit);
for(k in args) {
if(args.hasOwnProperty(k)) {
obj[k] = args[k];
}
}
obj.success = function(responseText) {
window.clearTimeout(timeout);
response.returned = true;
response.responseText = responseText;
};
obj.failure = function() {
window.clearTimeout(timeout);
response.returned = true;
response.failed = true;
};
// obj["async"] = true; // (automatically async)
$.ajax(obj);
// insert spinner timeout
while(true) {
if(response.returned || outOfTime) break;
}
return response;
}
Sample usage:
function doIt() {
var response = syncAjax({
url: "webpage.php",
data: {x: 5},
}, 500);
if(!response.returned) {
// do time out stuff
} else if(response.failed) {
// do failed stuff
} else {
console.log(response.responseText);
// do success stuff with response.responseText
}
}
// !! executing this function will lock up your browser !!
// doIt();
javascript cannot call your timeout until you return from your function. setTimeout is not a threaded call.
You could do this for your loop:
var start = Date().getTime();
while( start+timeLimit > Date().getTime() ) ;

Fetching Images through Cloud Code

I've been scratching my head over this for a while. What am I doing wrong? Your help is much appreciated :)
I've tried many different image codes, but I think it's a promise issue I'm seeing. With the code below I only see the "Start of loop" log message.
If I move the results push outside the promise structure to underneath then I see the Stage log messages, albeit after all the Start of loops have printed (hence why I put the push in the then function).
Parse.Cloud.job("fetchjson", function(request, status) {
var url = 'some json url';
Parse.Cloud.httpRequest({url: url}).then(function(httpResponse){
//var Image = require("parse-image");
var Seeds = Parse.Object.extend("Seeds");
var jsonobj = JSON.parse(httpResponse.text);
var results = [];
// do NOT iterate arrays with `for... in loops`
for(var i = 0; i < jsonobj.seeds.length; i++){
var seed = new Seed();
var a = new Seed(jsonobj.seeds[i]);
console.log("Start of loop");
Parse.Cloud.httpRequest({url: a.get("image") }).then(function(response) {
console.log("Stage 1");
//var file = new Parse.File('thumb.jpg', { base64: response.buffer.toString('base64', 0, response.buffer.length) });
//return file.save();
return "hi"
}).then(function(thumb) {
console.log("Stage 2");
//a.set("imageFile", thumb);
//a.set("viewsInt", parseInt(a.get("views")));
}, function(error) {
console.log("Error occurred :(");
}).then(function(){
results.push(seed.save(a)); // add to aggregate
});
}
// .when waits for all promises
Parse.Promise.when(results).then(function(data){
status.success("All saved");
});
}, function(error) {
console.error('Request failed with response code ' + httpResponse.status);
status.error("Failed");
});
});

Resources