Search code examples
javascriptnode.jscallbacknode-schedule

Node.js memory usage with callback functions and node-schedules


So i've created some code that when an event (an extended node-schedule) is passed into this.scheduler.schedule(), a random duration (in seconds) between min and max will be added onto the current date and handler will be called when the current date is equal to the random date.

    start(lock){
        const event = new RandomScheduledEvent({
            extension: this.extension.slug, 
            lock: lock.lock.id, 
            slug: "random-event",
            min: 2,
            max: 2,
            handler: () => { this.eventCycle(lock) }
        });

        this.scheduler.schedule(event)
    }

    /**
     * 
     * @param {RandomEventLock} oldLock 
     */
    async eventCycle(oldLock){
        const updatedLock = await this.getLock(oldLock.lock.id, oldLock.tokenManager);
        if(updatedLock.isTrusted() == true){
            this.scheduler.deschedule(this.scheduler.findOne({lock: oldLock.lock.id, slug: "random-event"}))
            console.log(this.scheduler);
        }
        return;
    }

My concern is how much memory will be taken up by these random events and im not sure how JavaScript handles these types of calls. I estimate to have around a 100 of these schedules occuring at any given moment and the server im hosting on is very strict on memory consumption. oldLock (the parameter for eventCycle) stores a fairly large object and I dont know if this object will persist in memory until a new schedule occurs. Ideally, the object will be used just to get the most up to date information and thrown away once the eventCycle is complete, practically only storing the node-schedule

this.getLock() is retrieving information from an API and im appending that data to an object which stores my data, used to process any actions I need, e.g. how frequent events occur, if the lock is enabled, etc.


Solution

  • If what you're asking is how long will oldLock stay in memory, that answer is determined by how the garbage collector works. As long as any code can still reach/use oldLock it can't be garbage collected and will exist in memory.

    In your specific code, you're passing the object that will become oldLock to the start() method and that is in turn using that value in the handler callback. So, at the very least, it stays in memory until handler() is called or until handler() can no longer be called. If that is called when the RandomScheduledEvent fires, then it certainly stays in memory until that event fires and calls the handler() function.

    Then, within the eventCycle() method, the oldLock object is referenced multiple times so it will still remain available for the lifetime of that method execution including while it's sitting at the await this.getLock() call since oldLock is referenced after that.

    Though the details of exactly how soon a garbage collector will clean up an object is not detailed by specification, it would be possible in your code to make oldLock eligible for garbage collection a little sooner such that is could be GCed during the await this.getLock() and wouldn't have to hang around until after that API call is done. You can that by not referencing oldLock after the await by changing to this:

    async eventCycle(oldLock){
        const id = oldLock.lock.id;
        const updatedLock = await this.getLock(id, oldLock.tokenManager);
        if(updatedLock.isTrusted() == true){
            this.scheduler.deschedule(this.scheduler.findOne({lock: id, slug: "random-event"}))
            console.log(this.scheduler);
        }
        return;
    }
    

    Whether this makes an actual difference or not depends entirely upon the internals of the garbage collector (which are not specified and can change), but it could make it eligible for GC sooner so it certainly won't hurt.

    You could make it more likely to get GCed sooner by not even passing it to eventCycle(). Instead, just pass the two properties you actually need:

    async eventCycle(id, tokenManager){
        const updatedLock = await this.getLock(id, tokenManager);
        if(updatedLock.isTrusted() == true){
            this.scheduler.deschedule(this.scheduler.findOne({lock: id, slug: "random-event"}))
            console.log(this.scheduler);
        }
        return;
    }
    

    P.S. On a different topic, you appear to be vulnerable to unhandled rejections. If this.getLock() rejects, you do not have a handler for that.