14

I'm using setTimeout in Node.js and it seems to behave differently from client-side setTimeout in that it returns an object instead of a number. I want to store this in redis, but since redis only stores strings, I need to convert the object to a string. However, using JSON.stringify throws a circular reference error. How can I store this object in redis if I want to be able to fetch it from redis and call clearTimeout on it?

15
  • I don't think the setTimeout call has anything to do with the circular reference error. stackoverflow.com/questions/1493453/… Commented Jul 2, 2012 at 20:40
  • @Trevor setTimeout creates this object: { _idleTimeout: 1000000000, _idlePrev: { _idleNext: [Circular], _idlePrev: [Circular], ontimeout: [Function] }, _idleNext: { _idleNext: [Circular], _idlePrev: [Circular], ontimeout: [Function] }, _onTimeout: [Function], _idleStart: Mon, 02 Jul 2012 20:28:18 GMT } Commented Jul 2, 2012 at 20:42
  • 2
    _idleNext and _idlePrev keys seem to be circular references... Commented Jul 2, 2012 at 20:42
  • I gather from your comments that what you really need is using redis to scale socket.io across instances. I recommend you look into using socket.io's RedisStore -- see e.g. this answer of mine. Commented Jul 3, 2012 at 11:09
  • 1
    Ok, I see. Can you tell us some more about what you need the timeouts for? I.e., what are you actually trying to accomplish? Perhaps, there's a better way. Commented Jul 3, 2012 at 15:25

4 Answers 4

2

You cannot store the object in Redis. The setTimeout method returns a Handler (object reference).

One idea would be to create your own associative array in memory, and store the index in Redis. For example:

var nextTimerIndex = 0;
var timerMap = {};

var timer = setTimeout(function(timerIndex) {
    console.log('Ding!');

    // Free timer reference!
    delete timerMap[timerIndex];
}, 5 * 1000, nextTimerIndex);

// Store index in Redis...

// Then, store the timer object for later reference
timerMap[nextTimerIndex++] = timer;

// ...
// To clear the timeout
clearTimeout(timerMap[myTimerIndex]);
Sign up to request clarification or add additional context in comments.

4 Comments

Ok, but I need to be able to call clearTimeout on the object after fetching it from redis
I want to be able to share it across instances so in memory wouldn't really be an option
@user730569 did you manage to store the timeout object in redis? You marked this as answered but this keeps it in memory
@scanales I did not manage to store it in redis. I have not yet scaled past one instance or server though.
1

I was attempting to do the same thing as the OP. My solution was to set the timeout with a conditional check on a new key inside the timeout in my disconnect handler:

redis.hset("userDisconnecting:" + userId, "disconnect", 1);

setTimeout(function() {
    redis.hget("userDisconnecting:" + userId, "disconnect",
     function(err, result) {
        if (result.toString() === "1") {
           //do stuff, like notify other clients of the disconnect.
        }
    });
}, 10000);

Then, when the client connects again, I set that key to 0, so the stuff that needs to fire on true disconnect doesn't happen:

redis.hset("userDisconnecting:" + userId, "disconnect", 0);

The timeouts themselves aren't persistent across server restarts, but you could solve that by kicking off a sweeper method on startup. Connected clients would come back "online" pretty quickly.

Comments

1

In the newer versions of node, you can use the Id of the Timeout object instead of the object itself to end the loop.

   redisClient.set('time', JSON.stringify(10))
   let timeoutObject = setInterval(async function(){
      let time = await JSON.parse(redisClient.get('time'))
      if(time === 0){
       let intervalId = await JSON.parse(redisClient.get('intervalId'))
       clearInterval(intervalId)
      }
       time -= 1
       redisClient.set('time', JSON.stringify(time))
    }, 1000)
    
    let intervalId = timeoutObject[Symbol.toPrimitive]()
    redisClient.set('intervalId', JSON.stringify(intervalId))

This is just an example of a timer built with setInterval and redis combined. As you can see, you can grab the Id of the Timeout Object and store that to end setInterval's execution instead of trying to store the whole object.

Here is the link to the node docs: https://nodejs.org/api/timers.html#timers_timeout_symbol_toprimitive

Comments

0

This code is used when the timeouts need not be persistent across server restarts

var timeouts = {};

app.get('/', function (req, res) {
  var index = timeouts.length;
  timeouts[index] = setTimeout(console.log, 1000000, req.user.name);

  redis.set('timeout:' + req.user.name, index, function (err, reply) {
    res.end();
  });
});

app.get('/clear', function (req, res) {
  redis.get('timeout:' + req.user.name, function (err, index) {
   clearTimeout(timeouts[index]);
   delete timeouts[index];
   redis.delete('timeout:' + req.user.name);
   res.end();
  });
});

If you need timeouts to be persistent across server restarts, then you might need to store _idleStart and _idleTimeout values for every timer in the redis, and load them up everytime you server restarts

app.get('/', function (req, res) {
  var timeout = setTimeout(console.log, 1000000, req.user.name);
  var time = timeout._idleStart.getTime() + timeout._idleTimeout;

  redis.set('timeout:' + req.user.name, time, function (err, reply) {
    res.end();
  });
});

app.get('/clear', function (req, res) {
  redis.delete('timeout:' + req.user.name);
  res.end();
});

// Load timeouts on server start
// *I know this is not the correct redis command*
// *It's not accurate, only approx*
redis.get('timeout:*', function (err, vals) {
  vals.forEach(function (val) {
    var time = val - new Date().getTime();
    setTimeout(console.log, time, username)
  });
});

7 Comments

You're leaking timer object reference.
@user730569 That won't work... the timer will be gone after a restart. You need to find a way to reschedule them after a restart. You can persist the start time, and recalculate the timeout value on restart.
@legege I didn't mean server restarts... what I really mean is across instances, so anything in memory isn't an option
@user730569 Across instances? Is the timer started on the n instances? Should it be canceled everywhere? Please provide a better context in your question.
@legege Ok so I'm using socket.io storage, and the documentation claims that in order to scale across multiple instances, I need to move away from memory store and towards redis.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.