Error: server instance pool gets destroyed in nested db collection functions - node.js

I have searched for the solution of the error specified in title.
MongoError: server instance pool was destroyed
I believe it is because misplacement of db.close(). But I am nesting dbo.collection and unable to get the exact solution of this error.
Firstly, I am fetching data (array of ids having status 0) from database and then I am concatenating (each app-id) them one by one with URL to get desired appUrl which will be used for crawling data one by one and then crawled data is meant to be stored into another collection of mongoDB. This process will repeat for each id in the array. But my code is having error of "server instance pool gets destroyed" before storing data into collection. I am doing misplacement of db.close() but I am unable to resolve this. Please help me resolving this error
Here is my code
///* global sitehead */
const request = require('request');
const cheerio = require('cheerio');
//const response = require('response');
const fs = require('fs');
const express = require('express');
const app = express();
var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/";
var dateTime = require('node-datetime');
MongoClient.connect(url, {useNewUrlParser: true}, function (err, db) {
if (err) {
throw err;
} else {
var dbo = db.db("WebCrawler");
var app_id;
var appUrl;
let arr = [];
dbo.collection("Unique_Apps").find({"Post_Status": 0}, {projection: {_id: 0, App_Id: 1}}).toArray(function (err, result)
{
// console.log(result);
if (err) {
throw err;
// console.log(err);
} else {
for (var i = 0; i < result.length; i++)
{
arr[i] = result[i];
}
arr.forEach((el) => {
app_id = el.App_Id;
//console.log(app_id);
appUrl = 'https://play.google.com/store/apps/details?id=' + app_id;
console.log(appUrl);
request(appUrl, function (error, response, html) {
if (!error && response.statusCode === 200) {
//START Crawling ###########
const $ = cheerio.load(html); //cheerio
const appTitle = $('.AHFaub');
const iconUrl = $('.T75of.sHb2Xb').attr("src");
const developedBy = $('.T32cc.UAO9ie').children().eq(0);
const category = $('.T32cc.UAO9ie').children().eq(1);
//store in database collection: "Single_App_Data_Post"
var curr_obj = {App_Id: app_id, App_Name: appTitle.text(),
Icon_Url: iconUrl, Price: "Free", Developed_By: developedBy.text(),
Category: category.text()
};
dbo.collection("Single_App_Data_Post").insertOne(curr_obj, function (err, res) {
console.log("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
if (err) {
throw err;
// console.log(err);
} else {
console.log("inserted....");
} //main else
});
dbo.collection("Unique_Apps").updateOne({App_Id: app_id}, {$set: {Post_Status: 0}}, function (err, res) {
if (err)
throw err;
console.log("1 document updated");
//dbo.close();
});
} else
{
throw error;
}
});
});
}
db.close();
});
} //else
}); //mongoClient connect db
Output

The following is a good start about how to turn callback into promises. Try to use it, execute the code block, by block, understand it and then add your updateOne/insertOne requests into it.
const request = require('request');
const cheerio = require('cheerio');
const fs = require('fs');
const express = require('express');
const app = express();
const MongoClient = require('mongodb').MongoClient;
const dateTime = require('node-datetime');
// Class used to handle the database basic interractions
class DB {
constructor() {
this.db = false;
this.url = "mongodb://localhost:27017/";
}
// Do connect to the database
connect() {
return new Promise((resolve, reject) => {
MongoClient.connect(this.url, {
useNewUrlParser: true,
}, (err, db) => {
if (err) {
console.log('error mongodb connect');
return reject(err);
}
this.db = db;
return resolve(db);
});
});
}
disconnect() {
db.close();
this.db = false;
}
getCollection(name) {
return this.db.db(name);
}
}
// Get the data from the database
function getAppsIds(dbObj) {
return new Promise((resolve, reject) => {
const dbo = dbObj.getCollection('WebCrawler');
dbo.collection('Unique_Apps').find({
'Post_Status': 0,
}, {
projection: {
_id: 0,
App_Id: 1,
}
}).toArray(function(err, result) {
if (err) {
return reject(err);
}
return resolve(result);
});
});
}
function requestPlayStore(idApp) {
return new Promise((resolve, reject) => {
const appUrl = `https://play.google.com/store/apps/details?id=${app_id}`;
request(appUrl, function(error, response, html) {
if (error || response.statusCode !== 200) {
return reject(error);
}
return resolve({
response,
html,
});
});
});
}
// Do treat one id app at a time
function treatOneIdApp(dbObj, idApp) {
return requestPlayStore(idApp)
.then(({
response,
html,
}) => {
// Perform your requests here updateOne and insertOne ...
});
}
const dbObj = new DB();
dbObj.connect()
.then(() => getAppsIds(dbObj))
.then(rets => Promise.all(rets.map(x => treatOneIdApp(dbObj, x.App_Id))))
.then(() => dbObj.disconnect())
.catch((err) => {
console.log(err);
});

Related

function returning nothing in mongodb query

I'm not capturing the connect function to return in the getAllowedEmails function, when I do console.log in allowedEmails, it returns the emails correctly, but when I assign to the variable emails, it is returning empty. I think it's an async await problem but can't figured out.
static async getAllowedEmails() {
var MongoClient = require("mongodb").MongoClient;
//db url
let emails = [];
await MongoClient.connect(url, async function (err, client) {
const db = client.db("data-admin");
var myPromise = () => {
return new Promise((resolve, reject) => {
db.collection("users")
.find({})
.toArray(function (err, data) {
err ? reject(err) : resolve(data);
});
});
};
var result = await myPromise();
client.close();
let allowedEmails = [];
result.map((email) => allowedEmails.push(email.email));
console.log(allowedEmails)
emails = allowedEmails;
});
console.log(emails)
return emails;
}
Your code has couple of issues, I have fixed few and enhanced it, given below is the basic code, try to test it and if everything works then enhance it as needed:
const MongoClient = require("mongodb").MongoClient;
async function getAllowedEmails() {
let client;
try {
const allowedEmails = [];
client = await MongoClient.connect(url);
const db = client.db("data-admin");
const result = await db.collection("users").find({}).toArray();
result.map((email) => allowedEmails.push(email.email));
console.log(allowedEmails)
client.close();
return allowedEmails;
} catch (error) {
(client) && client.close();
console.log(error)
throw error;
}
}

Moving nodejs MongoDB connection code to another file

Moving the database connection code another nodejs file, no connection object is returned.
I can write data to MongoDB in nodejs. All db connection code is written in a single .js file.
Now I try to seperate the db connection code to another .js file, and now it seems that no connection can be made successfully.
Here is the working code in 1 .js file:
const mongoDBIP = '192.168.1.71';
const mongoDBPort = 27017;
const mongo= require('mongodb').MongoClient;
const mongoURL = 'mongodb://<mongo admin>:<password>#'+`${mongoDBIP}`+':'+`${mongoDBPort}`;
...
mongo.connect(mongoURL, { useNewUrlParser: true, useUnifiedTopology: true }, (err, db) => {
if (err) {}
var dbo = db.db(<databaseName>);
var collection = dbo.collection('messages');
collection.insertOne(message, (err, result) => {
if (err) {}
})
...
Now I break this into 2 .js files:
var _mongo = require('./mongodb.js');
var mongoDBO = _mongo.mongoDBO;
...
var dbo = mongoDBO('feathers');
console.log('DBO:' + dbo); <-- here, dbo is NULL
var collection = dbo.collection('messages');
collection.insertOne(message, (err, result) => {
if (err) {}
})
...
Here is the content of mongodb.js:
// mongodb.js
const mongoDBIP = '192.168.1.71';
const mongoDBPort = 27017;
const mongoClient = require('mongodb').MongoClient;
const mongoURL = 'mongodb://<mongo admin>:<password>#'+`${mongoDBIP}`+':'+`${mongoDBPort}`;
function mongoDBO(database) {
var dbo;
mongoClient.connect(mongoURL, { useNewUrlParser: true, useUnifiedTopology: true }, (err, dbase) => {
if (err) {}
dbo = dbase.db(database); <-- here, dbo is NULL
});
return dbo;
}
module.exports = Object.freeze({
mongoDBO
});
I expect the object "dbo" in mongodb.js would not be NULL, but it is. This occurs under both nodejs v10.x and v12.x.
Here is an implementation what Tom explained, in case you need.
mongodb.js
const mongoClient = require('mongodb').MongoClient;
const mongoDBIP = '192.168.1.71';
const mongoDBPort = 27017;
const mongoURL = 'mongodb://<mongo admin>:<password>#'+`${mongoDBIP}`+':'+`${mongoDBPort}`;
let _db;
const initDb = callback => {
if (_db) {
console.log('Db is already initialized!');
return callback(null, _db);
}
mongoClient .connect(mongoURL)
.then(client => {
_db = client;
callback(null, _db);
})
.catch(err => {
callback(err);
});
};
const getDb = () => {
if (!_db) {
throw Error('Db not initialized');
}
return _db;
};
module.exports = {
initDb,
getDb
};
Initialize it in your main file (index, app or server.js)
const mongodb = require('./mongodb');
mongodb.initDb((err, mongodb ) => {
if (err) {
console.log(err);
} else {
app.listen(3000);
}
});
And use it in your routes:
const mongodb = require('./mongodb');
mongodb.getDb()
.db()
.collection('your_collection_name')....
You are trying to return a value that does not exist yet. dbo is not set until the callback function is invoked. The original function has already returned at that point.
You must alter your mongodb.js file to export a function that takes a callback, and passes dbo to that callback when it is ready.
I recommend reading up on callbacks and asynchronous programming.
Try like this
const mongoDBIP = "192.168.1.71";
const mongoDBPort = 27017;
const mongoClient = require("mongodb").MongoClient;
const mongoURL =
"mongodb://<mongo admin>:<password>#" +
`${mongoDBIP}` +
":" +
`${mongoDBPort}`;
var mongoDBO = database =>
new Promise((resolve, reject) => {
mongoClient.connect(
mongoURL,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err, dbase) => {
if (err) {
reject(err);
}
resolve(dbase);
}
);
});
module.exports = Object.freeze({
mongoDBO
});
&
var _mongo = require('./mongodb.js');
var mongoDBO = _mongo.mongoDBO;
...
try {
let dbo = mongoDBO('feathers');
console.log('DBO:' + dbo); <-- here, dbo is NULL
var collection = dbo.collection('messages');
collection.insertOne(message, (err, result) => {
if (err) {}
})
} catch (error) {
console.log(connection failed)
}
...

Node JS - function not executed

I have a module which i wrote myself. It contains 3 functions which i call as chained promises. The last one does not execute and i cannot figure out why.
The module geodata.js
require('use-strict');
var Promise = require('promise');
var NodeGeocoder = require('node-geocoder');
var mongoose = require('mongoose');
var User = require('../models/user');
var options = {
provider: 'google',
httpAdapter: 'https',
apiKey: 'AIzaSyA4v81WbNOMeRL7p911Mxr6PBZnidX0cIM',
formatter: null
};
module.exports = {
//*******************************************************
// Find user and return his adress (street + town)
//
//*******************************************************
findUser: function(username) {
return new Promise(function(resolve, reject) {
User.findOne({
'username': username
}, function(err, doc) {
if (!err) {
resolve(doc);
} else {
reject(err);
}
});
});
},
//*******************************************************
// Fetch geodata (latitude + longitude) to users record in
// the user collection.
//*******************************************************
fetchGeoData: function(userObj) {
return new Promise(function(resolve, reject) {
var geocoder = NodeGeocoder(options);
var adress = userObj.street + ' ' + userObj.town;
geocoder.geocode(adress, function(err, res) {
if (!err) {
var res2 = {
'username': userObj.username
}
console.log(res);
resolve([res, res2]);
} else {
reject(err);
}
});
});
},
//*******************************************************
// Fetch geodata (latitude + longitude) to users record in
// the user collection.
//*******************************************************
addGeoData: function(message) {
return new Promise(function(resolve, reject) {
User.findOne({
'username': message.username
}, function(err, doc) {
console.log(doc);
if (err) {
console.log(err);
reject(err);
}
if (!doc) {
console.log('User not found.');
reject('User not found.');
}
doc.longitude = String(message.longitude);
doc.latitude = String(message.latitude);
doc.save();
resolve(doc);
});
});
},
fixJSON: function(inpJSON) {
var strung = JSON.stringify(inpJSON);
obj = strung.substring(1, strung.length - 1);
return (JSON.parse(obj));
}
};
.. and here is the calling script:
require('use-strict');
var mongoose = require('mongoose');
var dbConfig = require('./db');
var geodata = require('./lib/geodata.js');
// Connect to DB
mongoose.connect(dbConfig.url, {
auth: {
authdb: "admin"
}
});
mongoose.set('debug', true);
geodata.findUser('jimmy').then(function(result) {
console.log('findUser() done');
return geodata.fetchGeoData(result);
}).then(function(result) {
console.log('fetchGeoData() done');
var res1Fixed = geodata.fixJSON(result[0]);
var params = {
username: result[1].username,
longitude: res1Fixed.longitude,
latitude: res1Fixed.latitude
}
console.log(params);
return geodata.addGeoData(params);
}).then(function(result) {
console.log('addGeoData() done');
});
mongoose.connection.close();
the execution stops after this line:
console.log('fetchGeoData() done');
and i can't figure out why?
Regards
Jimmy
If you are referring to mongoose.connection.close(); that is actually being run before your console.log('addGeoData() done');
Notice that mongoose.connection.close() is synchronous whereas the mongoose.findUser is asynchronous.

How can I execute db.copyDatabase through NodeJS's MongoDB native driver?

I do have a shell script that invokes
mongo --eval "db.copyDatabase('somedatabase', 'somedatabase_duplicate', 'sourcehost')"
to copy a database.
Currently I am stuck with doing the same from within a Node.JS application. Calling
mongoCommand = `db.copyDatabase("somedatabase", "somedatabase_duplicate", "localhost")`;
db.command(mongoCommand, function(commandErr, data) {
if(!commandErr) {
log.info(data);
} else {
log.error(commandErr.errmsg);
}
});
Always resulsts in a "no such command" error message.
Edit for clarification: Using db.admin().command() results in the same problem and using the command suggested in enter link description here, too.
What's the correct way to call this command or, alternatively, to clone a database from Node.JS?
Well, you are trying to copy database which is administration operation so have to do with admin account. Again, to copy database command is copydb.
try running this command in shell, db.copyDatabase and you'll see source of command.
try:
var assert = require('assert');
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://localhost:27017/test';
MongoClient.connect(url, function(err, db) {
if (err) {
console.log(err);
}
else {
var mongoCommand = { copydb: 1, fromhost: "localhost", fromdb: "test", todb: "test_dup" };
var admin = db.admin();
admin.command(mongoCommand, function(commandErr, data) {
if (!commandErr) {
console.log(data);
} else {
console.log(commandErr.errmsg);
}
db.close();
});
}
});
//core modules
const assert = require('assert')
const MongoClient = require('mongodb').MongoClient;
const moment = require('moment');
const mongo = require('mongodb')
//custom modules
let { ip, port, database } = require('./dbUpgradeConfig')
const url = `mongodb://${ip}:${port}`
let todayDate = moment().format('DD/MM/YYYY HH:mm')
console.log(todayDate)
const myDate = new Date()
console.log(myDate)
var d = Date(Date.now());
// Converting the number of millisecond in date string
a = d.toString()
// Options for mongoDB
const mongoOptions = { useNewUrlParser: true }
let db
//TODO: handle reconnect
let connect = () => {
return new Promise((resolve, reject) => {
if (db) resolve()
else {
mongo.connect(url, mongoOptions, (err, client) => {
if (err) reject(err)
else {
db = client.db(database)
resolve()
}
})
}
})
}
/**
* #description create duplicate database from current database in mongodb
*/
let CloneDb = () => {
return new Promise((resolve, reject) => {
connect()
.then(() => {
console.log(db)
let mongoCommand = { copydb: 1, fromhost: "localhost", fromdb: "db_name", todb: "db_name_duplicate" }
let adminDB = db.admin()
adminDB.command(mongoCommand, function (commandErr, data) {
if (!commandErr) {
console.log(data)
} else {
console.log(commandErr.errmsg)
}
});
})
})
}
CloneDb().then(data => {
// debugger;
console.log("The clone db", data)
})

node js mongo db dependencies (doc not being found)

I have the following code:
var method = PushLoop.prototype;
var agent = require('./_header')
var request = require('request');
var User = require('../models/user_model.js');
var Message = require('../models/message_model.js');
var async = require('async')
function PushLoop() {};
method.startPushLoop = function() {
getUserList()
function getUserList() {
User.find({}, function(err, users) {
if (err) throw err;
if (users.length > 0) {
getUserMessages(users)
} else {
setTimeout(getUserList, 3000)
}
});
}
function getUserMessages(users) {
// console.log("getUserMessages")
async.eachSeries(users, function (user, callback) {
var params = {
email: user.email,
pwd: user.password,
token: user.device_token
}
messageRequest(params)
callback();
}, function (err) {
if (err) {
console.log(err)
setTimeout(getUserList, 3000)
}
});
}
function messageRequest(params) {
var url = "https://voip.ms/api/v1/rest.php?api_username="+ params.email +"&api_password="+ params.pwd +"&method=getSMS&type=1&limit=5"
request(url, function(err, response, body){
if (!err) {
var responseObject = JSON.parse(body);
var messages = responseObject.sms
if (responseObject["status"] == "success") {
async.eachSeries(messages, function(message, callback){
console.log(params.token)
saveMessage(message, params.token)
callback();
}, function(err) {
if (err) {
console.log(err)
}
// setTimeout(getUserList, 3000)
})
} else {
// setTimeout(getUserList, 3000)
}
} else {
console.log(err)
// setTimeout(getUserList, 3000)
}
});
setTimeout(getUserList, 3000)
}
function saveMessage(message, token) {
// { $and: [ { price: { $ne: 1.99 } }, { price: { $exists: true } }
// Message.find({ $and: [{ message_id: message.id}, {device_token: token}]}, function (err, doc){
Message.findOne({message_id: message.id}, function (err, doc){
if (!doc) {
console.log('emtpy today')
var m = new Message({
message_id: message.id,
did: message.did,
contact: message.contact,
message: message.message,
date: message.date,
created_at: new Date().toLocaleString(),
updated_at: new Date().toLocaleString(),
device_token: token
});
m.save(function(e) {
if (e) {
console.log(e)
} else {
agent.createMessage()
.device(token)
.alert(message.message)
.set('contact', message.contact)
.set('did', message.did)
.set('id', message.id)
.set('date', message.date)
.set('message', message.message)
.send();
}
});
}
}) //.limit(1);
}
};
module.exports = PushLoop;
Which actually works perfectly fine in my development environment - However in production (i'm using Openshift) the mongo documents get saved in an endless loop so it looks like the (if (!doc)) condition always return true therefore the document gets created each time. Not sure if this could be a mongoose issue - I also tried the "find" method instead of "findOne". My dev env has node 0.12.7 and Openshift has 0.10.x - this could be the issue, and i'm still investigating - but if anybody can spot an error I cannot see in my logic/code please let me know
thanks!
I solved this issue by using a "series" like pattern and using the shift method on the users array. The mongoose upsert findOneOrCreate is good however if there is a found document, the document is returned, if one isn't found and therefore created, it's also returned. Therefore I could not distinguish between the newly insert doc vs. a found doc, so used the same findOne function which returns null if no doc is found I just create it and send the push notification. Still abit ugly, and I know I could have used promises or the async lib, might refactor in the future. This works for now
function PushLoop() {};
var results = [];
method.go = function() {
var userArr = [];
startLoop()
function startLoop() {
User.find({},function(err, users) {
if (err) throw err;
users.forEach(function(u) {
userArr.push(u)
})
function async(arg, callback) {
var url = "https://voip.ms/api/v1/rest.php?api_username="+ arg.email +"&api_password="+ arg.password +"&method=getSMS&type=1&limit=5"
request.get(url, {timeout: 30000}, function(err, response, body){
if (!err) {
var responseObject = JSON.parse(body);
var messages = responseObject.sms
var status = responseObject.status
if (status === "success") {
messages.forEach(function(m) {
var message = new Message({
message_id: m.id,
did: m.did,
contact: m.contact,
message: m.message,
date: m.date,
created_at: new Date().toLocaleString(),
updated_at: new Date().toLocaleString(),
device_token: arg.device_token
});
var query = { $and : [{message_id: m.id}, {device_token: arg.device_token}] }
var query1 = { message_id: m.id }
Message.findOne(query).lean().exec(function (err, doc){
if (!doc || doc == null) {
message.save(function(e) {
console.log("message saved")
if (e) {
console.log("there is an error")
console.log(e)
} else {
console.log(message.device_token)
var messageStringCleaned = message.message.toString().replace(/\\/g,"");
var payload = {
"contact" : message.contact,
"did" : message.did,
"id" : message.message_id,
"date" : message.date,
"message" : messageStringCleaned
}
var note = new apns.Notification();
var myDevice = new apns.Device(message.device_token);
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.badge = 3;
note.alert = messageStringCleaned;
note.payload = payload;
apnsConnection.pushNotification(note, myDevice);
}
})
}
});
});
}
else {
console.log(err)
}
}
});
setTimeout(function() {
callback(arg + "testing 12");
}, 1000);
}
// Final task (same in all the examples)
function series(item) {
if(item) {
async( item, function(result) {
results.push(result);
return series(userArr.shift());
});
} else {
return final();
}
}
function final() {
console.log('Done');
startLoop();
}
series(userArr.shift())
});
}
}
module.exports = PushLoop;

Resources