Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I was calculating orbital elements once per second with Perl and noticed a glitch that occasionally doubled one of the derivatives for a single second.

I was using the core function "time" and noticed that the unixtime stamp would skip a second just before the event would occur.

Then I tried to see more with Time::HiRes and saw that a second would be skipped when the decimal turned over.

I ran these two lines of code concurrently in bash shells, and saw the strange results:


perl -le 'while () { print time; sleep 1 }'

1579941920
1579941921
1579941922
1579941924 <- WTF?

perl -MTime::HiRes=time -le 'while () { print time; sleep 1 }'

1579941920.99577
1579941921.99751
1579941922.99824
1579941924.00213 <- WTF!

A third terminal running at the same time printed the missing second, but, it skipped one when it eventually turned over.
1579941920.51386
1579941921.51823
1579941922.52046
1579941923.52163 <- OK
1579941924.52477
The interval does not seem significant:
1579941920.99577
1.00174
1579941921.99751
1.00073
1579941922.99824
1.00389
1579941924.00213 <- WTF!
I tried a lot of things like disabling NTP and using usleep but it always skips and causes my program to flip out for a second. What is this sorcery? Thanx

Replies are listed 'Best First'.
Re: missing second of time
by tybalt89 (Monsignor) on Jan 25, 2020 at 15:15 UTC

    Don't sleep FOR one second, sleep until the NEXT second.

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11111868 use warnings; use Time::HiRes qw( time sleep ); while(1) { my $nextsecond = int time + 1; sleep $nextsecond - time; print time, "\n"; }

    Outputs:

    1579965143.0001 1579965144.00009 1579965145.00009 1579965146.00009 1579965147.0001 1579965148.0001 1579965149.00009 1579965150.0001 1579965151.0001 1579965152.00009 1579965153.00009 1579965154.0001 1579965155.00009 1579965156.00009 1579965157.00009 ...
      yeah, the usual trick, but it it should be noted that because of the int the first sleep-interval will be far shorter than one second.

      DB<25> use Time::HiRes qw( time sleep ); DB<26> print time, "\n";$nextsecond = int time + 1;sleep $nextsecon +d - time;print time, "\n" 1579981231.91855 1579981232.00031 DB<27>

      Which is normally not a problem, if the loops body starts with the sleep, before doing something.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      Don't sleep FOR one second, sleep until the NEXT second.

      Thanks for the lightbulb. I see no error since doing it that way. I wonder if the perldoc for sleep should add something like:

        To delay ON each second, don't sleep FOR one second, sleep until the NEXT second, using sleep and time from Time::HiRes:

        Explain why this is a subtle trap:

        while () {
          print something();
          sleep 1 # WRONG!
        }
        
        Calculate the next second:
        use Time::HiRes qw/sleep time/;
        while () {
          print something();
          sleep do { (int time + 1) - time } # RIGHT!
        }
        
      Maybe it's already a FAQ? I don't know. Thank you!

        You are missing the conceptual loophole here.

        > print something();

        So what if something() takes longer than a second?

        Your sleep would just skip the missed steps till the following interval.

        The clean approach would be to include an exception handling.

        Apart from this could the OS be too busy to return in time, that's why sleep only guaranties minimal time.

        See Re: missing second of time

        > I wonder if the perldoc for sleep should add ...

        All these conditions might not be true in your case, but how do you want to include the general case in the docs???

        I think this use cases are best covered in a dedicated module.

        Update

        Might be interesting to compare how setInterval() in JS is handling those cases.

        Update

        From what I read after searching JS setInterval long running function it seems that delayed calls are queued to be executed later, and this without raising an exception. ... Ugly.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      Greeting monks, OP here. Thanks for all the input. I implemented tybalt89's solution (int time + 1) - time and the program ran for about a month until it crashed with this cool error:
      Time::HiRes::sleep(-0.202505): negative time not invented yet
      
      I don't know why that happened but I'm going to try tybalt's other solution at Re: missing second of time and see what happens... Peace, Love and Perl
        I tried the second solution with the fudge factor but it goes negative in less than an hour. I'll go back to the original suggestion and make sure the value is not negative before using it to sleep. Thanks again
Re: missing second of time
by choroba (Cardinal) on Jan 25, 2020 at 11:11 UTC
    sleep says:
    Most modern systems always sleep the full amount. They may appear to sleep longer than that, however, because your process might not be scheduled right away in a busy multitasking system.

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: missing second of time
by afoken (Chancellor) on Jan 25, 2020 at 19:03 UTC

    First, sleep(), like any other system call, can be interrupted at any time.

    (Beancounting: Yes, sleep() is technically no system call, but it uses system calls.)

    That means that sleep(1) may actually return after just a few milliseconds. See also Signal to a sleeping Perl program.

    Second, at any system call, you are returning control back to the operating system. And unless you run a real-time operating system, there are NO guarantees for when you get back control. Most times, it happens within milliseconds. But under heavy load, the operating system may decide that your process has to wait, and so you may get delays in the seconds range from start to end of a system call, and you will see jumps of several seconds.

    Third, even without system calls, your programm will be forcefully interrupted and control returns forcefully to the operating system - unless you are running a real time operating system.

    Fourth, if you need more or less exactly 1.000 s between function calls, don't use a function that has only 1 s resolution for timing. You want at least millisecond resolution. See previous postings by other monks.

    So if you have hard real-time requirements, either use a real-time operating system, or run the timing-critical part on bare metal (e.g. on a microcontroller). See Re^2: Assembly language and Re^14: CPAN failed install for details.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: missing second of time
by tybalt89 (Monsignor) on Jan 25, 2020 at 17:19 UTC

    You could also try "fudging" your way to the next second.

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11111868 use warnings; use Time::HiRes qw( time sleep ); my $fudgefactor = 0.01; while(1) { my $nextsecond = int time + 1; sleep $nextsecond - time - $fudgefactor; 1 while time < $nextsecond; printf "%.6f\n", time; }

    Outputs:

    1579972033.000003 1579972034.000004 1579972035.000004 1579972036.000004 1579972037.000004 1579972038.000005 1579972039.000003 1579972040.000004 1579972041.000004 1579972042.000003 1579972043.000003 1579972044.000004 1579972045.000003 1579972046.000005 1579972047.000003

    Looks fairly consistent :)

      Nice. Here's a way using select(), so that there's no need for non-core modules (ie. Time::Hires):

      use strict; use warnings; my $fudgefactor = 0.01; while (1) { my $nextsecond = int time + 1; select(undef, undef, undef, $nextsecond - time - $fudgefactor); 1 while time < $nextsecond; printf "%.6f\n", time; }

      Output:

      1579973997.000000 1579973998.000000 1579973999.000000 1579974000.000000 1579974001.000000 ^C

        Time::HiRes is core Perl, as much as strict.

        $ corelist Time::HiRes Data for 2020-01-20 Time::HiRes was first released with perl v5.7.3

        Dave

Re: missing second of time
by haukex (Archbishop) on Jan 25, 2020 at 18:19 UTC
Re: missing second of time
by roboticus (Chancellor) on Jan 25, 2020 at 11:29 UTC

    Update: After finishing my coffee, I realize that it's normal behavior: it's just an artifact of adding a number slightly larger than an integer (epsilon_1) to another number slightly less than another integer (epsilon_2), where epsilon_1 happens to be greater than epsilon_2. As an example: 5.99 + 1.02 => 7.01


    I gave it a few tries and was able to reproduce it:

    $ perl -MTime::HiRes=time -le 'while () { $a=time; print $a, " ", $a-$ +b; $b=$a; sleep 1.01 }' Use of uninitialized value $b in subtraction (-) at -e line 1. 1579951233.92519 1579951233.92519 1579951234.92536 1.00017690658569 1579951235.92573 1.00036287307739 1579951236.92672 1.00098919868469 1579951237.93894 1.01222395896912 1579951238.95456 1.01561999320984 1579951239.97017 1.01561403274536 1579951240.98579 1.01561689376831 1579951242.00141 1.01561808586121 !!!! 1579951243.01703 1.01561903953552 $ perl -MTime::HiRes=time -le 'print $Time::HiRes::VERSION' 1.9741 $ perl --version This is perl 5, version 26, subversion 3 (v5.26.3) built for x86_64-cy +gwin-threads-multi (with 7 registered patches, see perl -V for more detail) Copyright 1987-2018, Larry Wall Perl may be copied only under the terms of either the Artistic License + or the GNU General Public License, which may be found in the Perl 5 source ki +t. Complete documentation for Perl, including FAQ lists, should be found +on this system using "man perl" or "perldoc perl". If you have access to + the Internet, point your browser at http://www.perl.org/, the Perl Home Pa +ge.

    Also including output of perl -V for interested parties:

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

Re: missing second of time
by karlgoethebier (Abbot) on Jan 25, 2020 at 16:39 UTC

    Re: timer in perl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

Re: missing second of time
by Anonymous Monk on Jan 25, 2020 at 11:47 UTC
    Use with hires printf "%.0f\n", time;
      It can still happen sometimes. If you sleep for a time longer than 1s, there is a time interval in which the number of sleeps won't be the same as the number of seconds. A similar problem has been discussed recently in The most precise second (timer).
      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
        You're generously overthinking op is an ijit

        only needs rounding

        1579941922.99824 1.00389 1579941924.00213 <- WTF!