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