Zuhayr
Zuhayr

Reputation: 352

Modifying current reference causes maximum stack size exceeded crash

In node js, using version 4.1.0 of the 'firebase-admin' SDK, I have a listener which listens to a message queue reference in my database, processes messages, and thereafter tries to remove it from the queue reference.

When I have greater than a certain number of records (1354 on my machine) in the queue prior to starting the script, the script crashes with a maximum call stack exceeded error.

The strange thing is that this only occurs when I have 1354+ values in the queue prior to script start. Any lower than this and the problem vanishes.

I don't know why this is happening, but I know that it only occurs when I try to modify/remove the object at the snapshot reference.

Here is a self-contained mcve with the problem area marked in the comments:

var admin = require("firebase-admin");

var serviceAccount = require("<ADMIN JSON FILE PATH GOES HERE>");

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "<FIREBASE URL GOES HERE>"
});

var ref = admin.database().ref();

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// the number of messages to generate for the queue. when this is >= 1354 (on my machine) the program crashes, if it's less than that,
// it works perfectly fine; your tipping point may vary
var amount = 1354;
// message payload to deliver to the queue <amount> times
var payload = {};

// message generation loop
for (i = 0; i < amount; i++) {
    var message = {msg: "hello"};
    payload['message-queue/' + ref.push().key] = message;
}

// add the generated messages simultaneously to message-queue
ref.update(payload).then(function () {

    // 'on child added' listener that causes the crash of the program when there are 1354+ pre-existing messages in the queue prior to application start
    ref.child('message-queue').on('child_added', function(snapshot) {

        var msgKey = snapshot.key;
        var msgContents = snapshot.val().msg

        // do something with msgContents (e.g. sanitize message and deliver to some user's message-received node in the firebase)

        // ***THIS*** is what causes the crash. if you remove this line of code, the program does not crash. it seems that any
        // modification/removal to/of the current <msgKey> node does the same
        ref.child('message-queue').child(msgKey).remove();
    });
});

And here is the stack trace of the crash:

FIREBASE WARNING: Exception was thrown by user callback. RangeError: Maximum call stack size exceeded

    at RegExp.exec (native)
    at RegExp.test (native)
    at tc (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:63:86)
    at ub (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:60:136)
    at vb (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:43:1228)
    at Xb.h.remove (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:52:44)
    at Xb.h.remove (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:52:136)
    at Xb.h.remove (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:52:136)
    at Xb.h.remove (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:52:136)
    at Xb.h.remove (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:52:136)

<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:63
(d="0"+d),c+=d;return c.toLowerCase()}var zc=/^-?\d{1,10}$/;function tc(a){retur
n zc.test(a)&&(a=Number(a),-2147483648<=a&&2147483647>=a)?a:null}function Ac(a){
try{a()}catch(b){setTimeout(function(){N("Exception was thrown by user callback.
",b.stack||"");throw b;},Math.floor(0))}}function Bc(a,b,c){Object.definePropert
y(a,b,{get:c})}function Cc(a,b){var c=setTimeout(a,b);"object"===typeof c&&c.unr
ef&&c.unref();return c};function Dc(a){var b={},c={},d={},e="";try{var f=a.split
("."),b=bb(hc(f[0])||""),c=bb(hc(f[1])||""),e=f[2],d=c.d||{};delete c.d}catch(g)
{}return{wg:b,Ge:c,data:d,mg:e}}function Ec(a){a=Dc(a);var b=a.Ge;return!!a.mg&&
!!b&&"object"===typeof b&&b.hasOwnProperty("iat")}function Fc(a){a=Dc(a).Ge;retu
rn"object"===typeof a&&!0===y(a,"admin")};function Gc(a,b,c){this.type=Hc;this.s
ource=a;this.path=b;this.children=c}Gc.prototype.Jc=function(a){if(this.path.e()
)return a=this.children.sub

RangeError: Maximum call stack size exceeded
    at RegExp.exec (native)
    at RegExp.test (native)
    at tc (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:63:86)
    at ub (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:60:136)
    at vb (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:43:1228)
    at Xb.h.remove (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:52:44)
    at Xb.h.remove (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:52:136)
    at Xb.h.remove (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:52:136)
    at Xb.h.remove (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:52:136)
    at Xb.h.remove (<MY_PROJECT_PATH>\node_modules\firebase-admin\lib\database\database.js:52:136)

<MY_PROJECT_PATH>>

<MY_PROJECT_PATH>>

<MY_PROJECT_PATH>>

Upvotes: 0

Views: 183

Answers (2)

Rohit
Rohit

Reputation: 152

Minmize the memory allocated to stack.for example static array inside function instead of that use dynamic array.

Upvotes: 0

Chad Robinson
Chad Robinson

Reputation: 4623

Even though you aren't processing it, the call to remove() is still async/promise-based and generates a context to run in. Promise contexts are fairly big and it's not a surprise you're running out of stack here. If you really needed a pattern like this to work properly, you could batch the updates - have child_added insert the values into a "to be deleted" array, then process that array a batch of entries at a time as a separate task until it was empty. There are plenty of helper methods for working with arrays and Promises in the BlueBird (http://bluebirdjs.com/) library that could help with this (e.g. map/mapSeries).

This isn't really a Firebase problem - every other VM (PHP, Java, etc.) has stack size limits to deal with as well. Like most others, V8's is tunable, and if you need to, you can query (and adjust) it using a command like:

node --v8-options | grep -B0 -A1 stack_size

But I believe your best approach is to structure your program to minimize your stack usage for this deletion pattern. Increasing stack size is always going to leave you open to the "is it big enough now?" question.

Upvotes: 1

Related Questions