I have a weird problem where my callback is never published and the message goes to timeout, even though the method runs in the queue. This happens in some specific queues and after it happens once, i cannot make any other requests from client which even previously worked, they all timeout. Have to restart the client and sever to make it working again.
This is the code, where its happening, and i cant seem to understand whats wrong.
Server.js file where i am creating the queues. I have several such queues, this is one of them.
var amqp = require('amqp');
var util = require('util');
var cnn = amqp.createConnection({host:'127.0.0.1'});
var getCart = require('./services/getCart');
cnn.on('ready', function() {
cnn.queue('getCart_queue', function(q){
q.subscribe(function(message, headers, deliveryInfo, m){
// util.log(util.format( deliveryInfo.routingKey, message));
// util.log("Message: "+JSON.stringify(message));
// util.log("DeliveryInfo: "+JSON.stringify(deliveryInfo));
getCart.handle_request(message, function(err,res){
cnn.publish(m.replyTo, res, {
contentType:'application/json',
contentEncoding:'utf-8',
correlationId:m.correlationId
});
});
});
});
});
Here, the handle request function is completed successfully, but the callback never goes through and its always timeout on the other end
var cart = require('../models/cart');
function handle_request(msg, callback) {
var user_id = msg.id;
cart
.find({id:user_id})
.populate('users ads')
.exec(function(err, results){
// This works, just the callback doesnt
if(!err){
console.log(results);
callback(null, results);
} else {
console.log(err);
callback(err, null);
}
});
}
exports.handle_request = handle_request;
this is how i am calling the request
var msg_payload = {"id":id};
mq_client.make_request('getCart_queue', msg_payload, function(err, results){
console.log(results); // never prints
//stuff that is never reached
});
These are my rpc files, i dont think there should be anything wrong with these, as some other queues work fine.
And this is the error shown on client
GET /getCart - - ms - -
Error: timeout 6ee0bd2a4b2ba1d8286e068b0f674d8f
at Timeout.<anonymous> (E:\Ebay_client\rpc\amqprpc.js:32:18)
at Timeout.ontimeout [as _onTimeout] (timers.js:341:34)
at tryOnTimeout (timers.js:232:11)
at Timer.listOnTimeout (timers.js:202:5)
Hope the information is not vague, if you need more, please let me know. Thanks!
I Think the error is in this file, because i tried debugging and from the rabbitmq server, the callback is being called and it has the correlation id as well as the reply to variable, so the request is not getting picked up here.
var amqp = require('amqp')
, crypto = require('crypto');
var TIMEOUT=8000;
var CONTENT_TYPE='application/json';
var CONTENT_ENCODING='utf-8';
var self;
exports = module.exports = AmqpRpc;
function AmqpRpc(connection){
self = this;
this.connection = connection;
this.requests = {};
this.response_queue = false;
}
AmqpRpc.prototype.makeRequest = function(queue_name, content, callback){
self = this;
var correlationId = crypto.randomBytes(16).toString('hex');
var tId = setTimeout(function(corr_id){
callback(new Error("timeout " + corr_id));
delete self.requests[corr_id];
}, TIMEOUT, correlationId);
var entry = {
callback:callback,
timeout: tId
};
self.requests[correlationId]=entry;
self.setupResponseQueue(function(){
self.connection.publish(queue_name, content, {
correlationId:correlationId,
contentType:CONTENT_TYPE,
contentEncoding:CONTENT_ENCODING,
replyTo:self.response_queue});
});
};
AmqpRpc.prototype.setupResponseQueue = function(next){
if(this.response_queue) return next();
self = this;
self.connection.queue('', {exclusive:true}, function(q){
self.response_queue = q.name;
q.subscribe(function(message, headers, deliveryInfo, m){
var correlationId = m.correlationId;
if(correlationId in self.requests){
var entry = self.requests[correlationId];
clearTimeout(entry.timeout);
delete self.requests[correlationId];
entry.callback(null, message);
}
});
return next();
});
};
This is the code for your make_request() in client.js file:
var amqp = require('amqp');
var connection = amqp.createConnection({host:'127.0.0.1'});
var rpc = new (require('./amqprpc'))(connection);
function make_request(queue_name, msg_payload, callback){
rpc.makeRequest(queue_name, msg_payload, function(err, response){
if(err)
console.error(err);
else{
console.log("response", response);
callback(null, response);
}
});
}
exports.make_request = make_request;
Look at what happens when you have an err on rpc.makeRequest():
rpc.makeRequest(queue_name, msg_payload, function(err, response){
if(err)
console.error(err);
//
//HERE: should be a callback call here.
//
else{
console.log("response", response);
callback(null, response);
}
});
This could be why you are getting a timeout. I hope it helps.
There wasn't a problem with rabbitMQ but with my queries in the handle request and after responding to the request.
For others coming with this problem, check and double check every statement, as the error will not show in the console, but will only show a timeout
Related
It may be a wrong way to use bull queue but here is what I want to do:
var Promise = require('bluebird');
var redis = require('redis');
var Queue = require('bull');
var redisClient = redis.createClient(6379);
var pdfQueue = new Queue('msg');
function check(resolve, reject,i) {
console.log('check called');
//Or if it is in Router then I want to send request, response in queue so that I can call them in on complete function
pdfQueue.add('msg',{'msg':'Hello', 'resolve':resolve,'reject':reject}).then(job=>{
console.log('added to the pdf')
});
}
pdfQueue.on('completed', function (job, result) {
//Here I want to call request.send('some msg');
//and resolve('complete');
resolve('final callback');
})
pdfQueue.process('msg',100,function (job,done) {
console.log('process');
done(null,'job done ')
})
function check2 () {
return new Promise(function(resolve, reject){
check(resolve,reject);
})
}
check2().then(data => {
console.log('got the value ', data)
});
In my real project I want to implement queue where I will be sending pdf to the user. Like res.download(pdf path); but this function should be in pdf.on('completed',()=>{ res.download(pdf path); }); or in resolve(pdfPath) but I am not able to find anyway to send pdf to the user using queue because I don't know how to call response or resolve in other functions by using queue jobs.
Please help me. Thanks you
I am making use of "socket.io-client" and "socket.io stream" to make a request and then stream some data. I have the following code that handles this logic
Client Server Logic
router.get('/writeData', function(req, res) {
var io = req.app.get('socketio');
var nameNodeSocket = io.connect(NAMENODE_ADDRESS, { reconnect: true });
var nameNodeData = {};
async.waterfall([
checkForDataNodes,
readFileFromS3
], function(err, result) {
if (err !== null) {
res.json(err);
}else{
res.json("Finished Writing to DN's");
}
});
function checkForDataNodes(cb) {
nameNodeSocket.on('nameNodeData', function(data) {
nameNodeData = data;
console.log(nameNodeData);
cb(null, nameNodeData);
});
if (nameNodeData.numDataNodes === 0) {
cb("No datanodes found");
}
}
function readFileFromS3(nameNodeData, cb) {
for (var i in nameNodeData['blockToDataNodes']) {
var IP = nameNodeData['blockToDataNodes'][i]['ipValue'];
var dataNodeSocket = io.connect('http://'+ IP +":5000");
var ss = require("socket.io-stream");
var stream = ss.createStream();
var byteStartRange = nameNodeData['blockToDataNodes'][i]['byteStart'];
var byteStopRange = nameNodeData['blockToDataNodes'][i]['byteStop'];
paramsWithRange['Range'] = "bytes=" + byteStartRange.toString() + "-" + byteStopRange.toString();
//var file = require('fs').createWriteStream('testFile' + i + '.txt');
var getFileName = nameNodeData['blockToDataNodes'][i]['key'].split('/');
var fileData = {
'mainFile': paramsWithRange['Key'].split('/')[1],
'blockName': getFileName[1]
};
ss(dataNodeSocket).emit('sendData', stream, fileData);
s3.getObject(paramsWithRange).createReadStream().pipe(stream);
//dataNodeSocket.disconnect();
}
cb(null);
}
});
Server Logic (that gets the data)
var dataNodeIO = require('socket.io')(server);
var ss = require("socket.io-stream");
dataNodeIO.on('connection', function(socket) {
console.log("Succesfully connected!");
ss(socket).on('sendData', function(stream, data) {
var IP = data['ipValue'];
var blockName = data['blockName'];
var mainFile = data['mainFile'];
dataNode.makeDir(mainFile);
dataNode.addToReport(mainFile, blockName);
stream.pipe(fs.createWriteStream(mainFile + '/' + blockName));
});
});
How can I properly disconnect the connections in function readFileFromS3. I have noticed using dataNodeSocket.disconnect() at the end does not work as I cannot verify the data was received on the 2nd server. But if I comment it out, I can see the data being streamed to the second server.
My objective is to close the connections in Client Server side
It appears that the main problem with closing the socket is that you weren't waiting for the stream to be done writing before trying to close the socket. So, because the writing is all asynchronous and finishes sometime later, you were trying to close the socket before the data had been written.
Also because you were putting asynchronous operations inside a for loop, you were also running all your operations in parallel which may not be exactly what you want as it makes error handling more difficult and server load more difficult.
Here's the code I would suggest that does the following:
Create a function streamFileFromS3() that streams a single file and returns a promise that will notify when it's done.
Use await in a for loop with that streamFileFromS3() to serialize the operations. You don't have to serialize them, but then you would have to change your error handling to figure out what to do if one errors while the others are already running and you'd have to be more careful about concurrency issues.
Use try/catch to catch any errors from streamFileFromS3().
Add error handling on the stream.
Change all occurrences of data['propertyName'] to data.propertyName. The only time you need to use brackets is if the property name contains a character that is not allowed in a Javascript identifier or if the property name is in a variable. Otherwise, the dot notation is preferred.
Add socket.io connection error handling logic for both socket.io connections.
Set returned status to 500 when there's an error processing the request
So, here's the code for that:
const ss = require("socket.io-stream");
router.get('/writeData', function(req, res) {
const io = req.app.get('socketio');
function streamFileFromS3(ip, data) {
return new Promise((resolve, reject) => {
const dataNodeSocket = io.connect(`http://${ip}:5000`);
dataNodeSocket.on('connect_error', reject);
dataNodeSocket.on('connect_timeout', () {
reject(new Error(`timeout connecting to http://${ip}:5000`));
});
dataNodeSocket.on('connection', () => {
// dataNodeSocket connected now
const stream = ss.createStream().on('error', reject);
paramsWithRange.Range = `bytes=${data.byteStart}-${data.byteStop}`;
const filename = data.key.split('/')[1];
const fileData = {
'mainFile': paramsWithRange.Key.split('/')[1],
'blockName': filename
};
ss(dataNodeSocket).emit('sendData', stream, fileData);
// get S3 data and pipe it to the socket.io stream
s3.getObject(paramsWithRange).createReadStream().on('error', reject).pipe(stream);
stream.on('close', () => {
dataNodeSocket.disconnect();
resolve();
});
});
});
}
function connectError(msg) {
res.status(500).send(`Error connecting to ${NAMENODE_ADDRESS}`);
}
const nameNodeSocket = io.connect(NAMENODE_ADDRESS, { reconnect: true });
nameNodeSocket.on('connect_error', connectError).on('connect_timeout', connectError);
nameNodeSocket.on('nameNodeData', async (nameNodeData) => {
try {
for (let item of nameNodeData.blockToDataNodes) {
await streamFileFromS3(item.ipValue, item);
}
res.json("Finished Writing to DN's");
} catch(e) {
res.status(500).json(e);
}
});
});
Other notes:
I don't know what paramsWithRange is as it is not declared here and when you were doing everything in parallel, it was getting shared among all the connections which is asking for a concurrency issue. In my serialized implementation, it's probably safe to share it, but the way it is now bothers me as it's a concurrency issue waiting to happen.
This is my app.js file
var http = require('http');
var url = require('url');
var mysql = require('mysql');
var requestListener = function(request, response){
var urlParse = url.parse(request.url,true);
var path = urlParse.pathname;
var query = urlParse.query;
var jsonString;
if(path === "/getArticleById"){
var conn = mysql.createConnection({
host:'localhost',
port:'3306',
user:'root',
password:'root123',
database:'food'
});
conn.connect(function(err){
if(err){
console.log('Error connecting to database');
return;
}
response.writeHead(200, {'Content-Type': 'text/plain' });
});
var id = query.id;
conn.query('select * from article where id=?', id,function(err,rows){
if(err){
console.log(err);
}
jsonString = rows;
});
conn.end();
console.log(jsonString);
}
};
var server = http.createServer(requestListener);
server.listen(8080);
In jsonString which is inside conn.query, I am getting value of rows printed.
The last console.log is not printing anything, it is also jsonString.
Sorry, I am a newbie in node.js not aware of how to use objects.
Javascript is async in nature.
conn.query('select * from article where id=?', id,function(err,rows){
if(err){
console.log(err);
}
jsonString = rows;
});
Try printing the jsonString within the callback. So, your code should look like
conn.query('select * from article where id=?', id,function(err,rows){
if(err){
console.log(err);
}
jsonString = rows;
console.log(jsonString);
});
How your code is getting executed
Assignment to var id is getting done.
A query to db gets hit. This is time consuming process (async task), your callback function gets called when this query will get resolved. Note this will take some time, that means after sometime this callback will be called, where you will get the rows.
Just after calling the query() ( but before getting the result/callback), your next line of code, i.e conn.end() will get executed. Then you are trying to print the jsonString, Note, till now you haven't got the results back from DB (as it is async operation). That's why jsonString doesnot holds any value yet. Hence you didnt get result printed.
Solution:
print the jsonString after retrieving the result. that means, in the callback.
I'm trying to do something relatively simple and am running into a "server ...-a.mongolab.com:36648 sockets closed" error all of a sudden every time I try to do an "insert".
Reads seem to work without error, but inserts seem to get an error every time and I'm not sure if it's my code (which recently underwent minor changes), or a reliability problem with the free server I'm using at MongoLab (which recently showed itself to be down for a few minutes).
Oddly enough, the record itself seems to save okay, I just get the error back!
Can anyone see an issue with my code, or could this be something else?
var mongoClient = require('mongodb').MongoClient;
var http = require('http');
var connectionString = "...";
var pictureWallsCollectionName = 'PictureWalls';
//this is what barfs. see *** details
exports.saveWall = function (req, res) {
//reformat
var toSave = {
_id: req.body.wallId,
pictures: req.body.pictures
};
var status;
mongoClient.connect(connectionString, function (err, db) {
if (err) { return console.error(err); }
var collection = db.collection(pictureWallsCollectionName);
//*** no err yet... ***
collection.insert(
toSave,
function (error, response) {
//*********************
//*** err here! ******
//*********************
db.close();
if (error) {
console.error(error);
//bad
status = 500;
}
else {
console.log('Inserted into the ' + collection_name + ' collection');
//good
status = 200;
}
});
response.status(status).end(http.STATUS_CODES[status]);
});
}
//this seems to work pretty reliably. including it just in case it's relevant
exports.findByWallId = function (req, res) {
var id = req.params.id;
console.log('Retrieving wall: ' + id);
mongoClient.connect(connectionString, function (err, db) {
if (err) { return console.dir(err); }
var collection = db.collection(pictureWallsCollectionName);
collection.findOne(
{ _id: id },
function (err, item) {
db.close();
if (err) {
console.error(err);
//something bad happened
var status = 500;
res.status(status).end(http.STATUS_CODES[status]);
}
else {
console.log('Found wall with ID ' + id);
//reformat and send back in the response
res.send({
wallId: item._id,
pictures: item.pictures
});
}
}
);
});
};
EDIT: Part of my original issue was duplicate parameter names. See the linked question for detail.
ORIGINAL RESPONSE:
The issue ended up being that I was calling:
res.status(status).end(http.STATUS_CODES[status]);
...before the async insert was finished, so it barfed.
However, I'm not exactly sure how to issue the response in this case. See my new question here:
How Do I Properly Issue Response To Post When Waiting For Async Method To Complete?
I'm looking for a package (or pattern) to handle events from mongodb so I can avoid nested callbacks and keep mongodb logic out of my request handlers.
Right now I've got code that looks like this:
start-express.js (server)
var express = require('express');
var Resource = require('express-resource');
var app = express.createServer();
// create express-resource handler which essentially does app.get('things', ...)
var things = app.resource('things', require('./things.js'));
app.listen(port);
things.js (express-resource request handler)
require('./things-provider');
// handle request 'http://example.com/things'
exports.index = function(request, response) {
sendThings(db, response);
};
things-provider.js (handles mongodb queries)
var mongodb = require('mongodb')
// create database connection
var server = new mongodb.Server(host, port, {auto_reconnect: true});
var db = new mongodb.Db(dbName, server);
db.open(function (err, db) {
if (err) { }
// auto_reconnect will reopen connection when needed
});
function sendThings(db, response) {
db.collection('things', function(err, collection) {
collection.find(function(err, cursor) {
cursor.toArray(function(err, things) {
response.send(things);
});
});
});
}
module.exports.sendThings = sendThings;
I'd like to avoid passing my http response object to my database handler or (worse) handling my db request in my http response handler.
I recently realized that what I want to do is create an event handler that registers an http request/response and waits for a response (event) from database before processing and sending the http response.
That sounds like a lot of duplication of what node.js already does though. Is there an existing framework that handles this use case?
Here's the solution I've come up with.
I used mongojs which greatly simplifies the mongodb interface --at the cost of flexibility in configuration-- but it hides the nested callbacks the mongodb driver requires. It also makes the syntax much more like the mongo client.
I then wrap the HTTP Response object in a closure and pass this closure to the mongodb query method in a callback.
var MongoProvider = require('./MongoProvider');
MongoProvider.setCollection('things');
exports.index = function(request, response){
function sendResponse(err, data) {
if (err) {
response.send(500, err);
}
response.send(data);
};
MongoProvider.fetchAll(things, sendResponse);
};
It is still essentially just passing the response object to the database provider, but by wrapping it in a closure that knows how to handle the response, it keeps that logic out of my database module.
A slight improvement is to use a function to create a response handler closure outside my request handler:
function makeSendResponse(response){
return function sendResponse(err, data) {
if (err) {
console.warn(err);
response.send(500, {error: err});
return;
}
response.send(data);
};
}
So now my request handler just looks like this:
exports.index = function(request, response) {
response.send(makeSendResponse(response));
}
And my MongoProvider looks like this:
var mongojs = require('mongojs');
MongoProvider = function(config) {
this.configure(config);
this.db = mongojs.connect(this.url, this.collections);
}
MongoProvider.prototype.configure = function(config) {
this.url = config.host + "/" + config.name;
this.collections = config.collections;
}
MongoProvider.prototype.connect = function(url, collections) {
return mongojs.connect(this.url, this.collections);
}
MongoProvider.prototype.fetchAll = function fetchAll(collection, callback) {
this.db(collection).find(callback);
}
MongoProvider.prototype.fetchById = function fetchById(id, collection, callback) {
var objectId = collection.db.bson_serializer.ObjectID.createFromHexString(id.toString());
this.db(collection).findOne({ "_id": objectId }, callback);
}
MongoProvider.prototype.fetchMatches = function fetchMatches(json, collection, callback) {
this.db(collection).find(Json.parse(json), callback);
}
module.exports = MongoProvider;
I can also extend MongoProvider for specific collections to simplify the API and do additional validation:
ThingsProvider = function(config) {
this.collection = 'things';
this.mongoProvider = new MongoProvider(config);
things = mongoProvider.db.collection('things');
}
ThingsProvider.prototype.fetchAll = function(callback) {
things.fetchAll(callback);
}
//etc...
module.exports = ThingsProvider;
Well, first off I find Mongoose somewhat easier to use in a well-structured app than straight mongo. So that might help you.
Second, I think what you're trying to do could easily be accomplished through middleware (app level or route level), since you're using express already. Alternatively, parameter-filtering, if your query will vary based on params. A pattern I've seen on the last looks like this:
var User = mongoose.model("user'); // assumes your schema is previously defined
app.param('user_id', function(req,res,next, id){
User.find(id, function(err,user){
if(err) next(err);
else {
req.user = user;
next();
}
});
});
It still has some nesting, but not nearly so bad as your example, much more manageable. Then, let's say you have a '/profile' endpoint, you can just do:
app.get('/profile/:user_id', function(req,res){ res.render('profile', req.user); }