I want to expand on this, just so my point gets across...
The general process of locking something for exclusive write is this:
my $data = $thing->fetch;
$thing->lock;
$thing->write('new data');
$thing->unlock;
...all well and good. However, these are ADVISORY locks, not actual disk-based locks on the file. That means a script that isn't taught to lock/unlock the file could just write all over it even if your fixed script has it locked. In other words, all of your software that deals with this file must all honour and set locks properly. Let's now not forget about the OS itself. Someone opens the file in Finder or Windows Explorer or something, that has no idea nor does it care about your software advisory locks. It'll write to your file no matter what.
Database. It's your only feasible way forward.
Update: Here is a snippet of an example of the only way I've ever found to 100% ensure that write locking would be effective... a database transaction, which rolls back if the write failed. Note the FOR UPDATE. (I'm no DB expert so I'm putting this out there for feedback on improvements while providing an example for the OP/thread):
if ($bcast_id) {
my $broadcast_transaction_status = eval {
# We have to disable AutoCommit so that all of the DB task
+s
# are build into a single transaction. This locks the row.
$self->dbh->{AutoCommit} = 0;
$self->dbh->do('BEGIN');
# We need to ensure the broadcast is still available. If i
+t is,
# we claim it. If it isn't, we do nothing
my $bcast_status_check_query = qq~
SELECT BroadcastID
FROM $db_table
WHERE BroadcastID=?
AND HostRunning IS NULL
FOR UPDATE
~;
my $broadcast_unclaimed = $self->dbh->selectrow_array(
$bcast_status_check_query,
undef,
$bcast_id
);
if ($broadcast_unclaimed) {
# We only update the DB table with broadcast claimed s
+tatus
# if nobody else has claimed it yet
$self->dbh->do(
qq~
UPDATE $db_table
SET HostRunning=?
WHERE BroadcastID=?
AND HostRunning IS NULL;
~,
undef,
"$hostname, Process $$",
$bcast_id
);
}
else {
# If the broadcast was claimed in between our first ch
+eck and our
# second check inside the transaction, we set this bro
+adcast to no
# longer available
$bcast_id = 0;
}
# Commit the transaction and re-enable AutoCommit so that
# it doesn't impact other DB operations
$self->dbh->commit;
$self->dbh->{AutoCommit} = 1;
1;
}
}
if (! $broadcast_transaction_status) {
$self->dbh->rollback;
$bcast_id = 0;
_email_sysadmins(
$self->dbh,
"Broadcast $bcast_id claim rolled back",
"Broadcast $bcast_id host claim transaction rolled back: $
+@"
);
}
Note the likelihood that the code may have come from a CMS ;) What happens there is we check a row, make sure nobody else has updated it, proceed to update it, but if the update fails, the entire transaction is rolled out with the DB not being touched at all. This is true write locking which can not be had if using a file as the backend. |