Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

No way to specify a run method always gets called every n seconds #9

Closed
emschwar opened this Issue Feb 22, 2012 · 4 comments

Comments

Projects
None yet
2 participants

I have an application where I need to start my run loop at within 1 second of the 0th second of every minute. My first thought was to create a Servolux::Server with an interval of 60:

server = Servolux::Server.new("RunEveryMinute", :interval => 60)
server.include RunThisEveryMinute
server.start

but of course, the server's run method drifts by the duration of the run method every time it's invoked. I believe this is due to Servolux::Threaded::ThreadContainer's run method doing

          sleep interval if running?

I can think of a couple of ways to get around this-- the simplest way I can think of would be to allow interval, here, to be a proc, and then we could do something like:

          if interval.respond_to?(:call)
            sleep interval.call if running?
          else
            sleep interval if running?
          end

Then, for my application, I could do:

   server = Servolux::Server.new("RunEveryMinute", :interval => lambda { 60 - (Time.now.sec) })
   ....

And that should, I think, give me what I want (namely, a run method that is invoked every 60 seconds).

Another option would be to give Servolux::Threaded::ThreadContainer a :strict_interval option (feel free to substitute a better name) that would make it do that calculation itself. That strikes me as a little harder for you to implement, but it might be a nicer API.

Owner

TwP commented Feb 22, 2012

I agree with your assessment of the situation. A strict interval option would be worth implementing inside the Servolux::Threaded module. The sleep interval would be reduced by the run time of the previous loop so that drift would be minimized.

If you have a seriously strict requirement that the run loop fire within 1 second of the minute rollover (a true real-time requirement), then anything you do will drift outside that window. Especially with pathological cases such as hung IO operations or anything that causes the actual processing to take longer than the sleep interval.

I can use some heuristics in my app to make it likely I don't roll over an interval, but if I happens once in a while, that's not the worst case; I just need to ensure that when I'm called, it's within 1 second of the minute rollover. I know I can't guarantee that sleep will return when I want it to, but honestly, a second is a fairly long time, and the number of messages I'll need to send out at that time is severely bounded (max 7 bytes, to at most 10 servers, no ack required). As long as I'm called within, say, between 0.000 and 0.954 of the minute, I figure I should be fine.

I don't know how you'd feel about using another gem (and I'm not sure how this would play with threads), but I managed to hack this together, which is fairly lame, but does give me accuracy up to ~200ms:

  def wait_for_next_tick
   now = Time.now
   time_to_sleep = configuration[:tick_frequency] - ((now.sec % configuration[:tick_frequency]) + (now.usec/1_000_000.0))
   SystemTimer.timeout_after(time_to_sleep) do
     sleep(time_to_sleep*2) # as long as it's bigger than time_to_sleep, it's fine
   end
 rescue Timeout::Error
 end

Right now I'm a bit behind, so I'm just using servolux for the daemonization part, and running this with :startup_command, but I'll try to look at something like this inside Servolux::Server shortly

TwP added a commit that referenced this issue Feb 23, 2012

Add strict interval semantics to the threaded class
A strict interval is used to ensure that subsequent executions of the run loop
occur as close to "interval" seconds as is possible. Currently the run loop
executions drift by the amount of time it takes to run the code. The strict
interval semantics take this time into account when sleeping between
executions.

addresses #9
Owner

TwP commented May 29, 2015

I believe this request is fulfilled with the implementation of strict timing semantics in the threaded module. Closing as fixed.

@TwP TwP closed this May 29, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment