The most significant difference between coding applications for single-user environments and multi-user environments (including local area networks) has to do with performing record updates (rewrites) and record deletions.
The basic problem is that to perform a record update or deletion in a multi-user system, you must own a record lock on the record of interest. Locks should be acquired when the user reads the record in preparation for updates, and the programmer should not relinquish the lock until the update is completed. However, one must be careful to ensure that locks are held for the shortest time possible or the performance of the application will be adversely affected.
Two types of locks can be applied to a record:
The c-treeDB API API provides two methods of acquiring these record locks – session-wide record locking (discussed in Starting Session-Wide Locking below) and manual record locking (discussed in the Working with Records / Record Locking section). Session-wide locking is the preferred method, because it is safer:
Calling ctdbLock() passing the appropriate lock mode (CTLOCK_READ, CTLOCK_READ_BLOCK, CTLOCK_WRITE, or CTLOCK_WRITE_BLOCK) initiates session-wide record locking. When calling ctdbLock(), use one of the lock modes listed in Session-Wide Lock Modes.
/* start locking */
if (ctdbLock(hAnyHandle, CTLOCK_WRITE_BLOCK) != CTDBRET_OK)
After a successful call to ctdbLock(), all records read, written, or deleted by the c-treeDB API API will be automatically locked using the lock mode that was passed to ctdbLock(). The automatic locking of records can be suspended temporarily by calling ctdbLock() with the mode CTLOCK_SUSPEND. Suspending session-wide record locking does not cause any existing record locks to be released, but while locks are suspended, additional record reads, writes, and deletions will not be automatically locked.
It is important to keep in mind that you are suspending the session-wide locking mechanism (which makes it stop auto-acquiring new record locks). You are not suspending the locks on individual records.
/* suspend locking */
if (ctdbLock(hAnyHandle, CTLOCK_SUSPEND) != CTDBRET_OK)
printf("Suspend lock failed\n");
Suspended session-wide record locking can be resumed by calling ctdbLock() with one of the resume lock modes CTLOCK_RESUME_READ, CTLOCK_RESUME_LOCK_BLOCK, CTLOCK_RESUME_WRITE, and CTLOCK_RESUME_WRITE_BLOCK. This turns auto-locking back on, while keeping all the locks that were in place before the SUSPEND happened.
/* resume locking */
if (ctdbLock(hAnyHandle, CTLOCK_RESUME_WRITE_BLOCK) != CTDBRET_OK)
printf("Resume lock failed\n");
Locks that were automatically acquired using the session-wide record locking mechanism are freed / released by calling ctdbUnlock() or by calling ctdbLock() with mode CTLOCK_FREE. This also turns off automatic, session-wide locking, so future record reads, writes, and deletes will not automatically acquire locks.
Note that in some cases, releasing locks this way can also release some locks that were manually acquired using ctdbLockRecord(). See ctdbUnlock() for details.
If freeing locks inside an active transaction, session-wide record locking will be suspended, rather than turned off, and the existing record locks will actually be freed/released when the transaction terminates, via a call to ctdbCommit() or ctdbAbort(). Note that these two transaction-ending functions also clear the current session-wide locking mode (turn off session-wide record locking).
/* free automatically-acquired record locks and turn off session-wide record locking */
if (ctdbUnlock(hAnyHandle) != CTDBRET_OK)
printf("Free lock failed\n");
If you wish, you can release only the record locks associated with a particular table by calling ctdbUnlockTable(). Only the locks held for records of that table are released, and all other record locks are kept. This function releases locks automatically acquired via the session-wide locking mechanism, and locks manually acquired via calls to the ctdbLockRecord() function. Note that this function does not affect the current session-wide locking mode.
/* free locks for a table */
if (ctdbUnlockTable(hTable) != CTDBRET_OK)
printf("Free table locks failed\n");
If freeing locks associated with a table inside an active transaction, the locks (both automatically and manually acquired) of updated records will only be actually freed when the transaction terminates via a call to ctdbCommit() or ctdbAbort().
Starting in V10.3, FairCom DB supports opening the same file multiple times in the same connection assigning a different file number to each file or, in c-treeDB API, a different file handle. This can be useful in situations where you want to allow the same file to be opened twice by the same thread with different locking attributes applied to each thread.
Each of these sibling files is referred to as a "co-file." For example, if the file customer.dat is opened in the same connection using file numbers 5 and 10, then we say that file 5 is a co-file of file 10, and vice versa.
In this case there are considerations about how locks interact within the same connection when operating using different co-files. For example, if a write lock is acquired on a record R using file number 5 within the same connection, what is the behavior of trying to acquire a lock on R using co-file number 10?
In this example, before this enhancement, FairCom Server behaved as follows:
The lock on R issued with co-file number 10 succeed and is considered a "secondary lock", while the lock acquired first (using file number 5) is considered "primary."
The difference in the locks manifests itself during calls to unlock the record: If the primary lock is unlocked first, then the primary lock and all the corresponding locks on co-files are removed. But if a secondary lock is unlocked before the primary lock is unlocked, then only the secondary user lock is removed; and the primary lock is maintained.
Any other connection saw the record locked until the primary lock was released.
This previous behavior has been maintained and it is the system-level default behavior.
It is now possible to configure the behavior choosing among 4 different options:
Recursive locks are not supported for co-files. An attempt to open a co-file when recursive locks are pending on the underlying file will fail with the error MUOP_RCR (998). An attempt to issue a lock on a co-file with the ctLK_RECR bit set in the lock mode will fail with the error MLOK_ERR (999).
Read locks behave in a manner consistent with write locks. The notable issues are:
The system-level default can be controlled by using one of the following configuration keywords which sets the behavior accordingly to their names.
A connection can override the system-level default for all open instances of a file by calling:
PUTHDR(datno, mode, ctMULTIOPNhdr)
Where mode is one of the following:
If no PUTHDR call is made, the system-level default is used for that connection's instances of the file. When a file is opened, if that connection already has the file open, the newly opened file inherits the MULTIOPN setting of the already-open file instance. An attempt to change the setting so that one instance of the file would be inconsistent with the others will fail with error MOFL_ERR. A file's MULTIOPN state can only be changed if it is the first open instance of the file and it has no pending locks.