Strange Nodejs memory leak

Viewed 775

Hey I experience strange memory leak on my nodejs server hosted in heroku. I try to find the leak for two days and after I remove my functions one by one I find that this one causes the problem but I'm still not sure where exactly is the problem. I can clearly see that I have a memory leak from heroku metrics. enter image description here Do someone have an idea why this func produce memory leak. Thanks

completeTicTacToeGame: function (game, winnerIndex, reasonForFinish, userId) {
    if (userId) {
      User.findOne({
        _id: userId
      }, function (err, user) {

        if (err || user == null) {
        }
        else {
          userLivesController.removeUserHeartWithoutResponeAndSaving(user, 'ticTacToeHearts', function (user, canPlay) {
            if (canPlay) {
              dateFormatterController.checkIfDateIsToday(user.lastCompitedTicTacToeGame, function (isToday, isPrevious) {
                var earnCredits = 0
                if (winnerIndex == 0) { earnCredits = 1 }
                if (winnerIndex == 1) { earnCredits = 4 }

                user.credits = user.credits + earnCredits
                user.lifetimeCredits = user.lifetimeCredits + earnCredits


                //increase ad today counter
                user.lastCompitedTicTacToeGame = (new Date()).getTime().toString()

                userBadgesController.checkIfUserNeedToWinBadgeForCredits(user, function (user) {

                  user.save(function (err, user) {
                    if (err) {
                      errorHandlingController.generalSendErrorWithMessage(req, res, 'serverError')
                    }
                    else {
                      var canPlayMoreGames = user.ticTacToeHearts > 0
                      if (earnCredits > 0) {
                        notificationController.sendNotificationToUserForNewTicTacToeGame(user, earnCredits)
                        rankingController.updateRankings(user, earnCredits, 'ticTacToe')
                      }

                      var canWatchAdToDoubleCredits = false
                      if (user.gamesWithoutAd >= ConfigParams.numberOfGamesForAd() && earnCredits > 0) {
                        canWatchAdToDoubleCredits = true
                      }
                      var timeLeftToNextHeart = (((ConfigParams.minutesForHeart() * 60000)) + parseInt(user.lastGivenHeart)) - ((new Date()).getTime())

                      var haveMaxHearts = user.mathGameHearts == ConfigParams.maxHearts() && user.memoryHearts == ConfigParams.maxHearts() && user.ticTacToeHearts == ConfigParams.maxHearts()
                      var canWatchVideoForHeart = true && haveMaxHearts == false
                      if (user.lastWatchedVideoForHeart) {
                        var canWatchVideoForHeart = (((ConfigParams.minutesForHeartWatchedAd() * 60000)) + parseInt(user.lastWatchedVideoForHeart)) - ((new Date()).getTime()) < 0 && haveMaxHearts == false
                      }

                      module.exports.sendMessageToSocketForComlitedGame(game, user._id.toString(), user.credits, earnCredits, canPlayMoreGames, canWatchAdToDoubleCredits, reasonForFinish, user.ticTacToeHearts, ConfigParams.maxHearts, null, user.gamesWithoutAd >= ConfigParams.numberOfGamesForAd() - 1, timeLeftToNextHeart, canWatchVideoForHeart)
                    }
                  })
                })
              })
            }
            else {
              var timeLeftToNextHeart = (((ConfigParams.minutesForHeart() * 60000)) + parseInt(user.lastGivenHeart)) - ((new Date()).getTime())

              var haveMaxHearts = user.mathGameHearts == ConfigParams.maxHearts() && user.memoryHearts == ConfigParams.maxHearts() && user.ticTacToeHearts == ConfigParams.maxHearts()
              var canWatchVideoForHeart = true && haveMaxHearts == false
              if (user.lastWatchedVideoForHeart) {
                var canWatchVideoForHeart = (((ConfigParams.minutesForHeartWatchedAd() * 60000)) + parseInt(user.lastWatchedVideoForHeart)) - ((new Date()).getTime()) < 0 && haveMaxHearts == false
              }

              module.exports.sendMessageToSocketForComlitedGame(game, user._id.toString(), user.credits, 0, false, false, reasonForFinish, user.ticTacToeHearts, ConfigParams.maxHearts, null, user.gamesWithoutAd >= ConfigParams.numberOfGamesForAd() - 1, timeLeftToNextHeart, canWatchVideoForHeart)
            }
          })
        }
      })
    }
  },
3 Answers

Like others are commenting here, it is very difficult to find the obvious cause from looking at the code.

One thing you should look at is possibly how often this function was being called in your app. Your call out to User.findOne. Could that be throttled somehow i.e. queuing or blocking requests due to a connection pool size limitation.

If there are more function invocations than there are available resources/ connections You will start to see an incremental increase in memory usage.

Not saying it is the cause but it is definitely where I would start to look.

The things that look like possible trouble here are the 'game' object and the created user object. So depending upon where they go (both are passed into sendMessageToSocketForComlitedGame) it could be they are queued or otherwise kept around. What I'd be worried about here is a closure leak, potentially some array or queue keeping them alive or perhaps a timer loop. Have you tried making 'sendMessageToSocketForComlitedGame' function a noop to see if the memory issue goes away?

Another thought - is it possible that this is in memory state data while a user session or game is ongoing? Is there some other long standing data structure that gets deleted upon an event? Because here it doesn't so much look like a permanent leak as that you're keeping data around for a while.

Since you've been agonizing over this already for a few days I'm sure you've read all types of articles about javascript leaks but I found this one to be a nice one. I would look above this function and below it to see if anything is keeping a reference to an instantiated closure or object.

Good luck! -Darrin

I am not optimizing,or pin pointing any loop holes of the codes causing memory leaks, but here are some tips which can aid you avoid such leaks

Global variables: Since global variables in JavaScript are referenced by the root node (window or global this), they are never garbage collected throughout the lifetime of the application, and will occupy memory as long as the application is running. This applies to any object referenced by the global variables and all their children as well. Having a large graph of objects referenced from the root can lead to a memory leak.

Multiple references: When the same object is referenced from multiple objects, it might lead to a memory leak when one of the references is left dangling.

Closures: JavaScript closures have the cool feature of memorizing its surrounding context. When a closure holds a reference to a large object in heap, it keeps the object in memory as long as the closure is in use. Which means you can easily end up in situations where a closure holding such reference can be improperly used leading to a memory leak.

Timers & Events: The use of setTimeout, setInterval, Observers and event listeners can cause memory leaks when heavy object references are kept in their callbacks without proper handling.

Related