I have a problem which has already cost me a couple of days and I totally fail to understand it. I use node.js and have a mongodb with places indexed for their coordinates. I verified that the index works fine beforehand. For a set of coordinates, I want to find the nearest place. Here's how I tried to go about it:
var Places = require(./models/places.js),
coordFinder = require('./coordFinder.js');
...
function getCoords(userId, callback) {
coordFinder.getCoords(userId, function(err, coords) {
if (err) {
callback(err);
} else {
callback(null, coords);
}
}
}
function getNearestPlaces(coords, callback) {
async.forEachSeries(
coords,
function(coord, done) {
Places.findOne(
{ coordinates: { $near: [ coord.lat, coord.lon ] } },
function(err, place) {
// do something with place
done();
}
)
},
function(err) {
callback(err ? err : null);
}
);
}
...
async.waterfall(
[
...
getCoords,
getNearestPlaces,
...
],
function(err) {
...
}
}
The mongodb query consistently fails with the following error message for the first query:
RangeError: Maximum call stack size exceeded
When I change the query in getNearestPlaces to
... { $near: [ 10, 10 ] } ...
I as consistently get no error and the correct place. The same happens if I avoid the call to coordFinderin getCoords and change getCoords to
function getCoords(userId, callback) {
callback(
null,
[
// some coordinates
]
);
I know the error indicates a stack overflow (increasing the stack size didn't help) but I can't figure out how I could have caused one. Does anyone have a clue as to why this happens? Thanks in advance for your insights!
Edit: It got the same problem when using mongodb-native directly, by the way, so mongoose seems not to be the problem. One more clue - using the same query never gives me a problem when I don't call it from within a callback. Could that be a source of error?
I use
Cheers, Georg
The problem is most likely coming from the hidden recursion of async.forEachSeries. Try wrapping the Places.findOne call with setTimeout like this:
function getNearestPlaces(coords, callback) {
async.forEachSeries(
coords,
function(coord, done) {
setTimeout(function () {
Places.findOne(
{ coordinates: { $near: [ coord.lat, coord.lon ] } },
function(err, place) {
// do something with place
done();
}
);
}, 1);
},
function(err) {
callback(err ? err : null);
}
);
}
This puts the Places.findOne call outside of the call stack of async.forEachSeries.
mjhm pointed me in the right direction. Apparently, the Number instanced passed to the mongoose query caused the stack overflow. I'm not sure why, but creating a new Number object from the old one (as mjhm suggested) or converting the number into a primitive with valueOf() solved the problem.
If anyone has a clue as to how the original Number object caused the stack overflow, I'd very much appreciate any insights you might have to offer.