Its About Time – TINN Timers

The last time I wrote about time: Hurry Up and Wait –  TINN Timing the focus was on introducing the wait() function and explaining how that fits into the TINN scheduler in general.

Having a wait() function is a great low level primitive.  It allows you to pause execution for an amount of time, in a synchronous manner.  That will work well when you’re doing something serially and need to take break at specified intervals.  What if your usage pattern is more complex though?  More likely than not, if you’re writing a web server of some sort, you’ll be doing this sort of sequence:
executeasynccodethattakestime()

if codeNotDoneAfter sometime then

cancel code

end

do other stuff while waiting

Basically, I need to do something like send off a web request.  If the request has not been satisfied within a specified amount of time, I want to cancel the operation.  I need to be able to do this asynchronously because I may have many requests in flight.  So, what to do?  I obviously need a Timer of some sort that will deal with this for me.

local Task = require("IOProcessor")

local Timer = {}
setmetatable(Timer, {
  __call = function(self, ...)
    return self:create(...)
  end,
});

local Timer_mt = {
  __index = Timer;
}

Timer.init = function(self,params)
  local obj = {
    Delay = params.Delay;
    Period = params.Period;
    Running = false;
    TimerFunc = params.TimerFunc;
  }
  setmetatable(obj, Timer_mt);

  if self.Running then
    obj:start()
  end

  return obj;
end

Timer.create = function(self, ...)
  return self:init(...);
end

Timer.start = function(self)
  self.Running = true;

  local function closure()
    if self.Delay then
      wait(self.Delay);
      self.TimerFunc(self);
    end

    if not self.Period then
      return
    end

    while self.Running do
      wait(self.Period)
      self.TimerFunc(self);
    end
  end

  spawn(closure);
end

Timer.stop = function(self)
  self.Running = false;
end

return Timer

To dissect, basically an object that provides easy wrapper convenience for the wait() function. You specify an initial delay, and a period and call the start() function. Start will spawn the actual function that is involved in doing the waiting and executing of the specified function.

Here is a simple use case:

-- test_Timer.lua

local Timer = require("Timer")

local function printThis(timer)
  print("this")
end

local function main()
  local timer = Timer {Delay = 1*1000, Period = 300, TimerFunc = printThis}

  timer:start();

  -- wait a few seconds, then stop the time
  print("wait 4 seconds...")
  wait(4*1000)
  print("stop timer")
  timer:stop(1000*4);

  -- Wait a few more seconds then exit
  print("wait 2 seconds...")
  wait(1000*2);
end

run(main)

In this case, I create a timer that has an initial 1 second delay, and a period of 300 milliseconds. So, after the initial delay, the printThis() function will be called. Then every 300 milliseconds after that, it will be called again.

In the sample, the timer is started, which causes it to run independently of the main task. Within the main task, wait 4 seconds, then call the stop() function on the time. Wait two more seconds, and finally exit altogether. This shows that a timer can run independently. The function that is called can be anything. If you want the function to have some parameters, it is itself probably a closure (function within a function). Additionally, since the function is passed the timer as the only parameter, it can cause the timer to stop. Here’s another example where a timer is running, and will stop after a certain number has been reached.

local count = 0;
local function counter(timer)
  count = count + 1;
  if count >= 5 then
    timer:stop();
  end
  print("counter: ",count)
end

local testCounter = function()
  local timer = Timer {Period = 200, TimerFunc = counter, Running=true}
end

run(testCounter)

The counter function is simple. Basically, increment a counter. Once it reaches 5, stop the time. Starting the counter in the first place is also fairly easy. Just set a period (forget the initial delay), tell it which function is to be executed every period, and set it to run automatically (without requiring an explicit ‘start()’).

This will call the function once every 200 milliseconds, and then quit. Nice to have.

With this little component in hand, you can probably imagine how I/O might be accomplished with timeouts. Basically, any I/O operation would look like:

function cancel(ioperator)
  local function closure(timer)
    timer:stop();
    ioperator:cancel();
  end
  return closure;
end

op = startioop(someparams)
timer=Timer{Delay=1000, TimerFunc=cancel(op), Running=true}

Here, a timer is created with a delay of one second. After one second, the timer fires and the cancel.closure() function is called. The operation is cancelled, if it’s not already done, and that’s the end of things. This assumes that there is no harm in canceling an already finished transaction.

Well, that’s just great. I/O with timeouts fully implemented in user space, asynchronous, and all that. This is a great thing for timers, io, and TINN code in general.

The only thing missing is the ability to do async on more than just sockets. So, next up is async ‘file’ I/O in general, of which sockets are a specialized subset.

Advertisements


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s