flocking issues
Joshua Keroes
joshua_keroes at eli.net
Wed May 30 13:57:43 CDT 2001
flock has been giving me headaches for a week now. Help!
Here are the ingredients:
1. Demo.pm emulates an access-server - essentially a bank of modems at your
ISP. It stores lists of fake users, logged into fakes dialups, and other
associated fake info.
2. foo.pl, bar.pl, and baz.pl all read from Demo.pm, but can write to it as
well. A lookup (who's on what dialup) is a read operation. They can also
disconnect users. That's a write-operation.
3. update_demo.pl writes to Demo.pm.
Real dialup traffic peaks after work, right around 7-8PM, as people go home
and log into their home computers to check email and whatnot. It drops off at
4-5AM as they go to sleep. The amount of traffic resembles a sine wave.
update_demo.pl emulates this by logging fake users on and off.
update_demo.pl stores userids and the dialups they're on in a state file.
It would be bad for more than one program to update the state file at the
same time.
update_demo.pl runs out of cron every minute. On average, it takes 4-5
seconds to complete. The other programs are run by users. All of these
programs attempting to update the state file at the same time is a recipe for
disaster.
---
Q: how do we get update_demo.pl to play nicely with foo, bar, and baz.pl?
A: flock. Create a file-lock when somebody's updating the state file. Make
everyone wait until the flock is gone before they get to work.
---
Implementation:
use constant STALE_SECS => 60 * 60;
use constant MAX_TIMEOUT => 15; # in seconds
# Take control.
# Let nobody else play.
# Do not play nice.
# Do not share.
#
# Accepts an optional filename to use as the flockfile.
#
sub Demo::_lock {
my ($self, $lockfile) = @_;
$lockfile ||= $self->{_lockfile};
my $timeout = 0;
if ( -f $lockfile ) {
# Remove the file lock if it's stale
#
my ($mtime) = (stat $lockfile)[9];
if ($mtime < time - STALE_SECS) {
unlink $lockfile
or die "Can't remove stale lockfile '$lockfile': $!";
}
# XXX spinlocks are bad, I think.
# XXX How do I do this correctly?
#
# Wait MAX_TIMEOUT seconds until an old filelock
# is gone before giving up.
#
while ( -f $lockfile ) {
if ($timeout++ < MAX_TIMEOUT) {
sleep;
} else {
die "Can't acquire lock on $lockfile: timed out";
}
}
}
sysopen LOCK, $lockfile, O_RDWR|O_CREAT, 0660
or die "Can't open file $lockfile: $!";
flock LOCK, LOCK_EX
or die "Can't acquire lock on $lockfile: $!";
}
sub _unlock {
my ($self, $lockfile) = @_;
$lockfile ||= $self->{_lockfile};
flock LOCK, LOCK_UN or warn "Can't unlock $lockfile: $!";
close LOCK or warn "Can't close $lockfile: $!";
unlink $lockfile or warn "Can't remove $lockfile: $!";
}
sub Demo::DESTROY {
my $self = shift;
if ($self->updated) {
my $statefile = $self->{_statefile};
$self->save_state();
}
$self->_unlock;
}
__END__
That should take care of everything, uh... right?
Misc Questions:
Will DESTROY get called if the user hits ^C, sending SIGINT?
If not, should one use $SIG{__DIE__} or $SIG{INT} to call Demo::_unlock?
Is that spinlock as bad as my conscience thinks it is?
---
Tried, fried and tired,
Joshua
TIMTOWTDI
More information about the Pdx-pm-list
mailing list