1-second events


(Paul Brannan) #1

I need to create an event that occurs exactly once per second.
(Understanding that this is impossible on Linux, I’m willing to accept a
solution that will average to exactly one event per second over the
course of a day). Someone on irc suggested writing an extension that
calls setitimer, but 1) I’ve never used setitimer, so I don’t know what
kinds of problems I might run into, and 2) I’d like a solution that
doesn’t rely on SIGALRM, since that limits me to one timer per
application.

Any ideas?

Thanks,

Paul


(Nobuyoshi Nakada) #2

Hi,

I need to create an event that occurs exactly once per second.
(Understanding that this is impossible on Linux, I’m willing to accept a
solution that will average to exactly one event per second over the
course of a day). Someone on irc suggested writing an extension that
calls setitimer, but 1) I’ve never used setitimer, so I don’t know what
kinds of problems I might run into, and 2) I’d like a solution that
doesn’t rely on SIGALRM, since that limits me to one timer per
application.

SIGALRM is used by the interpreter internally, so you can’t use
it.

require 'thread’
event = ConditionVariable.new
m = Mutex.new
Thread.new do
loop do
sleep 1
event.signal
end
end
m.lock
loop do

p Time.now
event.wait(m)
end
m.unlock

···

At Fri, 7 Jun 2002 02:19:52 +0900, Paul Brannan wrote:


Nobu Nakada


(Dossy) #3

What about this:

def do_after(sec, &block)
t = Thread.new(sec) {
sleep sec
}
t.join
yield
end

loop do
do_after(1.0) {
# … stuff to do every second …
}
end

– Dossy

···

On 2002.06.07, nobu.nokada@softhome.net nobu.nokada@softhome.net wrote:

Hi,

At Fri, 7 Jun 2002 02:19:52 +0900, > Paul Brannan wrote:

I need to create an event that occurs exactly once per second.
(Understanding that this is impossible on Linux, I’m willing to accept a
solution that will average to exactly one event per second over the
course of a day). Someone on irc suggested writing an extension that
calls setitimer, but 1) I’ve never used setitimer, so I don’t know what
kinds of problems I might run into, and 2) I’d like a solution that
doesn’t rely on SIGALRM, since that limits me to one timer per
application.

SIGALRM is used by the interpreter internally, so you can’t use
it.

require 'thread’
event = ConditionVariable.new
m = Mutex.new
Thread.new do
loop do
sleep 1
event.signal
end
end
m.lock
loop do

p Time.now
event.wait(m)
end
m.unlock


Dossy Shiobara mail: dossy@panoptic.com
Panoptic Computer Network web: http://www.panoptic.com/
“He realized the fastest way to change is to laugh at your own
folly – then you can let go and quickly move on.” (p. 70)


(Michael Campbell) #4

SIGALRM is used by the interpreter internally, so you can’t use
it.

This seems a rather heavy handed tact to take by the language; do
other scripting languages have this restriction?

···

=====

Yahoo IM: michael_s_campbell


Do You Yahoo!?
Yahoo! - Official partner of 2002 FIFA World Cup


(Nobuyoshi Nakada) #5

Hi,

What about this:

It’s equivalent to:

loop do
sleep 1.0

… stuff to do every second …

end

And runs with 1-second intervals, not periodically.

This is based on yours.

def periodically(sec, *args)
loop do
t = Thread.new {sleep sec}
yield(*args)
t.join
end
end

periodically 1.0 do
p Time.now
sleep 0.5
end

···

At Fri, 7 Jun 2002 09:00:09 +0900, Dossy wrote:


Nobu Nakada


(Paul Brannan) #6

I think it uses SIGVTALRM for threading; this is a common trick to
implement user-level threads. Looking at the source from CVS, though, I
don’t see anywhere that SIGALRM is reserved.

Paul

···

On Fri, Jun 07, 2002 at 11:09:00PM +0900, Michael Campbell wrote:

SIGALRM is used by the interpreter internally, so you can’t use
it.

This seems a rather heavy handed tact to take by the language; do
other scripting languages have this restriction?


(Paul Brannan) #7

I like the simplicity of this solution. But if I change:
p Time.now
to
puts "#{Time.now.tv_sec}.#{Time.now.tv_usec}"
then I see quite a lot of drift:
1023456778.810465
1023456779.815316
1023456780.825325
1023456781.835464
1023456782.845571
1023456783.855783
1023456784.865900
1023456785.876033
1023456786.886212
1023456787.896280
1023456788.896795
1023456789.906567
1023456790.916710

Over the course of a day I would expect to eventually lose a second.

Paul

···

On Fri, Jun 07, 2002 at 09:25:48AM +0900, Nobuyoshi Nakada wrote:

This is based on yours.

def periodically(sec, *args)
loop do
t = Thread.new {sleep sec}
yield(*args)
t.join
end
end

periodically 1.0 do
p Time.now
sleep 0.5
end


(Nobuyoshi Nakada) #8

Hi,

···

At Sat, 8 Jun 2002 00:15:04 +0900, Paul Brannan wrote:

SIGALRM is used by the interpreter internally, so you can’t use
it.

This seems a rather heavy handed tact to take by the language; do
other scripting languages have this restriction?

I think it uses SIGVTALRM for threading; this is a common trick to
implement user-level threads. Looking at the source from CVS, though, I
don’t see anywhere that SIGALRM is reserved.

Sorry, mistaken. You’re right.


Nobu Nakada


(Dossy) #9

I like the simplicity of this solution. But if I change:
p Time.now
to
puts "#{Time.now.tv_sec}.#{Time.now.tv_usec}"
then I see quite a lot of drift:
[…]

Over the course of a day I would expect to eventually lose a second.

Welcome to the world of soft real-time systems. You’re battling
computational overhead (thread, code execution, load on your
system) as well as things like RTC jitter and not having real
high-resolution time …

Out of curiousity: what are you doing that requires such precise
periodic execution? Embedded software for a pacemaker? :slight_smile:

– Dossy

···

On 2002.06.07, Paul Brannan pbrannan@atdesk.com wrote:


Dossy Shiobara mail: dossy@panoptic.com
Panoptic Computer Network web: http://www.panoptic.com/
“He realized the fastest way to change is to laugh at your own
folly – then you can let go and quickly move on.” (p. 70)


(Wakou Aoyama) #10

loop do
select(nil, nil, nil, 1.0)
puts "#{Time.now.tv_sec}.#{Time.now.tv_usec}"
end

1023463440.611578
1023463441.611593
1023463442.611581
1023463443.611563
1023463444.611587
1023463445.611582
1023463446.611578
1023463447.611576
1023463448.611577
1023463449.611585

a little better?

···

On Fri, Jun 07, 2002 at 10:35:58PM +0900, Paul Brannan wrote:

1023456778.810465
1023456779.815316
1023456780.825325
1023456781.835464
1023456782.845571
1023456783.855783
1023456784.865900
1023456785.876033
1023456786.886212
1023456787.896280
1023456788.896795
1023456789.906567
1023456790.916710


Wakou Aoyama wakou@ruby-lang.org


(Nobuyoshi Nakada) #11

Hi,

···

At Fri, 7 Jun 2002 22:35:58 +0900, Paul Brannan wrote:

I like the simplicity of this solution. But if I change:
p Time.now
to
puts "#{Time.now.tv_sec}.#{Time.now.tv_usec}"
then I see quite a lot of drift:

See [ruby-talk:41954].


Nobu Nakada


(Paul Brannan) #12

Five milliseconds of drift is a lot of drift, even for a non-real-time
application, as it only takes 200 iterations to lose a second. It’s not
so important to me that I have precise periodic execution, but more so
that over the course of an hour I get exactly 3600 events (with 5 ms of
drift, I would get roughly 3572 events). I suppose it’s not critical,
but it makes analyzing my log files a lot easier.

Paul

···

On Fri, Jun 07, 2002 at 10:57:14PM +0900, Dossy wrote:

Out of curiousity: what are you doing that requires such precise
periodic execution? Embedded software for a pacemaker? :slight_smile:


(Dave Thomas) #13

Wakou Aoyama wakou@ruby-lang.org writes:

1023456778.810465
1023456779.815316
1023456780.825325
1023456781.835464
1023456782.845571
1023456783.855783
1023456784.865900
1023456785.876033
1023456786.886212
1023456787.896280
1023456788.896795
1023456789.906567
1023456790.916710

loop do
select(nil, nil, nil, 1.0)
puts "#{Time.now.tv_sec}.#{Time.now.tv_usec}"
end

Here’s a version that corrects for the execution time of the body:

 def every(period)
   base = last = Time.now.to_f
   count = 0

   loop do
     now = Time.now.to_f
     actual_secs = now - base
     expected_secs = period * count
     correction = expected_secs - actual_secs
     correction = -period if correction < -period
     select(nil, nil, nil, period + correction)
     now = Time.now
     last = now.to_f
     count += 1
     yield(now)
   end
 end


 every(1.0) do |now|
   puts "#{now.tv_sec}.#{now.tv_usec}"

   # the sleep below simuates doing some arbitrary amount of work
   # that takes up to 1 second
   
   sleep(rand())
 end



 1023465199.140059
 1023465200.140064
 1023465201.140056
 1023465202.140069
 1023465203.140065
 1023465204.140062
 1023465205.140089
 1023465206.140062
 1023465207.140061
···

On Fri, Jun 07, 2002 at 10:35:58PM +0900, > Paul Brannan wrote:


(Christopher Browne) #14

Centuries ago, Nostradamus foresaw when Paul Brannan pbrannan@atdesk.com would write:

Out of curiousity: what are you doing that requires such precise
periodic execution? Embedded software for a pacemaker? :slight_smile:

Five milliseconds of drift is a lot of drift, even for a non-real-time
application, as it only takes 200 iterations to lose a second. It’s not
so important to me that I have precise periodic execution, but more so
that over the course of an hour I get exactly 3600 events (with 5 ms of
drift, I would get roughly 3572 events). I suppose it’s not critical,
but it makes analyzing my log files a lot easier.

If you strictly need that kind of accuracy, then you’ll need to
design your process in keeping with that.

What you’d need would be to have part of that “scheduler” that tracks
how much things are drifting, and waiting/acting-early in order to
adjust for the drift.

If the only granularity that you’ve got is something that will do
something looking like sleep(1), with resolution of 1 second, you
can’d do anything better than to be within a second.

If, in contrast, you could live with events every 10 seconds, you can
do quite a lot better. Something with pseudocode sort of like:

This isn’t really ruby, but it’s not anything else either…

start = time.stamp() # Measured in milliseconds
interval = 10
next = start + interval
loop do
now = time.stamp()
wait_period = Integer(next - now) # How long do we need to wait?
next = next + interval
sleep(wait_period)
do_whatever()
end

The point of the calculations is that you’re always calculating how
long each interval needs to be.

If there is some regular drift, whereby the average “sleep” needs to
be 9.5 seconds, then it’s likely that there will be a rough
alternation between 9 and 10 second intervals.

This actually ought to work marginally acceptably even with 1 second
intervals; what will likely happen is for there to be a set of sleep()
calls with for 0, 1, and maybe, sometimes 2 seconds. Unfortunately,
with the very low resolution, that’ll jitter all around.

The larger the number of time intervals that you have to work with,
the less jittery it should be.

  • If you can sleep for intervals measured in milliseconds, then you
    can get a pretty steady result when trying to get 1 second
    intervals, as there shouldn’t be need to get off by more than a
    millisecond or two.

  • If you can sleep for intervals measured in seconds, and want 10
    second intervals, you ought to be able to stay within 1s. That’s
    much less satisfactory than with ms intervals.

  • Trying to be within 1s with 1s sleep() intervals is going to be
    highly jittery.

···

On Fri, Jun 07, 2002 at 10:57:14PM +0900, Dossy wrote:

(concatenate 'string “aa454” "@freenet.carleton.ca")
http://www3.sympatico.ca/cbbrowne/linuxxian.html
Why are men like blenders?
You need one, but you’re not quite sure why.


(Pit) #15

You can get it even simpler with the original code and a method
sleep_until:

def sleep_until( end_time )
duration = end_time - Time.now
sleep( duration ) if duration > 0
end

def periodically( seconds, *args )
next_time = Time.now
loop do
sleeper = Thread.new { sleep_until( next_time += seconds )}
yield( *args )
sleeper.join
end
end

periodically( 1.0 ) do
puts "#{Time.now.tv_sec}.#{Time.now.tv_usec}"
sleep( rand * 1.3 )
end

Just as Dave’s code (I suppose), this should also handle work
times longer than 1 second, as long as the average work time is
less than one second. This is simulated by “rand * 1.3” in the code
above and shown in the following sequence:

1023572512.226000
1023572513.217000
1023572514.219000
1023572515.220000
1023572516.222000
1023572517.453000 # work longer than a second
1023572518.515000 # work longer than a second
1023572519.216000 # back to normal
1023572520.217000
1023572521.219000
1023572522.220000
1023572523.222000
1023572524.223000
1023572525.225000
1023572526.226000
1023572527.217000
1023572528.259000 # work longer than a second
1023572529.220000 # back to normal
1023572530.222000
1023572531.223000

Regards,
Pit

···

On 8 Jun 2002, at 0:58, Dave Thomas wrote:

Here’s a version that corrects for the execution time of the body:

 def every(period)
   base = last = Time.now.to_f
   count = 0

   loop do
     now = Time.now.to_f
     actual_secs = now - base
     expected_secs = period * count
     correction = expected_secs - actual_secs
     correction = -period if correction < -period
     select(nil, nil, nil, period + correction)
     now = Time.now
     last = now.to_f
     count += 1
     yield(now)
   end
 end

 every(1.0) do |now|
   puts "#{now.tv_sec}.#{now.tv_usec}"

   # the sleep below simuates doing some arbitrary amount of work
   # that takes up to 1 second

   sleep(rand())
 end