Supporting Multiple Clients
This chapter discusses the following topics:
Btrieve Clients
A Btrieve client is an application-defined entity that makes Btrieve calls. Each client can make Btrieve calls and has its own resources (such as files) that are registered with the MicroKernel. In addition, the MicroKernel maintains the status of transactions (both exclusive and concurrent) on a per-client basis.
When you need to support multiple clients concurrently, use the BTRVID or BTRCALLID function, which includes a Client ID parameter. The Client ID parameter is the address of a 16-byte structure that allows the MicroKernel to differentiate among the clients on a computer. Following are examples of situations in which using a Client ID might be useful:
•You write a multi-threaded application that conducts several transactions, all in progress at the same time. For each Begin Transaction operation, the application specifies a different Client ID. The MicroKernel maintains separate transaction states for each Client ID.
•You write an application that uses two Client IDs, and for each Client ID, opens several files. Your application can execute a Reset operation using BTRVID, BTRCALLID, or BTRCALLID32, causing the MicroKernel to close the files and free the resources for a single specified Client ID.
•You write an application that allows multiple instances of itself to run simultaneously. For the integrity of your application’s data, all instances must appear to the MicroKernel as a single client. In this situation, your application provides the same Client ID parameter on each Btrieve call, regardless of which instance of the application is making that call.
•You write an application that acts as a Dynamic Data Exchange (DDE) server. Your server application, which makes Btrieve calls, must divide the returned information between the applications originating the requests to your server application. In this situation, your application can assign a different Client ID to each requesting application, providing a way to track information to be distributed among several clients.
The MicroKernel provides several concurrency control methods and uses several implementation tools to resolve conflicts that can occur when multiple clients attempt to access or modify records in the same file concurrently.
The concurrency control methods are as follows:
The implementation tools are as follows:
•Explicit Record Locks
•Implicit Record Locks
•Implicit Page Locks
•File Locks
The following topics discuss MicroKernel concurrency control methods in detail. While reading each topic, refer to Table
31, which summarizes the types of conflicts that can occur when two clients attempt to access or modify the same file. The table describes the actions of local clients.
Note If your application uses the BTRVID function to define and manage multiple clients within the same application, such clients are considered local clients.
In both tables, client 1 performs an action identified by an abbreviation in the far-left column of the table, and then client 2 attempts to perform one of the actions identified by an abbreviation in the top row of the table.
The actions represented by the abbreviations are described in
Action Codes.
Assumptions
Table
31 assumes the following:
•For a specific cell of the table, client 2 attempts to perform an action after client 1 starts performing an action. The first action must finish before the second action can start.
•For any cell in which client 2 performs an update or delete operation, client 2 is assumed to have read the affected record before client 1 performs its action.
•Unless explicitly stated in the action description for client 2 (as in actions MDR and MTDR), clients 1 and 2 always perform their actions on the same record, when both operations are reads or when both operations are updates or deletes.
•Unless explicitly stated in the action description for client 2 (see action ITDP), when both client 1 and client 2 perform an insert, update, or delete operation, both clients change at least one of the pages they have in common.
•When client 2 performs a modification following an insert operation by client 1, the modified record is not the inserted record, although both records share one or more data, index, or variable pages in the file.
Action Codes
RNL | Read without lock request, either non-transactional or in a concurrent transaction. |
RWL | Read with lock request, either non-transactional or in a concurrent transaction. |
INT | Insert, non-transactional. |
ICT | Insert in a concurrent transaction. |
ITDP | Insert in a concurrent transaction, changing different pages than those modified by an insert, update, or delete by client 1, who is also in a concurrent transaction. |
MNT | Modify (update or delete), non-transactional. |
MDR | Modify (non-transactional) a different record than the one modified by client 1. |
MCT | Modify in concurrent transaction. |
MTDR | Modify (in concurrent transaction) a different record than the one modified by client 1. |
EXT | Read, insert, or modify in exclusive transaction. |
Conflict Codes
N/A | Not applicable. |
NC | No conflict or blocking between the actions of client 1 and client 2. |
RB | Record-level blocking. Client 2 is blocked because of a record lock held by client 1. |
PB | Page-level blocking. Client 2 is blocked because of a page lock held by client 1. |
FB | File-level blocking. Client 2 is blocked because of a file lock held by client 1. |
RC | Record conflict. Client 2 cannot execute the operation because the record has been modified by client 1 in the time since client 2 originally read the record. The MicroKernel returns status code 80. |
For conflict codes RB, PB, and FB, the MicroKernel retries client 2’s action unless client 2 has specified a no-wait type operation (for example, a read with a no-wait lock or an insert/modify in a concurrent transaction that was started with a 500 bias). For a no-wait operation, the MicroKernel returns an error status code.
Table 31 Possible File Operation Conflicts Involving Local Clients
Client 2 Action |
| RNL | RWL | INT | ICT | ITDP | MNT | MDR | MCT | MTDR | EXT |
Client Action | | | | | | | | | | |
RNL | NC | NC | NC | NC | N/A | NC | N/A | NC | N/A | NC |
RWL | NC | RB | NC | NC | N/A | RB | N/A | RB | N/A | RB |
INT | NC | NC | NC | NC | N/A | NC | N/A | NC | N/A | NC |
ICT | NC | NC | PB | PB | NC | PB | N/A | PB | N/A | PB |
MNT | NC | NC | NC | NC | N/A | RC | NC | RC | NC | NC |
MCT | NC | RB | PB | PB | NC | RB | PB | RB | PB | PB |
EXT | NC | FB | FB | FB | N/A | FB | FB | FB | FB | FB |
Following are examples for interpreting action code combinations in Table
31:
•Combination EXT-RWL: Client 1 reads a record from the file from within an exclusive transaction. Client 2 receives status code 85 (FB, file-level blocking) when it tries to read a record from the file with a no-wait lock bias in a non-transactional mode. If client 2 specifies a wait lock bias, the MicroKernel retries the operation.
•Combination ICT-ICT: Client 1 inserts a record from within a concurrent transaction. The MicroKernel retries the operation when client 2 attempts to insert a record into the same file, because one of the pages to be modified by the operation has already been changed by the insert operation performed by client 1. If client 2 starts the concurrent transaction with a 500 bias, the MicroKernel returns status code 84. (See the
Assumptions concerning this table.)
•Combination ICT-ITDP: This combination resembles ICT-ICT, except that client 2 does not change any page that has already been modified by client 1. In this case, the operation attempted by client 2 is successful (NC, no blocking, no conflict).
•Combination MCT-MTDR: Although client 1 and client 2 modify different records, client 2 is blocked by a page lock. This blockage results because the records being modified share a data page, index page, or variable page in the file. (See the
Assumptions concerning this table.)
Passive Concurrency
You may choose to rely on passive concurrency for resolving update conflicts if your application performs single-record read and update operations while not inside a transaction or from within a concurrent transaction. Passive concurrency is applied automatically by the MicroKernel; it requires no explicit instructions from the application or the user.
With passive concurrency, the MicroKernel allows a client to read a record without applying any lock bias for the operation; if a second client changes the record between the time the first client reads it and the time the first client attempts to update or delete it, the MicroKernel returns status code 80. In this situation, the modification that the first client initiates is based on an outdated image of the record. Therefore, the first client must read the record again before performing the update or delete operation.
Passive concurrency allows developers to move applications directly from a single-user to a multiuser environment with only minor modifications.
Table
32 and Table
33 show how two clients interact when using passive concurrency: non-transactionally and from within a concurrent transaction.
Table 32 Passive Concurrency (Non-transactional Example)
Client 1 | Client 2 |
1. Open file. | |
| 2. Open file. |
3. Read record A. | |
| 4. Read record A. |
5. Update record A. | |
| 6. Update record A. The MicroKernel returns status code 80. |
| 7. Reread record A. |
| 8. Update record A. |
Table 33 Passive Concurrency (Concurrent Transaction Example)
Client 1 | Client 2 |
1. Begin concurrent transaction. | |
| 2. Begin concurrent transaction. |
3. Read record A. | |
4. Update record A. | |
| 5. Read record A. |
6. End transaction. | |
| 7. Update record A. The MicroKernel returns conflict status code. |
| 8. Reread record A. |
| 9. Update record A. |
| 10. End transaction. |
Note Even though client 2 reads record A after client 1 has already executed the update operation, the MicroKernel correctly detects a conflict error in Step 7. This conflict exists because client 1 does not commit the change it made to record A until ending its transaction in Step 6. By the time client 2 attempts its update in Step 7, the image it read of record A (in Step 5) is outdated.
Record Locking
In many situations, a client might want stronger concurrency control than passive concurrency provides. Thus, the MicroKernel allows a client to ensure that the client can update or delete certain records without getting a conflict error (status code 80, an indication that another client has modified the record after this application read it). To achieve this, the client must read the record with a lock request. If the MicroKernel grants the lock, no other client can lock, update, or delete the record until the client that holds the lock releases it.
Thus, the ability to update or delete the record is guaranteed, even if in some cases the client has to wait because the operation is temporarily blocked. (For example, temporary blocking can occur when another record on the same data page as the client’s record is modified by another application in a concurrent transaction still in progress.)
There are various kinds of record locks a client can explicitly request. For more information, see
Locks.
User Transactions
Transactions reduce the possibility of lost data. If you have a number of modifications to make to a file and you must be sure that either all or none of those modifications are made, include the operations for making those modifications in a transaction. By defining explicit transactions, you can force the MicroKernel to treat multiple Btrieve operations as an atomic unit. To include a group of operations within a transaction, you enclose those operations between a Begin Transaction (19) operation and an End Transaction (20) operation.
The MicroKernel provides two types of transactions: exclusive and concurrent. The type you use depends on how severely you want to restrict other clients’ access to the file you are modifying. (The MicroKernel does not allow other applications or clients to see the changes involved in any transaction (exclusive or concurrent) until the transaction ends.)
When a task operates on a file inside an exclusive transaction, the MicroKernel locks the entire file for the duration of the transaction. Once a file is locked in an exclusive transaction, other non-transactional clients can read the file, but they cannot make changes to it. Another client that is also in an exclusive transaction cannot perform any operations that require a position block on the file (even standard Get or Step operations), until the first client unlocks the file by finishing the transaction.
When an application operates on a file inside a concurrent transaction, the MicroKernel locks only the affected records and pages in the file, as follows:
•The MicroKernel locks one or more records in a Get or Step operation if that operation has been specifically called with a read lock bias or if that operation has inherited a read lock bias from the Begin Transaction operation. (See
Locks for information about locks and lock biases.)
•The MicroKernel locks records on a data page being modified in an insert, update, or delete operation. Additionally, if the record is a variable-length record, the MicroKernel locks all the variable pages containing portions of the record. Finally, the MicroKernel locks entries on any index pages that will be modified as a result of the insert, update, or delete operation. A small percentage of index page changes may cause entries to move from one page to another. An example is when an index page is split or combined. These changes will result in a full page lock on the index page until the transaction is completed.
As with exclusive transactions, other tasks can always read data that is locked from within a concurrent transaction. In any data file, multiple tasks can have their own concurrent transactions operating, in which they are performing insert, update, or delete operations, or in which they are performing Get or Step operations that contain read lock biases. The only restriction on these activities is that no two tasks can lock the same record or page simultaneously from their respective concurrent transactions.
The following additional features apply to concurrent transactions:
•Locked pages remain locked for the duration of the transaction.
•If the transaction simply reads a record, the MicroKernel does not lock either the record or the corresponding page.
•Other clients cannot see the changes to a file in a concurrent transaction until the transaction ends.
Locks
Records, pages, or even an entire file can be locked. Once locked, a record, page, or file cannot be modified by any client other than the one responsible for the lock. Similarly, locks owned by one client can prevent record, page, or file locking by another client, as explained in the rest of this section.
The MicroKernel provides two kinds of locks: explicit and implicit. When a client specifically requests the lock by including the lock request with a Btrieve operation code, that lock is called an
explicit lock. However, even when a client does not explicitly request a lock, the MicroKernel may lock an affected record or page as the result of an action that the client performs. In this situation, the lock that the MicroKernel makes is called an
implicit lock (see
Implicit Record Locks and
Implicit Locks).
Note Unless otherwise noted, the term record lock refers to an explicit record lock.
Records can be locked implicitly or explicitly. Pages can be locked only implicitly. Files can be locked only explicitly.
The rest of this section discusses the various locks as they apply in both non-transactional and transactional environments.
Explicit Record Locks in a Non-Transactional Environment
This section discusses explicit record locks in a non-transactional environment. For information about how transactions affect the use of record locks, see
Record Locks in Concurrent Transactions.
A client might not want to rely on passive concurrency. However, that same client might need to ensure that any record it reads can later be updated or deleted without receiving status code 80 (which would require the client to reread the record). A client can comply with both these requirements by requesting an explicit record lock on the record. For your application to lock a record when reading it, you can add one of the following bias values to the appropriate Btrieve Get or Step operation code:
•100 – Single wait record lock.
•200 – Single no-wait record lock.
•300 – Multiple wait record lock.
•400 – Multiple no-wait record lock.
You can only apply these lock biases to Get and Step operations. You cannot specify a lock bias on any other operations in a non-transactional environment.
Note Single-record locks and multiple-record locks are incompatible; therefore, a client cannot hold both types of locks simultaneously on the same position block (or cursor) in a file.
Single-Record Locks
A single-record lock allows a client to lock only one record at a time. When a client successfully locks a record with a single-record lock, that lock remains in effect until the client completes one of the following events:
•Updates or deletes the locked record.
•Locks another record in the file (using a single-record lock).
•Explicitly unlocks the record using the Unlock (27) operation.
•Closes the file.
•Closes all open files by issuing a Reset (28) operation.
•Obtains a file lock during an exclusive transaction.
When a client locks a record, no other client can perform any Update (3) or Delete (4) operations on that record. However, other clients can still read the record using a Get or Step operation, as long as the Get or Step operation adheres to the following conditions:
•Contains no explicit lock bias.
•Is not performed from within a transaction that would cause the record to be locked when it is read (as in an exclusive transaction, where the MicroKernel locks the entire file, or in a concurrent transaction that was begun with a lock bias). For more information, see
Record Locks in Concurrent Transactions and
File Locks.
Multiple-Record Locks
A multiple-record lock allows a client to lock several records concurrently in the same file. When a client successfully locks one or more records with multiple-record locks, those locks remain in effect until the client completes one or more of the following events:
•Deletes the locked record(s).
•Explicitly unlocks the record(s) using the Unlock (27) operation.
•Closes the file.
•Closes all open files by issuing a Reset (28) operation.
•Obtains a file lock during an exclusive transaction.
Note An Update operation does not release a multiple-record lock.
As with a single-record lock, when a client locks one or more records using a multiple-record lock, no other client can perform any Update (3) or Delete (4) operations on those records. Other clients can still read any of the locked records using a Get or Step operation, as described in
Locks.
When a Record Has Already Been Locked
When your client requests a no-wait lock on a record that is currently not available (either the record is locked by another client or the whole file is locked by an exclusive transaction), the MicroKernel returns either status code 84 (Record/Page Locked) or status code 85 (File Locked). When your client requests a wait lock and the record is currently not available, the MicroKernel retries the operation.
Record Locks in Concurrent Transactions
Because exclusive transactions (operation 19) lock the entire file, record locks in a transaction apply only to concurrent transactions (operation 1019). (For information about transaction types, see
User Transactions).
The MicroKernel allows a client to lock either single or multiple records in a file from within a concurrent transaction. The client can lock the record(s) by either of the following methods:
•Explicitly specify a record lock bias value on a Get or Step operation using one of the same bias values listed previously. (Record lock bias values for concurrent transactions are the same as those for non-transactional record locks.)
•Specify a record lock bias value on a Begin Concurrent Transaction (1019) operation. Again, these bias values are the same as those for non-transactional record locks, listed previously.
When you specify a record lock bias value on a Begin Concurrent Transaction operation, each operation inside that transaction – if it has no bias value of its own – inherits its bias value from the Begin Concurrent Transaction operation. For example, a Get Next (06) operation inherits the 200 bias from the preceding biased Begin Concurrent Transaction (1219) operation, causing the Get Next to be performed as a no-wait read and lock (206) operation.
As implied in the preceding paragraph, a client can still add bias values to the individual Step or Get operations that occur within the concurrent transaction. Biases added in this manner take precedence over the inherited bias.
The events that cause the release of single- and multiple-record locks in concurrent transactions are similar to those for the non-transactional environment. For single, see
Single-Record Locks. For multiple, see
Multiple-Record Locks with the following exceptions:
•A Close operation does not release explicit record locks secured from within a concurrent transaction. With version 7.0 of the MicroKernel, you can close the file within a transaction even if a record is locked.
•An End or Abort Transaction operation releases all record locks obtained from within the transaction.
Finally, when a client in a concurrent transaction reads one or more records using an unbiased Get or Step operation, and no lock bias was specified on the Begin Concurrent Transaction operation, the MicroKernel performs no locking.
Implicit Record Locks
When a client attempts to update or delete a record, either
external to any transaction or from within a
concurrent transaction, the MicroKernel
implicitly tries to lock that record on behalf of the client. In an exclusive transaction, an implicit record lock is unnecessary because the MicroKernel locks the entire file prior to performing the update or delete operation. (See
File Locks).
The MicroKernel can grant an implicit record lock to a client as long as no other client:
•Holds an explicit lock on the record.
•Holds an implicit lock on the record.
•Has locked the file containing the record.
Note The MicroKernel allows any single client to hold both an explicit lock and an implicit lock on the same record.
The MicroKernel performs the specified Update or Delete operation only if it can successfully obtain the implicit record lock
and any other locks required to secure the integrity of the file during execution of the operation. (See
Implicit Locks).
If the operation is in a nontransactional environment, the MicroKernel drops the implicit record lock on completion of the update or delete operation. If the operation is in a concurrent transaction, the MicroKernel retains the lock. The lock then remains in effect until the client ends or aborts the transaction, or the client is reset (which implies an Abort Transaction operation). No explicit Unlock operation is available to release implicit record locks.
By retaining implicit locks during a transaction, the MicroKernel can prevent conflicts that occur as the result of a client explicitly locking a record (via a Get/Step operation with a lock bias value) if that record has a new uncommitted image that another client generates.
Consider what could happen if the MicroKernel did not retain implicit locks. Client 1, from within a concurrent transaction, performs an update on record A, thereby altering the image of the record. However, because client 1 has not ended its concurrent transaction, it has not committed the new image. Client 2 attempts to read and lock record A.
With no implicit locks retained, client 1 no longer has an implicit record lock on record A, meaning that client 2 can successfully read and lock the record. However, client 2 reads the
old image of record A, because client 1 has not committed the new image. When client 1 ends its transaction (committing the changed image of record A) and client 2 attempts to update record A, the MicroKernel returns status code 80 (Conflict) because client 2’s image of that record is no longer valid. (See the example in Table
34.)
Consider a situation in which a client has locked a record (either explicitly or implicitly) or has locked the entire file containing that record. If another client attempts to update or delete the record in question from within a concurrent transaction – if it tries to implicitly lock the record – some implementations of the MicroKernel will wait, continually retrying until the client whose lock is blocking the operation releases that lock. (No version of the MicroKernel attempts any retry effort for a nontransactional update or delete.)
Supplying a bias value of 500 on the Begin Concurrent Transaction (1519) operation forces the MicroKernel not to retry the insert, update, and delete operations within a transaction.
For local clients, the MicroKernel performs deadlock detection. However, because the 500 bias suppresses retries, the MicroKernel does not need to perform deadlock detection.
This 500 bias value on the Begin Transaction operation can be combined with the record lock bias values. For example, using 1019 + 500 + 200 (1719) suppresses retries for insert, update, and delete operations and specifies single no-wait read locks at the same time.
The following example illustrates the usefulness of implicit locks. In the example, temporarily assume that implicit locks do not exist.
Table 34 Example without Implicit Locks
Client1 | Client2 |
1. Begin concurrent transaction. | |
2. Read record A. | |
3. Update record A (locks on pages involved, but no implicit lock on record). | |
| 4. Read record A with single-record lock (explicit lock on record). |
5. End transaction (releases page locks). | |
| 6. Update record A (conflict, status code 80). |
| 7. Reread record A with lock. |
| 8. Update record A. |
Assuming that the MicroKernel does not apply an implicit record lock in Step 3, client 2 can successfully read and lock record A in Step 4 but cannot update that record in Step 6 because in Step 4, client 2 reads a valid image of record A. However, by the time client 2 reaches Step 6, that image is no longer valid. In Step 5, client 1 commits a new image of record A, thereby invalidating the image of the record read by client 2 in Step 4.
In reality, however, the MicroKernel implicitly locks record A in Step 3, which means that the MicroKernel returns status code 84 in Step 4, requiring client 2 to retry its read operation until client 1 performs Step 5.
Consider what would happen if Steps 3 and 4 were reversed in the preceding example. Client 2 obtains an explicit lock on record A. Client 1 is forced to wait and retry its update operation until client 2 completes its own update of record A (which releases client 2’s explicit lock on that record). On client 1’s next retry to update record A, the MicroKernel returns status code 80. This status indicates that client 1’s image of record A was no longer valid (client 1 having read record A prior to that record being changed by client 2).
Implicit Locks
Clients have significant freedom to modify a file simultaneously because they share cache under the same MicroKernel. Non-transactional modifications (insert, update, or delete operations) never block other non-transactional modifications or modifications in concurrent transactions by another client. Pending modifications in a concurrent transaction do not block other modifications (either nontransactional or in concurrent transactions), as long as those changes do not affect the same records.
The MicroKernel tries, on behalf of the client, to implicitly lock the
records that are modified during execution of an insert, update, or delete operation if the modification occurs either outside of a transaction or from within a concurrent transaction. (In an exclusive transaction, an implicit record or page lock is unnecessary because the MicroKernel locks the entire file prior to performing an update or delete operation. In the case of an Insert operation, the MicroKernel requests a file lock if the client does not have one yet. See
File Locks) As with implicit record locks, the MicroKernel Engine provides implicit page locks; the client does not explicitly request them.
The records on a data page being modified (or inserted) must always be locked. However, a single operation might need to lock several other records as well. For example, if the change made to a record involves one or more of the record’s keys, then the MicroKernel must lock the records on the index pages containing the affected key values. The MicroKernel must also lock all index pages modified by the action of balancing the B-tree(s) during operation. If a modification affects the variable-length portion of a record, the MicroKernel must lock the variable pages as well.
If such an operation is performed in a nontransactional environment, the MicroKernel drops the implicit record locks on completion of the operation. If the operation is performed from within a concurrent transaction, the MicroKernel retains the locks, which then remain in effect until the client ends or aborts the transaction, or until the client is reset, which implies an Abort Transaction operation. No explicit Unlock operation is available to release implicit record or page locks.
If an Insert, Update, or Delete operation issued in a concurrent transaction must modify a record or page (it requires an implicit record lock), but that record or page is currently locked by another concurrent transaction (or the whole file is locked by an exclusive transaction), the MicroKernel waits, continually retrying the operation until the client whose lock is blocking the operation releases that lock. The MicroKernel does not attempt any retry effort for a nontransactional update or delete.
When a client is unsuccessful in getting an implicit record lock, the client can suppress retries on the operation by using bias value 500 on the Begin Concurrent Transaction operation.
Implicit page locks and explicit or implicit record locks have no blocking effect on each other. A client can read and lock a record on a page even if another client has implicitly locked the page containing that record (as long as the record to be locked is not the same one that has been updated, as discussed in
Implicit Record Locks). Conversely, a client can update or delete a record, thereby implicitly locking the data page that contains the affected record even if that data page contains a record already locked by another client.
File Locks
When a client touches a file for the first time in an exclusive transaction, that client tries to obtain a file lock.
Note As the preceding statement implies, the MicroKernel does not lock a file when the client performs a Begin Transaction operation. The lock occurs only when the client reads or modifies a record after performing the Begin Transaction operation.
A file lock, as its name suggests, locks the entire file. A client’s file locks remain in effect until that client ends or aborts the transaction, or until the client is reset (which implies performing an Abort Transaction operation).
If a client tries to lock a file in an exclusive transaction but another transaction already holds a lock on that file (a record, page, or file lock), the MicroKernel waits, continually retrying the operation until the client whose lock is blocking the operation releases that lock. In addition, if a local client blocks the operation and the MicroKernel detects a deadlock situation, the MicroKernel returns status code 78 (Deadlock Detected).
When a client is unsuccessful in getting a file lock, the client can suppress retries on the operation using a no-wait lock bias value of 200 or 400 on the Begin Exclusive Transaction (219 or 419) operation. If a client starts a transaction in this way, the MicroKernel returns status code 84 or 85 when a file lock cannot be granted.
Bias values 200 and 400 are derived historically from record locks. However, the concept of single and multiple locks from the record lock environment means nothing in an exclusive transaction environment. In effect, all records in the file are locked when the file is locked. Only the no-wait meaning of the biases is preserved in the exclusive transaction environment.
The MicroKernel accepts a wait lock bias (100 or 300) on a Begin Exclusive Transaction operation (119 or 319, respectively). However, these additional bias values have no meaning because the default mode on the Begin Transaction (19) operation is to wait.
When any part of a file is first touched in an exclusive transaction, the MicroKernel locks the entire file. Therefore, the MicroKernel ignores record lock bias values explicitly added to the operation codes for any Get or Step operations performed inside an exclusive transaction, with the following exception.
When a client performs a Begin Transaction operation in wait mode (19, 119, or 319), but the first read (Get or Step operation) in that transaction is biased by 200 or 400 (a no-wait lock bias), the no-wait bias takes precedence over the Begin Transaction operation’s wait mode. Therefore, when the client performs this biased read operation but cannot lock the file (for example, another client has already locked a record in the file), the MicroKernel does not wait (which is its default) and does not check for deadlock, because it assumes that the client retries the read operation an unlimited number of times. In this same situation, other versions of the MicroKernel that perform retries automatically recognize the no-wait bias as an indication not to retry the file lock and not to check for deadlock.
Note The 200 and 400 bias values on a Get or Step operation performed from within an exclusive transaction have only the meaning of not waiting; they do not request an explicit record lock, as they would from within a concurrent transaction.
File locks are incompatible with both record locks and page locks; therefore, the MicroKernel does not grant a file lock to a client if another client holds a record or a page lock on that file. Conversely, the MicroKernel does not grant a record or page lock to a client if another client has already locked that file.
Examples of Multiple Concurrency Control
The following examples illustrate the use of the different concurrency control mechanisms.
Example 1
Example 1 shows the interaction between explicit and implicit record locks, implicit page locks, and passive concurrency. Assume that the two records manipulated in this example (record A and record B) reside on the same data page and that the file has only one key. For further explanation of each step, see the paragraphs following the example.
The following table shows the interactions among implicit and explicit record locks, implicit page locks, and passive concurrency.
Table 35 Interaction Among Record Locks, Page Locks, and Concurrency
Client1 | Client2 | Client3 (non-transactional) |
1. Begin Concurrent Transaction with multiple no-wait lock bias (1419) | | |
| 2. Begin Concurrent Transaction with single wait lock bias (1119) | |
3. Read Record A using Get Equal with single no-wait lock bias (205) | | |
| 4. Read record B using Get Equal (5) (single wait lock bias inherited) | |
| | 5. Read record B using Get Equal (5) |
| | 6. Attempt to Delete (4) record B: MicroKernel returns status code 84, and client 3 must retry |
| 7. Update (3) record B | |
8. Attempt to Update (3) record A: MicroKernel must retry | | |
| 9. End Transaction (20) | |
10. Retry Update (3) of record A: Successful | | |
| | 11. Retry Delete (4) of record B: MicroKernel returns status code 80, and client 3 must reread record B |
| | 12. Reread record B using Get Equal (5) |
| | 13. Retry Delete (4) of record B: MicroKernel returns status code 84 |
14. End Transaction | | |
| | 15. Retry Delete (4) of record B: Successful |
In Step 1, client 1’s Begin Concurrent Transaction operation specifies a generic bias value of 400 (multiple-record no-wait locks). This bias will be inherited by each non-biased Get or Step operation in this transaction. At this point, the MicroKernel has applied no locks to the file, its pages, or its records.
In Step 2, client 2’s Begin Transaction operation specifies a generic bias value of 100 (single-record wait locks). This bias will be inherited by each non-biased Get or Step operation in this transaction. The MicroKernel still has not applied any locks to the file, its pages, or its records.
In Step 3, client 1’s Get Equal operation specifies a bias value of 200 (single-record no-wait lock). The MicroKernel accepts this bias value rather than the inherited 400 (multiple no-wait record lock), because an individual operation’s specified bias value takes precedence over any inherited bias value.
In Step 4, client 2’s Get Equal (5) operation does not specify any bias value of its own; therefore, it inherits the single wait lock bias value of 100 from client 2’s Begin Concurrent Transaction (1119) operation. Even though record A and record B are on the same page, both lock requests (Step 3 and Step 4) are successful because a record lock request locks only the specified record; it locks neither the data page on which the record is located, nor any associated index pages.
In Step 5, client 3’s non-transactional Get Equal (5) operation with no lock request is successful, because non-transactional reads are always successful (as long as the requested record exists).
In Step 6, client 3 attempts to Delete (4) record B; however, it cannot obtain the implicit record lock on record B required to delete the record, because client 2 holds an explicit lock on the record. Consequently, the MicroKernel returns status code 84 (Record or Page Locked) to client 3. Client 3 must then relinquish control and retry deleting (if it wishes) later.
In Step 7, client 2 first successfully obtains an implicit record lock on record B. While record B is already explicitly locked by client 2 (because of Step 4’s inherited single wait lock bias from Step 2), no problem exists because both the explicit lock and the implicit lock belong to the same client. At this same time, client 2 also successfully obtains page locks on the data page containing record B and on the index page containing record B’s key value.
Note The data page locked by client 2 also contains record A, which is explicitly locked by client 1. However, as explained in
Implicit Locks, record locks do not block page locks.
When the MicroKernel performs the actual Update (3) operation in Step 7, it writes the new, uncommitted images of the modified data and index pages as shadow pages to the file. At this point, the MicroKernel releases client 2’s explicit lock on record B. However, client 2 retains its implicit record lock on record B, as well as the implicit page locks it just obtained. Even after client 2 completes its Update (3) operation in Step 7, client 3 still cannot get the implicit record lock on record B, because now client 2 holds an implicit record lock on the record. Client 3 must continue its retry efforts.
If the clients are remote, client 2 puts a pending modification status on the file (in addition to the page locks, which are still necessary for concurrency control among clients local to client 2) before actually updating the file.
In Step 8, client 1 first successfully obtains an implicit record lock on record A. Even though record A’s data page has already been locked by client 2, no lock conflict exists, because page locks do not block record locks (see
Implicit Locks). Next, client 1 attempts to obtain an implicit page lock on the data page containing record A. This attempt fails because the data page has already been locked by client 2 in Step 7. Because the Begin Concurrent Transaction (1419) operation did not have a 500 bias specified, the MicroKernel retries the operation. The MicroKernel also performs deadlock detection if the clients are local.
Had client 1 issued its Begin Transaction operation with an additional 500 bias (1919), the MicroKernel would have returned control to the user immediately.
If the clients are remote, client 1 encounters the pending modification status set by client 2 in Step 7. Therefore, the MicroKernel retries the operation.
In Step 9, by ending its transaction, client 2 releases its implicit record lock on record B and the implicit page locks on the data index pages it locked in Step 5. At this point, the MicroKernel commits all the new page images that client 2 created during the transaction. These images now become a valid part of the file.
If the clients are remote, client 2 clears the pending modification status on the file, in addition to releasing the locks.
In Step 10, client 1’s continuing update retries are finally successful, because client 2 no longer has record A’s data and index pages locked.
In Step 11, despite the fact that client 2 ended its transaction in Step 9 (thereby releasing all its locks), client 3 still cannot delete record B. Now, when client 3 attempts to delete the record, the MicroKernel’s passive concurrency control returns status code 80 (Conflict), because client 2 has modified record B since client 3 originally read it in Step 5. At this point, client 3 must re-read the record before it can retry the Delete operation.
In Step 12, client 3 reads record B again, getting an image that reflects the changes made to the record by client 2 in Step 7 and committed in Step 9.
In Step 13, client 3 again unsuccessfully attempts to delete record B, receiving a status code 84 from the MicroKernel. This status code reflects the fact that client 1, in updating record A, has an implicit page lock on the data and index pages containing record B (assuming, as stated earlier, that the same data page contains records A and B, and that the same index page contains the key values for those records).
In Step 14, client 1 ends its transaction, committing its changes and releasing its implicit page locks.
In Step 15, client 3 is finally able to delete record B.
Example 2
Example 2 shows how file locks and passive concurrency control interact. For further explanation of each step, see the paragraphs following the example.
Table 36 Interaction with File Locks and Passive Concurrency
Client1 | Client2 |
1. Open file 1 (0) | |
2. Open file 2 (0) | |
3. Open file 3 (0) | |
4. Get record E, file 3 using a single record lock (105) | |
| 5. Open file 1 (0) |
6. Begin Exclusive Transaction (119) | |
7. Get record B, file 1 (5) | |
| 8. Get record A, file 1 (5) |
| 9. Update record A, file 1 (3) (status code 85, retrying) |
10. Get record C, file 2 (5) | |
11. Update record C, file 2 (3) | |
12. Delete record B, file 1 (4) | |
13. End Transaction (20) | |
| 14. Retry Step 9 (successful) |
In Step 4, client 1 obtains an explicit record lock on record E, file 3.
In Step 6, client 1 begins an exclusive transaction. Even though client 1 has three files open, the MicroKernel has not yet locked any of those files, nor does the MicroKernel release the explicit lock on record E in file 3 as a result of performing the Begin Transaction operation.
In Step 7, client 1 obtains a file lock on file 1 as a result of touching the file. (See
File Locks) Step 7 would have failed if, in a previous step (for example, between Steps 5 and 6), client 2 had read a record from file 1 using an operation with a lock bias.
In Step 8, client 2 successfully reads record A from file 1. This read is successful because it does not request any lock. However, had the Get Equal (5) operation been issued with a lock bias, the operation would have failed because client 1 currently has file 1 locked.
In Step 9, client 2 cannot obtain an implicit record lock because client 1 has the file locked. Therefore, the MicroKernel returns status code 85 (File Locked) back to client 2. Client 2 must now relinquish control and retry Step 9 until client 1 ends or aborts its transaction (which happens in Step 13). (See
When a Record Has Already Been Locked)
In Step 10, client 1 obtains a file lock on file 2.
In Step 13, client 1 releases file locks on files 1 and 2.
Note Client 1 never locked file 3 because it never touched that file in its exclusive transaction. In fact, even after Step 13, client 1 retains its explicit record lock on record E of file 3. Client 1 would have released record E only if the client had touched file 3 (thereby locking the entire file in the transaction).
In Step 14, client 2’s retry of the update operation in Step 9 is finally successful.
Concurrency Control for Multiple Position Blocks
The MicroKernel supports using multiple position blocks (cursors) for the same client in the same file.
Inside either a concurrent or an exclusive transaction, multiple position blocks share the same view of modified pages. Each position block in a set of multiple position blocks sees the changes made by the set’s other position blocks immediately, even before those changes are committed.
Multiple position blocks share all locks: explicit and implicit record locks, implicit page locks, and file locks. Consequently, for any client, no one position block’s locks can prevent another position block from obtaining another lock in the same file.
When a client ends or aborts its transaction, the MicroKernel releases all that client’s implicit locks and file locks. However, the MicroKernel releases a client’s explicit record locks only as each position block in a file requires, regardless of whether the lock was granted from within a transaction.
For example, an Unlock (27) operation with -2 as its key value releases only the multiple-record locks belonging to the specified position block. A Close (1) operation releases only those locks obtained for the same position block that is specified when performing the Close operation. Similarly, when a client obtains a position block’s first record, page, or file lock inside a transaction, the MicroKernel releases only those explicit record locks that had been obtained for that position block. In
Example 2, if client 1 had opened the same file three times (instead of file 1, file 2 and file 3), and if client 1 had touched the file using only the first two position blocks, the explicit lock obtained for the third position block would have remained even after the End Transaction operation.
Multiple Position Blocks
If an application using the BTRV function has two active position blocks on the same file and issues a read with a multiple record lock for the same record from both position blocks, both receive a successful status. However, when attempting to unlock the record with either a Key Number of -1 and the position in the Data Buffer or with a Key Number of -2, the record is unlocked only if both position blocks issue the unlock calls. If only one position block makes the unlock call (it does not matter which one), another user receives a status code 84 upon trying to lock the record. After both position blocks issue the unlock, the second user can lock the record.
This behavior is also true with single record locks, although the unlock command in this case does not require a specific Key Number and position in the Data Buffer. However, both position blocks still must issue the unlock in order for another user to lock the record.
Each cursor (that is, each position block) gets a lock. The MicroKernel allows cursors of the same client to lock the same record, but each cursor must issue an unlock before the record is completely unlocked.
ClientID Parameter
When developing an application using the BTRVID function rather than BTRV, you must specify an additional parameter called a ClientID. This allows an application to assign itself more than one client identity to Btrieve and execute operations for one client without affecting the state of the other clients.
For example, assume that two applications are running on Windows and each uses three different clientIDs. This counts as six Active Clients. It does not matter if this is two instances of the same application (and the same ClientID values in each instance) or two different applications. Btrieve distinguishes between each of the six ClientIDs.