4.14 Using File and Record Locking



As stated before, RMS does not guarantee accuracy of multiple changes to an RMSfile by several users when locking is not used. If a program will be operating in a multi-user environment where some of the users are working with the same RMSfiles, the program should issue calls to the file or record locking routines to ensure data consistency. The following RMS functions require a file or record locking pair around the function call: dinsert(C-3), dupdate(C-3), and ddelete(C-3). The following presents some sample calling sequences that should be used by your programs:

To insert records:

/* prepare data into user buffer */

if (dilock (sub)) { /* first lock file for insert */

dinsert (&subbuf, sub); /* ..then add record */

diunlock (sub); /* ..now unlock file */

}

else

printf ("file busy, try again\n");

To change records using file locking:

if (dflock (sub)) { /* first lock file */

/* find record to change */

dfindk (&subbuf, sub);

/* make changes to record */

dupdate (&subbuf, sub); /* store updated version */

dfunlock (sub); /* .. now unlock file */

}

else

printf ("file busy, try again\n");

To change records using record locking:

/* find record to change */

dfindk (&subbuf, sub);

/* lock the record found */

if (dclock (sub)) {

/* reread the current record, in case it

was changed before it was locked */

dreadc (&subbuf, sub);

/* make changes to record */

dupdate (&subbuf, sub); /* store updated version */

dcunlock (sub); /* .. now unlock record */

}

else

printf ("record busy, try again\n");

To delete records using record locking:

/* find record to delete */

recno = dfindk (&subbuf, sub);

if (drlock (recno, sub)) { /* first lock record */

ddelete (sub); /* ..remove record */

drunlock (recno, sub); /* ..now unlock record */

}

else

printf ("record busy, try again\n");

These sequences illustrate the care that must be taken in the multi-user environment.

Deadlocks

A deadlock situation occurs when two or more processes are competing for a limited set of resources. It is generally assumed that any given program will eventually release (unlock) any resource it is holding (locked). For example, if a program has a record locked, it is assumed that at some point the program will unlock the record.

Deadlocks occur when two or more programs are waiting to lock something. The simplest case involves two programs. The first program locks file A. The second program locks file B. Then, the first program tries to lock file B. Since the file is locked, the first program waits for the file to be unlocked. Now, the second program tries to lock file A (currently locked by the first program). If no deadlock detection is done and the second program decides to wait for the lock, the two programs become deadlocked: the first is waiting to lock file B and the second is waiting to lock file A. Neither program will ever continue because both are waiting.

The above scenario can apply to any mixture of file and record locking. Because this condition is so easy to create, most systems that provide locking mechanisms also perform deadlock detection. If a program tries to wait for a lock and the system determines that waiting would produce a deadlock, an error is returned and no locking is done. This is why the return values of all the waiting locking functions must be checked. Normally, the value will be ok, but there are times when a deadlock is detected and the lock is refused.

What should a program do when it gets a failed wait lock? The best approach, if possible, is to get user interaction. When writing user_edit routines for form(C-3), this can mean returning a message that the file is busy. It is then up to the user to decide when to try again. For a non-interactive process, this can mean producing an error message and letting the user restart the program.

There are times when the above approach is not acceptable. It may be that the program is in some critical stage that must be completed, or any number of other possibilities. In this case, the best solution is to release all locks that the program currently has and then try relocking the locks.

In theory, this defeats the deadlock detection algorithms. In practice, there is another program running that is waiting for something locked by your program. By releasing all locks, the waiting program is allowed to resume and eventually it will release the locks your program needs to complete. In any event, this is a nasty situation to get into and it is best to avoid it if at all possible.

How can a program avoid a deadlock? The easiest way to avoid a deadlock is to only lock one thing at a time. While easy, this is often not practical. For example, when writing user_edit routines, form locks the record or RMSfile associated with the screen at various times. Programs usually need to change data in more than one RMSfile at a time.

The next easiest method of avoiding deadlocks is to not use the waiting locks. If a process does not wait for a locked resource (record or file) a deadlock can't occur. Your program must be prepared for the possibility of the lock failing due to another lock (and do something other than trying again).

The only other way to absolutely avoid deadlocks is to establish an order of locking. The actual implementation of this method is beyond the scope of this manual. The basic idea is to eliminate the circle of locks created by locking things in a random order. In our deadlock example, if we had specified that file A must be locked before file B, the second program would be waiting to lock file A and not have file B locked. In this case, the first program could complete it's task and release file A for the second program. For more details, consult a book on concurrent programming techniques.