[an error occurred while processing this directive]
Preventing lost updates when using custom file updates

The &set_lock and &free_lock commands allow a script to gain exclusive access to a file from the time &set_lock(FILENAME) is called until either &free_lock(FILENAME) is called, or the script ends. This allows you to avoid the problem of the "lost update" which is possible only in the very rare case where you have a form application in which reads a value from a file, modifies that value (e.g., increments it), and writes it back to the file. Most forms just insert data into or append data to a data file, and in such straight-forward cases the form processor automatically locks files for you to coordinate multiple simultaneous accessors in case your form gets very busy (lot's of people submitting it). Only when you write your own custom file update script to read data from a file, modify it, and write it back, is their potential for a problem. In such a case, if two accessors were running your script at the same time, one could potentially overwrite the other's change if they do not coordinate shared access through file locks.

To illustrate, suppose the current value of the counter is 100, and two people execute this script at the same time. The following sequence of events might very well occur:

  1. User A slurps counter 100 from count.dat
  2. User B slurps counter 100 from count.dat
  3. A increments their local $counter variable from 100 to 101
  4. B increments their local $counter variable from 100 to 101
  5. A writes new value of $counter, 101 to count.dat
  6. B writes new value of $counter, 101 to count.dat

Both users ran the script, so we want the new counter value to be 102. However, we end up with 101 in the file.

This situation can be prevented through the use of file locking. Before slurping the file in, it should be locked:

&set_lock("form/count.dat");
And after overwriting the file with the new value, the file is unlocked:
&free_lock("form/count.dat");

With this procedure, we now get the following sequence of events:

  1. User A acquires exclusive lock
  2. User B attempts exclusive lock, but A already has it. B waits.
  3. A reads counter from count.dat
  4. B continues waiting for exclusive lock
  5. A increments counter from 100 to 101
  6. B waits
  7. A writes new value, 101 to count.dat
  8. B waits
  9. A releases lock
  10. B acquires exclusive lock
  11. B reads counter from count.dat
  12. B increments counter from 101 to 102
  13. B writes new value, 102 to count.dat
  14. B releases lock

If a process attempts a lock on a file already locked, it waits. After about 30 seconds, if it still can't get the lock, it gives up, and 1 is returned (instead of the successful code of 0).

Here's the sample application rewritten with file locking:

# get exclusive access
if (&set_lock("form/count.dat") != 0)

format screen
<H1>This service is very busy - please try again!</H1>
.

else

&slurp("counter","form/count.dat");
$counter=$counter+1;
format screen
<H1>This form has been submitted $counter times.<H1>
.
# now save the new value of the counter
format file form/count.dat overwrite
$counter
.

# release the lock

&free_lock("form/count.dat");

endif

If your script ends without explicitly freeing the lock, the next time a lock is attempted on that file, the locking mechanism will detect that the extant lock is defunct, and grant the lock to the new requester (conceptually, if you forget to free the lock it's automatically released for you when your script ends). However, it's good practice to explicitly free the lock when it's safe to do so.

NOTE: There is no need to use locking for simple writes to files with the format file statement; the form processor automatically uses locking for format file in case two people, for instance, submit a guestbook entry at the same time. Explicit file locking is only required for multi- part transactions (read file data, do something to it, write it back).

[an error occurred while processing this directive]