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

Hi, I just want to confirm if the following scenario is in fact possible:

$a = (localtime)[1]; while (length($a) lt 2) { $a = "0$a"; }

Can this ever, under any circumstances, cause an infinite loop? Is there any circumstance under which Perl might be convinced that it should convert $a to an integer after the assignment, thereby causing the test to always fail?

Yes, I know this is horribly bad code... I didn't write this, but I'm trying to determine if this is the cause of the problem I'm seeing. This is in a CGI script which has been running all night at 100% CPU. Unfortunately I can't reproduce this problem, and the script wasn't running with debugging on, so all I could do was to use gdb to try to find out what happened.

I attached strace to the process, but see nothing: it doesn't seem to be making any syscalls. Attaching gdb to the process (and tediously interrupting/resuming it many times and collating the backtraces), I see that it's making the following calls (in unknown order; there are no debugging symbols so I don't know what order these functions are called in, and I don't know how complete this picture is):

(X -> Y in the above list means that a backtrace shows that function X called function Y at some point. Chained arrows indicate that at some point I saw that nested sequence of calls in the backtrace.)

From the (very) little I know of Perl internals, this seems to correspond with the code fragment listed above. I don't see any other place in the code that has a tight loop involving these operations. There are several places in the script with similar-looking code; I suggested to the writer of the script to replace them with sprintf, but I wanted to check here to make sure that I'm not totally off-base with my guess that these loops are probably the cause of the problem.

Replies are listed 'Best First'.
Re: Can scalar representation cause infinite loops?
by ikegami (Patriarch) on Jul 19, 2007 at 22:05 UTC

    length($a) lt 2 should be length($a) < 2. You are converting the returned length to a string and doing a lexical (not a numerical) compare.

    $a = "abcdefghij"; length($a) lt 2 ==> "10" lt "2" ==> true $a = "0$a"; length($a) lt 2 ==> "11" lt "2" ==> true ... $a = "0$a"; length($a) lt 2 ==> "20" lt "2" ==> false

    If length($a) starts in [0..1], the final length($a) will be 2.
    If length($a) starts in [2..9], $a won't change.
    If length($a) starts in [10..19], the final length($a) will be 20.
    If length($a) starts in [20..99], $a won't change.
    If length($a) starts in [100..199], the final length($a) will be 200.
    If length($a) starts in [200..999], $a won't change.
    If length($a) starts in [1000..1999], the final length($a) will be 2000.
    If length($a) starts in [2000..9999], $a won't change.
    If length($a) starts in [10000..19999], the final length($a) will be 20000.
    etc.

    This could cause thrashing with very long strings (which localtime doesn't return), but I don't see this ever creating an infinite loop.

    Try using

    my $min = (localtime)[1]; $min = sprintf('%02d', $min);
    • $a (and $b) are reserved for sort. List::Util and List::MoreUtils also use these variables. They're somewhat magical and should be avoided.
    • A nearby declared lexical will avoid the possibility of the variable used being tied or other special.
    • Using sprintf will change the code to something quite different, possibly avoiding a compiler bug.

      Aha! Thanks for pointing out the use of lt vs. <. I've noticed it was odd before, but didn't pause to think about what the consequences are. There is another similar loop earlier in the script that looks like this:

      while (length($extnum) lt $numlength) { $extnum = "0" . $extnum; }

      where $numlength can potentially be set to 4. Again, $extnum is unlikely to be a very long string, so this doesn't seem to be the culprit either, but I'll have to comb through the script again to see if other abuses of lt could have caused the bug.

      Thanks for the prompt reply!

      Yeah, I told the author of the script to use sprintf instead, because it's more efficient, but also because it is actually correct. :-)