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

Honored Monks,

use strict; use warnings; my $expiry = time + 3600; my $time; my $previoustime; while ( $time = time and $time + 300 < $expiry and ( $time - $previoustime >= 10 or sleep (10 + $previoustime - $time) ) ) { $previoustime = $time; warn $time; }

The above code warns (for example) 1447717268;
ten seconds later, it warns 1447717268 and 1447717278 (on separate lines of output);
ten seconds later, it warns 1447717278 and 1447717288; etc.

My intention and expectation was that it start each runthrough only ten seconds after the previous runthrough started, and that it warn only one time (the current one) each runthrough. At least one of those two expectations is not being met, though I don't know which. What's going on, please, and why?

Replies are listed 'Best First'.
Re: while loop acting up, though I'm not sure how
by SimonPratt (Friar) on Nov 17, 2015 at 06:21 UTC
    What's going on, please, and why?

    I'll have a crack at answering this. Its easier to understand when you simplify it down to psuedo-code:

    while set $time to current time test $time < $expiry fail: break loop test $time - $prevtime >= 10 fail: sleep 10 $prevtime = $time warn $time
    So, this is what is happening:
    $expiry = 3600 $prevtime = undef $time = undef 1st loop: $time = 123 $time 123 < $expiry 3600 (true) $time 123 - $prevtime undef >= 10 (true) $prevtime = 123 warn $time 123 2nd loop: $time = 123 (first iteration takes less than 1 second) $time 123 < $expiry 3600 (true) $time 123 - $prevtime 123 >= 10 (false) sleep 10 $prevtime = 123 warn $time 123 3rd loop: $time = 133 (slept on last iteration so advance 10s) $time 133 < $expiry 3600 (true) $time 133 - $prevtime 123 >= 10 (true) $prevtime = 133 warn $time 133 4th loop: $time = 133 (last iteration took less than 1s) ...
    You can clearly see the pattern of sleep, no sleep, sleep, no sleep. Essentially the even iterations are doing the 2nd iteration and odd iterations are doing the 3rd iteration (just advancing numbers in each case).

    That is what is happening. Why it is happening is because you set a fixed point in time and then use current time to compare it, mixing in a bit of sleeping as well. This effectively shifts your processing from working in the now to working in the past (or future, depending on your perspective) and then updating to work on the now again for the next loop (but still comparing your now value to the previous past value).

      Thank you!

        You're welcome

        Its good practice to write up what you want to do in psuedo-code and run a few iterations by hand, to get a feel for the logic you are attempting. Once you've got a bit more experience it'll become second nature and you start being able to do simple ones like this in your head ;-)

        Good luck!

Re: while loop acting up, though I'm not sure how
by Athanasius (Archbishop) on Nov 17, 2015 at 06:28 UTC

    Hello msh210,

    The logic of your while loop condition is too complicated. Rearranging and simplifying, I came up with this, which seems to do what you want:

    #! perl use strict; use warnings; use constant { DELAY => 10, EXPIRY => 3600, MARGIN => 300, }; my $time = time; my $expiry = $time + EXPIRY; while ($time + MARGIN < $expiry) { my $previoustime = $time; warn $time; sleep DELAY + $previoustime - $time unless $time - $previoustime >= DELAY; $time = time; }

    Output:

    16:17 >perl 1453_SoPW.pl 1447741073 at 1453_SoPW.pl line 18. 1447741083 at 1453_SoPW.pl line 18. 1447741093 at 1453_SoPW.pl line 18. 1447741103 at 1453_SoPW.pl line 18. 1447741113 at 1453_SoPW.pl line 18. ...

    A side note: sleep returns the number of seconds slept (as an integer). So the condition:

    ( $time - $previoustime >= 10 or sleep (10 + $previoustime - $time) )

    could evaluate to zero — which is false — causing the loop to terminate prematurely. In my testing I don’t see this happening, but it’s still a logic error (a potential bug), and so a further reason to simplify the logic of the loop condition.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: while loop acting up, though I'm not sure how
by soonix (Chancellor) on Nov 17, 2015 at 08:35 UTC
    You can see what's going on by minimally changing your code, like this:
    use strict; use warnings; my $expiry = time + 3600; my $time; my $previoustime; sub mysleep { printf 'before sleeping: time = %d, $time = %d, %s', time, $time +, "\n"; sleep shift; printf 'after sleeping: time = %d, $time = %d, %s', time, $time +, "\n"; } while ( $time = time and $time + 300 < $expiry and ( $time - $previoustime >= 10 or mysleep (10 + $previoustime - $time) ) ) { $previoustime = $time; warn $time; }

      That adds information, but I still don't see the bug. The output is

      Use of uninitialized value $previoustime in subtraction (-) at soonix. +pl line 15. 1447773471 at MWE.pl line 22. before sleeping: time = 1447773471, $time = 1447773471, after sleeping: time = 1447773481, $time = 1447773471, 1447773471 at MWE.pl line 22. 1447773481 at MWE.pl line 22. before sleeping: time = 1447773481, $time = 1447773481, after sleeping: time = 1447773491, $time = 1447773481, 1447773481 at MWE.pl line 22. 1447773491 at MWE.pl line 22. before sleeping: time = 1447773491, $time = 1447773491, after sleeping: time = 1447773501, $time = 1447773491, 1447773491 at MWE.pl line 22. 1447773501 at MWE.pl line 22.

      etc. The reason for the second warn each time around is (presumably) because mysleep doesn't get called i.e. because !($time - $previoustime >= 10) i.e. (presumably) because $time - 10 < $previoustime. But that's not the case: after all, we've set $previoustime = $time (which we were just warned is time - 10) and then $time = time. So I still don't get what's going on.

        • The first time, $previoustime is undefined (warning from "line 15"), hence ($time - $previoustime >= 10) is true and the rest of the or is ignored (first "line 22" warning), because the loop condition is true anyway.
        • Second time the condition is evaluated: Since in the previous iteration the condition was true without sleep, the time assignment happened within the same second as the previous one, resulting in a sleep 10
        • Third time: After the sleep, time has advanced by 10 seconds, ($time - $previoustime >= 10) being true again, same situation as in first iteration, i.e. no sleep.
        • Fourth iteration is just like the second
        The sleep is not evaluated (hence, not executed) every odd iteration because of the "short-circuit" nature of perl's boolean operators. davido explains this thoroughly in Perl Idioms Explained - && and || "Short Circuit" operators - despite the subject, this is also regarding and and or.
Re: while loop acting up, though I'm not sure how
by Anonymous Monk on Nov 17, 2015 at 00:00 UTC

    What's going on, please, and why?

    Rewrite the code so it tells you what is going on, its Basic debugging checklist

    Basically start with something like this and run with it

    use strict; use warnings; my $expiry = time + 3600; my $time; my $previoustime; #~ while ( 1 ) { for(1..10){ $time = time; my $and = $time + 300 < $expiry; my $or = $time - $previoustime >= 10; $or or my $orr = sleep (10 + $previoustime - $time); dd( [ time => $time], [ expiry => $expiry,], [ and => $and ], [ or + => $or, ], [ orr => $orr ] ); $previoustime = $time; warn $time; } __END__

      Thanks for your reply. There's no dd function in my version of Perl, but I used

      print "@$_" for @{[[ time => $time], [ expiry => $expiry,], [ and => $and ], [ or => $or, ], [ orr => $orr ] ]};

      instead and the output was singularly uninformative. I don't know what you were trying to point out in those results; perhaps you can clarify. (Or maybe dd would have been more informative?)

      However, I'll try other things from that debugging-advice page. Thanks for the link.

        Data::Dump brings in dd, sorry about that :)

        . I don't know what you were trying to point out in those results; perhaps you can clarify.

        Huh?

        If you want to figure out what is going on, you start by gathering information -- the beginning

        Then you either see your problem (wrong value, wrong condition, wrong assumption...) or look for more/different data or you throw away the whole thing and start over

        I'm not sure where your confusion lies so I can't clarify

        Maybe its as simple as

        while( tenSecondsHasPassed() ){ doStuff(); } sub tenSecondsHasPassed { my $newtime = time; my $diff = $newtime - $lasttime; ... ## if expired "end loop" ## if not ten seconds, sleep to make it ten seconds ## if expired "end loop" ## otherwise continue looping }