Skip Menu |

This queue is for tickets about the File-Tail CPAN distribution.

Report information
The Basics
Id: 125813
Status: new
Priority: 0/
Queue: File-Tail

People
Owner: Nobody in particular
Requestors: bram-pcna [...] mail.wizbit.be
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



Subject: Race condition in 'checkpending' when file is truncated
While fixing a race condition in 'checkpending'/'reset_pointers' (bug report to be created soon) I spotted another race condition. v1.3 of File::Tail contains: sub checkpending { my $object=shift @_; my $old_lastcheck = $object->{lastcheck}; $object->{"lastcheck"}=time; unless ($object->{handle}) { $object->reset_pointers; unless ($object->{handle}) { # This try did not open the file either return 0; } } $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END); if ($object->{"endpos"}<$object->{curpos}) { # file was truncated $object->position; } elsif (($object->{curpos}==$object->{"endpos"}) && (time()-$object->{lastread})>$object->{'resetafter'}) { $object->reset_pointers; $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END); } if ($object->{"endpos"}-$object->{curpos}) { sysseek($object->{handle},$object->{curpos},SEEK_SET); readin($object,$object->{"endpos"}-$object->{curpos}); } return ($object->{"endpos"}-$object->{curpos}); } If file is truncated after: $object->reset_pointers; and before $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END); then result will be: $object->{"endpos"}: set to new file size $object->{"curpos"}: set to last byte that was read before being truncated this then causes File::Tail::read to return instantly and *not* return data.. (and when it's called in a while loop it keeps the CPU busy) Triggering the race condition is easiest if code is slightly altered: $object->reset_pointers; print STDERR "# Time to trigger race condition (sleeping 5 seconds)\n"; sleep 5; print STDERR "# Done sleeping\n"; $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END); Running it: first create file '/tmp/foo' with contents 'foo' (`echo foo > /tmp/foo`) Then run: #!/usr/bin/perl use strict; use warnings; use File::Tail; my $f = File::Tail->new(name => "/tmp/foo", maxinterval => 1, tail => 0); print while (defined ($_ = $f->read())); When it prints "Time to trigger race condition": truncate the file: (`echo a > /tmp/foo`) Checking running processes shows that's using 100% CPU, new data added in the file is also not printed.. What happens: - in 'checkpending': number of bytes to read is calculated as '$object->{"endpos"} - $object->{"curpos"}': this becomes a negative number. - 'readin' is called with a negative length but this ignores it (ok I guess) - 'read' is called (by the calling script): this first check if there is pending data by calculating: '$object->{"endpos"} - $object->{"curpos"}'. This too is negative.. What happens next: 'read' compares this number with 0; if it's not zero it calls readin() (which ignores the negative number) and then returns empty string - script then calls 'read' again and same thing happens Fixing this race condition: in 'checkpending' checking if file is truncated should be done *after* $object->{"endpos"} is updated (which is done after call to 'reset_pointers'). That is: ... $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END); if (($object->{curpos}==$object->{"endpos"}) && (time()-$object->{lastread})>$object->{'resetafter'}) { $object->reset_pointers; $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END); } if ($object->{"endpos"}<$object->{curpos}) { # file was truncated $object->position; } ...
Subject: 003-truncated_race.patch
diff --git a/Tail.pm b/Tail.pm index 068ac74..dc7acc9 100644 --- a/Tail.pm +++ b/Tail.pm @@ -425,14 +425,16 @@ sub checkpending { } $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END); - if ($object->{"endpos"}<$object->{curpos}) { # file was truncated - $object->position; - } elsif (($object->{curpos}==$object->{"endpos"}) + if (($object->{curpos}==$object->{"endpos"}) && (time()-$object->{lastread})>$object->{'resetafter'}) { $object->reset_pointers; $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END); } + if ($object->{"endpos"}<$object->{curpos}) { # file was truncated + $object->position; + } + if ($object->{"endpos"}-$object->{curpos}) { sysseek($object->{handle},$object->{curpos},SEEK_SET); } diff --git a/Tail.pm.debug b/Tail.pm.debug index 683776b..5abdd53 100644 --- a/Tail.pm.debug +++ b/Tail.pm.debug @@ -440,15 +440,17 @@ sub checkpending { $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END); $object->logit("endposition = ".$object->{curpos}); - if ($object->{"endpos"}<$object->{curpos}) { # file was truncated - logit($object,"File was truncated - resetting position"); - $object->position; - } elsif (($object->{curpos}==$object->{"endpos"}) + if (($object->{curpos}==$object->{"endpos"}) && (time()-$object->{lastread})>$object->{'resetafter'}) { $object->reset_pointers; $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END); } + if ($object->{"endpos"}<$object->{curpos}) { # file was truncated + logit($object,"File was truncated - resetting position"); + $object->position; + } + if ($object->{"endpos"}-$object->{curpos}) { sysseek($object->{handle},$object->{curpos},SEEK_SET); }