System and methods for improved file management in a multi-user environment5692178Abstract A computer system having concurrently shared objects or resources is described. An exemplary embodiment includes a multi-user database management system having information tables and related objects stored in shared directories on a file server. A plurality of lock types, including directory lock, full lock, write lock, prevent full lock, and prevent write lock, are provided for controlling concurrent access. Methods are described for managing locks by creating a special lock file for each shared directory that is accessed. The lock file stores at least one logical lock file having locking or concurrency information specific to a family of related members. The logical lock file itself includes a plurality of entries for specifying concurrency information of associated family members. A shared object or resource is accessed according to the information retrieved from the corresponding logical lock file. Claims What is claimed is: Description COPYRIGHT NOTICE
______________________________________
Prevent Prevent
Full Lock Write Lock write lock
full lock
______________________________________
Full lock
Write lock .check mark. .check mark.
Prevent write .check mark.
.check mark.
lock
Prevent full .check mark.
.check mark.
.check mark.
lock
______________________________________
As shown, full locks are incompatible (i.e., cannot co-exist) with all other locks, including record locks. Write locks, on the other hand, are compatible with prevent full locks and other write locks; however, a write lock does not guarantee that the user will be able to change the write-locked table. Prevent write locks are compatible with other prevent write locks and with prevent full locks. Finally, prevent full locks are compatible with all locks except full locks. Referring now to FIGS. 2B-C, exemplary lock interactions will now be described for concurrent operations in a multi-user environment. As shown, multi-user system 200 includes clients 210 connected to server 230 through network 220. An information table 233, which includes a plurality of information records, is stored in a shared directory of the server 230. Clients 210 includes Users A-D, each of which desires a certain operation to be performed on the table 233. User A, for example, wishes to perform an exclusive write (private edit) operation on the table 233; this operation requires a full lock to be placed on the table 233, as shown in FIG. 2C. Since a full lock is incompatible with other lock types, no other users (i.e., Users B-D) can have access to the table 233 during this exclusive operation. Next, User B wishes to perform a view (read) operation on the table 233. This requires that a prevent full lock be placed on the table, as shown in FIG. 2C. The prevent full lock blocks full locks and, thus, would block an exclusive write operation (e.g., by User A). A prevent full lock does not, however, block other lock types; thus, other operations (such as those of User C and D below) may continue to be performed concurrently. User C desires a copy operation, such as copying the source table 233 into a target table (not shown). For the operation to be successful, the contents of the source table must not change during the operation. With a write lock in place, as shown in FIG. 2C, this requirement is met. Specifically, other users are given read-only access to the table and, thus, cannot change the contents of the table. As the lock is incompatible with both full locks and other write locks, the lock would block the operations of User A (write operation) and User D (CoEdit operation, described next). User D wishes to perform a "CoEdit" operation, i.e., editing or writing to the table 233 but only one record at a time. As shown, a prevent write lock is placed, thus preventing other users from starting operations which require either full or write locks. Hence, the lock would block the operations of User A and User C. C. Automatic and explicit locks Locks can be placed either explicitly by the user or automatically by the system. The former is accomplished by issuing one of a number of lock commands; the latter is invoked in response to a particular operation. Each of these will now be described in further detail. 1. Automatic locks In a multi-user environment, the system of the present invention automatically locks objects during every operation where there could be contention for a resource. In general, locks that are placed automatically by the system are the weakest (least restrictive) possible, yet sufficient for maintaining data integrity for the duration of the operation. This is best explained by an example. When a COPY operation is invoked (e.g., copy a family of objects), the system places a write lock on the source table and on each of the objects in its family. The write lock prevents other users from modifying any member of the source family during the copy operation, but does not prevent them from accessing family members for read-only operations such as viewing. The system also places a full lock on the destination table involved in the copy; this prevents other users from accessing the destination table in any way during the copy. In this instance, the destination table often does not even exist at the start of the COPY operation. Nevertheless, the system can lock it. The ability to lock a nonexistent resource is very useful. For example, this allows a user to prevent others from creating an object during the period of time the user is doing so. Also, other users are prevented from deleting a table out from under the user (i.e., deleting the table between the time the user creates it and the time he or she first uses it). The system of the present invention provides four types of automatic locks: family locks, record locks, group locks, and write record locks. With the exception of record locks, only the system (not the user) can place these locks, and it does so only in specific circumstances. Since the locks are automatically placed on a user's behalf, they do not restrict what he or she can do with an object; instead, automatic locks only restrict what others can do. However, if others are performing operations that lock an object, those locks restrict what the user can do (and hence what automatic locks may be placed). Each automatic lock will now be described in turn. a) Family locks A family lock is a write lock placed on all family members of a table, including the table itself. A family lock allows the same level of concurrency as a write lock: it prevents other users from making changes to any of the family objects. b) Record locks CoEdit mode allows multiple users to edit tables simultaneously and interactively, thus a lock is needed for locking individual records of a table. When the user coedits a record, the system automatically locks it. When the user finishes making changes to the record and moves the cursor to another record, the system automatically removes the lock and posts the user's changes to the table. A LockKey command is also provided for explicitly locking or unlocking a current record. User feedback is provided for indicating when a record is locked. When a record is locked, other users can view it but cannot modify or delete it. If the user tries to change a record that is locked by another user, the system informs the user the name of the user who has locked it. The user also cannot place a write lock on a table that has locked records; thus, the presence of at least one record lock in a table has the same effect as a prevent write lock on the table. c) Group locks and write record locks Group locks and write record locks work in tandem to maintain referential integrity between tables that are linked by key fields through a multi-table form. The system automatically applies both kinds of locks when a user coedits using a multi-table form that expresses a one-to-many or many-to-many relationship. Like record locks, group and write record locks come into play only in a coediting environment and occur at the record level, not the table level; this approach provides the greatest amount of concurrent use. These locks are in fact record locks, locking both linked master and detail records. If the user begins to coedit the primary key of a master record in a multi-table form, the system places a group lock on the detail records owned by that master record. This prevents other users from altering the detail records while the user alters the master (even if they are coediting the detail tables in table view, a single-table form view, or another multi-table form instead of with the same multi-table form one is using). Once the user finishes editing the master record, he or she should move off the master record--to another master record or to a detail record, or press the LockKey command key (e.g., Alt-L), the system automatically makes the same changes to the linked fields of the detail records. Write record locks work from the opposite standpoint. The system applies a write record lock on the associated master record whenever the user opens a record or coedits an existing detail record in a multi-table form. The write record lock thus guarantees that the associated master record is not deleted or changed by others while the user is coediting the detail group of records (even if the other users are coediting the master table in table view, a single-table form view, or another multi-table form instead of with the same multi-table form the user is currently using). The system of the present invention locks objects intelligently. In every situation, the least restrictive lock consistent with the operation the user is performing is applied. The system therefore allows the greatest possible access to tables, forms, and reports by all users. In addition, enhanced concurrency for processing queries and reports are provided. For example, in the case of an INSERT query, when the user places the Query form on the desktop, the system places a prevent full lock on the table the user is querying. When the operation is confirmed by the user, the system of the present invention places a full lock on the table while it processes the query. In some operations, the system may place locks on two or more tables simultaneously. For example, when the user copies a table (i.e., from a source table to a target table), the system automatically places locks on both the source and the target table. 2. Explicit locks Even though the system of the present invention provides automatic locking for every operation, in most multi-user applications, the user will want to use explicit locking commands to control access to resources in addition to depending on the automatic locks. For example, the user might want to lock a table explicitly when he or she wants to make sure it is available (e.g., for continual updating). The explicit lock commands give the user more control than the automatic locks and also make it easier for the user to handle situations where a user cannot place a lock because of contention for a resource with other users. In a preferred embodiment, the user can explicitly lock and unlock tables in two ways: by menu commands (e.g,. using Tools.linevert split.Net.linevert split.Lock and Tools.linevert split.Net.linevert split.PreventLock) or by using database application programming commands (e.g., using LOCK and UNLOCK commands). In this manner, the user can place locks of varying strengths on objects, including (in order of increasing concurrency): full lock, write lock, prevent write lock, prevent full lock, and directory lock. Both explicit and automatic locks can be active at the same time on the same object. For example, if the user employs the LOCK command to explicitly place a full lock on the Orders table and then uses the COPY command to copy Orders to Newords, he or she places both an explicit full lock and an automatic write lock on Orders. From the perspective of other users, Orders appears to have only a full lock on it, since that is the stronger of the two locks the user has placed. At the end of the COPY operation, the automatic write lock disappears, leaving the user's explicit full lock intact. Other users can also place locks, both explicit and automatic, on objects that the user has locked. However, locks so placed must coexist with existing locks (see lock compatibility discussed above). In a preferred embodiment, attempts to place object and record locks, both automatic and explicit, are honored on a first-come, first-served basis. Whether placed by the system automatically or by the user explicitly, a lock of a certain kind retains its same effect. For example, suppose the user want to enter new data into a Customer table (e.g., using a Modify.linevert split.DataEntry command), the system places a prevent full lock on Customer. This prevents other users from changing the table's structure or from editing the table while the user is entering data into it. Then, when the user posts the new records to Customer, the system of the present invention places a prevent write lock on the table. But suppose another user places a write lock on Customer while the first user is entering data. In that case, when the user posts, he or she does not get sufficient control over the table to finish his or her data entry operation. If the user anticipates such a situation, he or she can explicitly place a prevent write lock on the Customer table before the user begin data entry. This guarantees that the table will be available to the user when he or she wants to complete the data entry operation. Explicit locks are used most often for multi-user applications, when the application developer needs precise control over access to tables, forms, and reports. In practice, the user should use explicit locks sparingly, since they might needlessly prevent other users from accessing objects. Instead, the user should employ the automatic locks of the system to maximize concurrent access. Internal Operation of Locks: Lock Files The system of the present invention manages locks by creating a special lock file for each shared directory that users access. As shown in FIG. 3A, for example, a client 310 desires access to a shared table 333 which resides on a file server 330. The system 150, in turn, creates a "physical" lock file 350 for storing various locking and related concurrency information. In a preferred embodiment, the file 350 exists as a separate disk (i.e., "physical") file which is stored on the server 330, preferably in the same network directory as the table. Each file 350 stores at least one logical lock file 380 having locking information specific to the shared table 333 (and its family members). Unlike the physical files, however, the logical lock files do not exist as separate disk files. At the conclusion of a multi-user session for a shared directory, the corresponding physical file, together with its logical files, is deleted. A. Physical lock file layout Referring now to FIG. 3B, the structure of the physical lock file 350 will be described in further detail. As shown, physical lock file 350 stores one or more logical lock files 380. For managing these files, physical lock file 350 also includes a header 360 and a directory 370. Each of these will be described in further detail. Header 360 includes housekeeping information. For example, header 360 stores a "highwater mark" value 365 pointing to the location (end of the physical lock file) where new logical lock files or resized existing logical files are to be allocated (appended). The highwater location varies dynamically according to the number and size of logical lock files present in the file 350. Specifically, as logical lock files are added to the physical file, the highwater mark is adjusted upward. Since logical lock files are preferably never physically deleted, however, the mark will not be adjusted downward. Header 360 also stores a Dir Lock 367, which is a flag indicating whether a directory lock is in place. According to the present invention, a directory lock provides read-only access to all objects present in that directory (as described hereinabove). When present, the directory lock is controlling; specifically, other locks (e.g., record locks, table locks, and the like) are simply ignored (or blocked). With a directory lock in place, the remaining information stored in the logical lock files is irrelevant and, thus, need not be read. Access into the logical lock files themselves is provided by directory 370. The directory stores an entry (e.g., entry 371) for each logical lock file. Each entry includes a name, such as a string or handle corresponding to a family name, and a pointer to the particular logical lock file being referenced. In this fashion, the directory serves as an index into the available logical lock files for a given physical lock file. When a workstation is first accessing an object of a family (e.g., table), it checks the directory 370 for the existence of a corresponding logical lock file. In the instance that a logical lock file does not exist (no directory entry is found), one is created on the fly. Specifically, a new logical lock file is appended to the end of the existing logical lock files (i.e., at current highwater mark 391) and an entry is made in the directory 370 for referencing the file. To speed up subsequent accesses to that logical lock file, however, a workstation need only memorize (e.g., store in local memory 102) the address or offset of that logical file. The system in fact remembers (i.e., stores in memory 102) the respective location of each logical lock file read. In this manner, the directory need only be read once for each particular logical lock file accessed. The logical lock files themselves (e.g., file 381, file 383, file 389) stores concurrency information associated with a particular database family. Each is allocated at an inital block size. Depending on operating system employed, blocks may advantageously be allocated as a multiple of a common disk unit (e.g., as byte clusters of 512, 1024, 2048, and the like). Normalizing the logical file to pre-selected block sizes not only simplifies directory maintenance but also speeds access to the concurrency information contained within the files. The latter advantage is now examined in further detail. To minimize allocation of additional storage for a logical lock file, each block typically includes a free or buffer area, at least initially, in which the number of logical file entries stored can grow and shrink (i.e., without continually allocating additional blocks). When the initial storage capacity of a logical file (e.g., 1K) has been exceeded, the file is effectively moved to the physical lock file 350 with a larger block size (e.g., 2K). After examining the detailed structure of logical lock files, the growth of physical lock files will be described in further detail. B. Logical lock file layout As shown in FIG. 4, the logical lock file or table itself includes a plurality of entries for specifying the concurrency information of associated family members. A preferred layout for a logical lock file 400 of the present invention includes a header 410 and lock data 420. Basically, the header maintains housekeeping information. Lock data 420, on the other hand, stores a variable number of registration entries; exclusive access to these data may be achieved by locking a selected byte ("byte locking") of the logical file. Header 410, as illustrated, includes Family or Dir ID 411 and NBlock 413 fields. Dir ID 411 stores information identifying the family of database objects which is associated with the logical file. Family members include a database table and its related objects, such as forms, data validation, reports, indexes, and the like. In a preferred embodiment, the base name of the database table identifies all related members of a given family. A customer table (e.g., customer.db) may have associated with it a customer form (e.g., customer.f) and a data validation file (e.g., customer.val). Any database object which shares the same base name as the table (e.g., all database objects in the family Customer.*) also employs the same logical lock file. While the base name may be stored as the Dir ID (e.g., as a text string), a simple handle scheme is preferably employed instead. All told, the approach facilitates the locking of associated members, such as locking forms and reports. In addition to storing family information, the header 410 preferably includes information capable of redirecting a workstation to another logical file. As shown, header 410 includes the NBlock field 413 for storing the number of storage blocks allocated for the logical lock file. In the event that a logical lock file is "moved" to a new storage block(s), NBlock 413 is set equal to "DELPENDING" (i.e., delete pending), which in a preferred embodiment is defined by the value 0.times.FFFD. In this instance, a pointer referencing the new logical file block is also stored (e.g., by overwriting the data no longer needed). A detailed example employing this mechanism is set forth below in FIGS. 5A-D. Logical lock file 400 stores the actual concurrency information of interest as registration entries (e.g., record 430) in the data area 420, which includes a single entry header 421. Each entry (entry or record 430) includes information completely describing a single lock or concurrent action. The single header 421 stores status or historic information for the entries, including size 423, soft (special critical section) lock 425, version 427, and the like. Data 431, on the other hand, includes Type ID 433, Length 435, User ID 437, and Registration Data 439 fields. Type ID field 433 includes information (e.g., a special ID code) identifying the general type of information maintained. For example, an entry may be identified as a record lock, group lock, image area, table lock, or the like. User ID 437, on the other hand, identifies the actual holder (user) of the entry. Preferably, this information is stored succinctly as a handle (integer) referencing a network user. Length field 433 stores the length of the record or entry. While most records may be standardized to a fixed length, it is also desirable to store variable information, such as a key value (e.g., for group locks). The length field provides this capability. Moreover, the length information fosters compatibility: a system need not know the length of different entry types beforehand. Instead, the information is simply read as needed. Registration Data 439 stores the specific lock information for the entry. In a table lock, for example, Data 439 stores one of a full lock, a write lock, a prevent full lock, or a prevent write lock. In a record lock, on the other hand, Data 439 stores the record number of the record being locked. To lock a set or range of records (a group lock), Data 439 stores a key value (e.g., $3,000). This is useful for a one-to-many relationship: the detail table may be conveniently locked by the key of the master table. Since different users may have disparate views of the same non-keyed tables, Data area 439 stores information for synchronizing non-keyed tables. If one user, for example, inserts several records before (above) a current record for a second user, the record number for that current record, as seen by the second user, is no longer valid. According to the present invention, in this instance the workstation modifying the shared table examines the lock file to update the image area for other users. In a preferred embodiment, a skew or synchronization factor is added to the image area entries of others, thus adjusting the relative position of the records as viewed by different users. For keyed tables, in contrast, sufficient information already exists in the primary key (i.e., index file) for adjusting relative positions, all without accessing lock files. In actual operation, the individual workstations effect locks by adding and deleting relevant registration entries to the corresponding logical lock file. To add a full lock to a Customer table, for example, a workstation would register with the Customer logical lock file an entry specifying a table lock (Type ID), the holder (User ID), and a full lock (Lock Data). Before such as entry is permitted, however, the system verifies that no incompatabilities occur with existing entries. In this manner, a logical lock file serves as a list which may be sequentially processed for determining lock information for various objects of a family. As locking information for a family of objects may be continually changing, logical file 440 includes a buffer zone or area 440 for accommodating a variable number of registration entries. In this fashion, related concurrency information may be maintained together for rapid sequential access. According to the present invention, however, additional methodologies are provided for even further improving access. c. Logical lock file growth The locking scheme of the present invention includes novel techniques for controlling the addition and revision of locking information. To optimize performance, a particular logical file is preferably always accessed in one contiguous read, thus minimizing the performance penalty for additional I/O operations. This may be achieved by maintaining logical lock files as self-contained blocks. According to the present invention, a logical lock file is created at an initial block size, such as one kilobyte (1K). The entries of the file are stored compacted, without any particular linking information. For example, new entries are simply appended to the end (i.e., at the beginning of the free area); dead entries, on the other hand, are squeezed out during compaction of the logical file. During this time of actually modifying (writing to) a logical file, the accessing workstation maintains exclusive control over that file (e.g., through byte locking). Growth of logical files (and hence underlying physical file) is controlled as follows. When increased storage is required (e.g., when numerous concurrent users are present), the storage currently available for the logical lock file (e.g., 1K) may be insufficient; additional storage must be allocated. According to the present invention, the storage for the logical lock file is not increased by chaining a new storage block to the old one (e.g., through a pointer). Instead, an entirely new logical lock file, one having a larger block size (e.g., now at 2K), is created in the physical lock file. Referring now to FIGS. 5A-D, this operation is illustrated. FIG. 5A illustrates a physical lock file 500a having a plurality of logical lock files. Suppose, for example, that the storage requirement for logical lock file A (501a) exceeds that currently allocated for the file. As shown, additional space 503a is available towards the end of the physical lock file 500a. According to the present invention, additional space for logical lock file A is allocated as follows. As shown in FIG. 5B, an entirely new logical lock file A (501b) is appended to the end of the existing lock files (i.e., at the beginning of available space 503a). The new file (501b), which is allocated twice the original size (i.e., now with two 1K storage blocks), stores current entries as well as any new entries for the logical file. As shown, available or free space 503a shrinks accordingly (now space 503b). To complete the operation, logical lock file A (501a) is now marked as an invalid block (501a'). The invalidation is achieved by enabling a pointer which points from the old block 501a' to the new block 501b. Two items of interest are memorized: a) allocated or maximum size of the logical lock file (ends at 504b), and b) actual size used (bytes of content) in the logical lock file (ends at 505). The latter is used for predictive reading, which is fully described below. At this point, two different perspectives exist for the physical file. For the current workstation (i.e., the one effecting the change), the physical lock file appears as file 500b of FIG. 5C. Specifically, logical lock file of interest, file A (501b), has a new starting location 502b and ending location 507. Thus, access to file A will include a seek to location 502b, followed by a read from location 502b to no more than location 507 (and typically much less). Other workstations, specifically those which have not accessed file A since its re-allocation, will have an outdated perspective. As shown by FIG. 5D, a subsequent workstation will initially access (seek to) starting location 502a--where it expects to find File A. Once at that location, however, it finds that the block (now block 501c) no longer contains valid locking information; instead, the block contains a pointer to new storage block (501b) where the newly allocated file A is stored. In essence, the other workstations are told that the logical lock file has been moved from the first block to a larger second block. Once a workstation has received this notification (that it should access file A at the second block), the workstation will then continue accessing the second block for needed locking information, at least until it is notified of yet another change. Specifically, the offset of interest (offset 502b) is stored locally (e.g., in memory 102) so that subsequent access to the file A may occur directly, i.e., without traversing any pointers or re-reading the directory. In this manner, lock files remain contiguous and, at the same time, file I/O operations are kept to a bare minimum. D. Improved access: predictive reads By "predictively reading" the logical lock files, additional performance gains may be realized. Specifically, each workstation remembers the location and size of a lock file and a predictive amount. On the next read of that file, the workstation seeks to the memorized location and reads the memorized size plus an additional or predictive amount (size read=size from prior access+predictive amount). In the event that a prior size is not known, one may be simply guessed (e.g., defaults to 80% of block size). Suppose, for example, a workstation accesses a logical lock file having a size of 550 bytes. On a subsequent read of this file, the workstation may predict that the lock file will grow in size--that new registrations entries will be added. Instead of just reading 550 bytes, the system may read more, 800 bytes for example. In the event that the logical file is shrinking, on the other hand, the system will read less. In this manner, a logical lock file will always or substantially always be read in a single I/O operation. Referring now to FIGS. 6A-B, the operation of predictive reading is illustrated. In FIG. 6A, logical lock file 600a stores a plurality of entries, spanning from beginning offset 601 to ending offset 603. Thus, the current size (of entries) is illustrated by size 607. Upon subsequent access of the file 600a, a workstation could simply read just the information between offsets 601 and 603. If additional records exist beyond offset 603, however, that approach would require a second read operation, thus incurring a substantial performance penalty. By adding a predictive amount 609 to the previous size 607, the system of the present invention intelligently reads the file, thus minimizing or eliminating additional file I/O operations. This approach is further illustrated in FIG. 6B. To read logical file 600b, the workstation first seeks to location 601. Next, a single, sequential read is performed from location 601 to location 613. In this manner, entries added since the last read (e.g., new entries 611) are read without an additional file I/O operation. While more bytes will typically be read than is absolutely necessary, these additional bytes may be simply ignored (i.e., need not be further processed)--the performance penalty for two or more reads of a physical disk substantially outweighs any disadvantage from an intelligent single read of additional bytes. For networking systems (e.g., Novell's Netware) where the performance penalty for file I/O operations is high, the methodology is particularly advantageous. In the unlikely event that a predictive read fails to read sufficient information or faults (e.g., when the file has moved), an additional file I/O operation may be performed. The worst possible case for predictive reading, however, fares no worse than conventional techniques. Moreover, the predictive amount and the logical file block size may be dynamically adjusted to minimize or virtually eliminate such faults; at the same time, when the size of the lock file decreases, the size of the predictive read decreases as well. E. Byte locking Using the techniques of the present invention, once a logical lock file has been created, each workstation knows where to find the locking information. To actually alter the contents of a logical lock file, however, a workstation may obtain exclusive control over the file. A byte lock is employed for this purpose. Byte locking or record locking occurs as follows. Given a file, a particular byte may be locked by a single user. For each logical lock file, a critical section (i.e., a portion that only one process can modify at a time), such as a particular byte, may be specified for locking. In operation, a byte lock is held for a very short period of time--only the time required to perform the actual update of a logical lock file. After processing the lock information, the system simply releases the byte lock (instead of closing the file). In this manner, the system requires only a single file handle for the physical lock file (instead of one for each logical lock file). Since locking of a byte is an exclusive event, a mechanism is provided by operating systems for controlling conflicts. In Microsoft's MS-DOS, only a simple system is provided: if an attempt is made to lock a byte which is already locked by another, the system only returns an error code. For these types of systems, therefore, a polling mechanism is employed to resolve conflicts of byte locks. In more advanced networking environments, such as Novell's Netware, a queuing mechanism is typically provided, thus eliminating the need to poll byte locks. In these latter systems, the lock requester may wait for any pre-existing byte lock to be removed (e.g., within a specified timeout period). All told, the system of the present invention adapts to the growing and shrinking of lock information in the logical file. By employing byte locking technique, individual lock files may be managed with a minimum of system overhead. F. Preferred method for locking objects Referring now to FIG. 7A, a method 700 for locking objects in accordance with the present invention will now be described. In step 701, a request is received for access to a shared object of a family (e.g., table or related member). At step 702, the method 700 checks to determine if a physical lock file exists for the requested object. If no, then one is created in step 703. Otherwise (yes at step 702), the method skips step 703. At step 704, the system checks for the existence of the logical lock file: either the address is already known (i.e., stored locally from previous read), or found in the directory 370. If no address can be found (i.e., the file does not exist), then the logical file is created (block allocated) in step 705. In this instance, the highwater mark (of FIG. 3) is set to point to the end of the last file block. Otherwise (yes at step 704), the method skips steps 705 and 709. After creating a new physical and/or logical file and updating the high water mark, the method proceeds to step 710. If the logical file is found (yes at step 704), however, then in step 706 the method reads the file after seeking to its offset (beginning address). Since it can be predicted that the lock file will grow, the step performs a larger sequential (predictive) read than would normally be done. In particular, the read includes an amount equal to the size from the previous read plus an additional (predictive) amount. In a streamline embodiment, the predictive amount may be arbitrarily set and then empirically adjusted until an optimum value is obtained; specifically, the value is increased or decreased until read faults are minimized. In a more complex embodiment, expert system techniques, employing a knowledge base and inference engine, may be provided for intelligently determine an optimum value. The design and implementation of expert systems is known in the art. See e.g., Klahr, P. Expert Systems: Techniques, Tools and Applications, Addison-Wesley, 1986; the disclosure of which is hereby incorporated by reference. At step 707, if the currently accessed block of the logical lock file is invalid, then the system will jump to the new valid logical file block (by reading a pointer to it) at step 708; the new address is now remember (stored) locally. To increase performance, often-accessed information, such as offset address, is stored locally (i.e., in the system memory of the workstation), preferably in cache memory (e.g., memory 109). If the current logical file is not invalidated however (yes at step 707), then step 708 is skipped. At step 710, the locking information within the logical lock file of interest is processed; this step is described in further detail below in FIG. 7B. At step 711, the method remembers the current size of the logical lock file. Again, this information is preferably stored in local memory. At step 712, the method loops back to step 706 for new operations with known families. At step 712, the method loops back to step 704 for new operations for unknown families. At step 714, the method loops back to step 701 for operations in a new directory. At the conclusion of these steps, the method has completed. Referring now to FIG. 7B, the individual substeps of step 710 (from method 700) are illustrated. At step 751, the method reads registration entries and applies the information (i.e., effects the necessary locks) accordingly. At step 752 if new entries are to be update, then method proceeds to step 753 to test whether there is sufficient space present in the file block. Otherwise (no at step 752), the method jumps to step 757. If insufficient space exists for new entries, then a new block is allocated at step 754 for the logical lock file. The old file block is invalidated, and a pointer to the new block is stored. Upon reading the pointer to the new address, subsequent workstations will memorize the new address (see step 708). At step 755, the highwater mark is increased by an amount equal to the newly allocated file block. If, on the other hand, sufficient space does exist at step 753, then steps 754 and 755 are skipped. At step 756, the method updates the registration entries, as needed, by writing to the logical lock file; a byte lock is temporarily employed on the logical file during the step. Finally at step 757, if additional operations are to be performed on this object family, then the method loops to step 751 accordingly. Otherwise, the method returns. Attached hereto is an Appendix A further describing techniques for managing locks in a database command language application. User and programmer guides and additional reference materials providing additional description for the system of the present invention are provided as Appendix B. Also attached hereto is a microfiche Appendix C containing source listings in C programming language illustrating exemplary declarations for the lock files of the present invention. While the invention is described in some detail with specific reference to a single preferred embodiment and certain alternatives, there is no intent to limit the invention to that particular embodiment or those specific alternatives. For example, application of the present invention is particularly advantageous in those architectures having shared resources, including not only multi-user platforms but also multi-tasking ones as well. Thus, the true scope of the present invention is not limited to any one of the foregoing exemplary embodiments but is instead defined by the following claims. Appendix A Managing locks in a programmed database application Design goal An important consideration in designing any multi-user application is deciding how to resolve simultaneous requests for the same resources. Apply the weakest locks possible; for example, do not lock an entire table when locking a record suffices, and do not use a full lock when a write lock is sufficient. Application of these principles in the system of the present invention will now be described. Use of locking commands in application programs LOCK and UNLOCK commands are provided to place explicit table locks. Both LOCK and UNLOCK take as arguments a comma-separated list of one or more pairs of strings representing table names and keywords representing lock types. The following command, for example, places a full lock on the Orders table and a write lock on the Stock table: LOCK "Orders" FL, "Stock" WL Keywords WL and FL are defined for write lock and full lock, respectively. Regardless of result obtained (success or fail), the LOCK command always completes its job. For example, if the user attempts to place more than one lock in a single LOCK statement, as above, the command will ensure that either all the locks are placed or none of them. This feature lets the user avoid application deadlocks. Also, LOCK sets a special system variable--Retval--to True or False, depending on whether all the locks were successfully secured. In the event the locking attempt fails, error-code functions (ERRORCODE(), ERRORMESSAGE(), and ERRORUSER()) are provided. For example, if Orders were in use by another user named Ralph when the user issued the preceding LOCK command, ERRORCODE() would return 3 (Table in use), ERRORMESSAGE() would return the string "Orders table is in use by Ralph", and ERRORUSER() would return the string "Ralph". ERRORCODE() returns 0 if the lock succeeds. As good programming practice, the user should either interrogate Retval or use ERRORCODE() after attempting to place explicit locks. It is often convenient to place the LOCK command and the related test on Retval or ERRORCODE() in a WHILE loop, as
______________________________________
WHILE (True)
LOCK "Employee"
IF (Retval) ; lock succeeded?
THEN QUITLOOP ; then continue beyond loop
ELSE MESSAGE ERRORMESSAGE()
; show user the error message
ENDIF
. . . ; rest of module
ENDWHILE
______________________________________
What is placed in the ELSE clause in this code depends upon the application. For example, it might be appropriate to ask users whether they are willing to wait for the table to be free. If a user has structured the application in such a way that it will be tied up for only a very short period of time, he or she may just want the application program to display a Waiting... message, pause for a second using the SLEEP (delay) command, then loop back to the top of the WHILE. The user should avoid looping without first pausing, as this will load the network unnecessarily. A SETRETRYPERIOD command may also be employed to build an automatic retry period into the LOCK command itself. As soon as an explicit lock is no longer needed, the UNLOCK command should be used to release it. For example, to undo the locks on the Orders and Stock tables in our earlier example, execute: UNLOCK "Orders" FL, "Stock" WL or use: UNLOCK ALL which releases all explicit table locks which have been placed. ARESET command is also provided, which releases all explicit table locks. Since locks are not automatically released when script play ends (but are released when the user exits from the system), one should execute a RESET command near the end of the script in order to clear the workspace, undo all locks, and clear any passwords that have been presented during the course of running the application. Deadlock Situations exist in which contention for resources in a multi-user environment can lead to "deadlock"--when two elements in a process are each waiting for the other to respond. For instance, suppose two users, Ken and Dick, both need to lock Orders and Stock. Say Ken has successfully locked Orders and is attempting to lock Stock; meanwhile, Dick has locked Stock and is attempting to lock Orders. Each of the two users is waiting on a resource held by the other, producing a deadlock. The LOCK command, because it gives the user the ability to lock more than one table at once, has built-in deadlock prevention. If Ken executes: LOCK "Orders" FL, "Stock" FL at exactly the same time that Dick executes LOCK "Stock" FL, "Orders" FL one of the two will secure a lock on both resources, while the other will fail to lock either. A key to avoiding deadlock situations is to attempt to lock all the resources the user needs for a particular operation with a single LOCK statement. If the user structures the application by using small modules and by locking all the tables the user needs at once, deadlocks can be avoided. Multiple locks on one table The user can explicitly place two or more different types of locks on the same table simultaneously. For example, LOCK "Orders" WL, "Orders" PWL attempts to place both a write lock and a prevent write lock on the Orders table. The write lock keeps other users from modifying the table; the prevent write lock keeps other users from blocking the user's modifications. When applying multiple locks to a single table, each explicit lock should be paired with an explicit unlock of the same type. An object is not unlocked until the user removes as many locks as that user placed. Note that placing two or more of the same type of lock on a single table does not alter the effect of the lock. However, the ability to apply multiple locks of the same type can sometimes simplify flow of control in programs. For example, this ability permits a script or procedure to perform a LOCK followed by an UNLOCK without undoing a lock already secured by a calling script or procedure. Determining the status of locks A LOCKSTATUS() function is providing for determining whether (and how many times) the user has explicitly placed a certain type of lock on a table. For example, the function call LOCKSTATUS ("Orders", "WL") returns 0 if the user has not placed any write locks on Orders, or 2 if the user has placed two write locks. The keyword ANY as the second argument lets the user determine how many locks of whatever type the user has placed on the table. LOCKSTATUS() reports only the user's own locks--not those of other users. A menu command (e.g., Tools.linevert split.Info.linevert split.Lock) is provided to get a list of all the locks other users have placed on a table. However, the usefulness of this information may be of limited value in an application: another user or process can always place or release a lock an instant after the user inquiry is made. Record locking Coedit mode is unique in that it lets the user lock individual records of a table. Locking a record prevents other users from modifying or deleting it for the duration of the lock; however, they can still view it. When another user moves to a record that the user has locked, it has the same value as it did at the time the lock was placed. When the user subsequently unlocks the record, the user's changes are posted to the table and can then be seen by other users. From the standpoint of an application, record locking serves two purposes. First, it provides the user with the exclusive ability to make changes to a given record. Second, it forces a "refresh," so that the user is guaranteed to be working with the most up-to-date field values. While a REFRESH command (and the keyboard equivalent Alt-R) may be employed, it is not particularly useful in an application: another user might make a change an instant after the command is executed. Thus, even if the very next statement after the REFRESH interrogates the value of a field, the user could still get an obsolete value. Therefore, if the user is in a situation in which other users might be changing or deleting a record the user wants to examine, he or she should lock it before examining it even if he or she does not intend to change it. In this manner, the user can guarantee that he or she is working with the record's current value. There are also situations where the user's script is periodically examining a record, waiting for a value to change. In this instance, the user should preferably not lock out other users of the record (since they be unable change it). Here, a REFRESH right before the test is preferred. Locking and unlocking records Like table locks, record locks can be placed either automatically, by performing an action that modifies a record, or explicitly, by using the LOCKRECORD command. Similarly, record locks can be released either automatically by moving the cursor to a different record or explicitly by using UNLOCKRECORD. The user should usually rely on automatic record locking and unlocking within a programmed application in those instances where the user is sure the lock cannot fail. In all other circumstances, the user should lock and unlock records explicitly, using the LOCKRECORD and UNLOCKRECORD commands. This is because, unlike the system's automatic record-locking mechanism, the LOCKRECORD command does not produce a script error if it cannot obtain a record lock because of a resource conflict. Instead, it sets Retval to False and sets an error code, which the user can then interrogate with the ERRORCODE() function or the ERRORINFO command. In some cases, there are no automatic locks applied to satisfy the needs of an operation. For example, suppose the user's application lets users view, add, and delete records, but never change existing records. One might think that the following code works:
______________________________________
MOVETO ›Name!
LOCATE "John Doe"
IF (Retval)
THEN DEL
ENDIF
______________________________________
While this may always work, it is not guaranteed to. If another user deletes the record the instant after it is found by LOCATE but before it is deleted by DEL, the user will get the message "Another user deleted record". A more thorough approach would be:
______________________________________
MOVETO ›Name!
LOCATE "John Doe"
IF (Retval)
THEN LOCKRECORD
IF (Retval)
THEN DEL
ELSE MESSAGE "Cannot lock record."
ENDIF
ENDIF
______________________________________
The user can also lock and unlock records explicitly using the LOCKKEY command, which is the PAL.TM. equivalent of pressing Lock Toggle Alt-L. However, the LOCKRECORD and UNLOCKRECORD commands are both more versatile and less likely to result in coding errors and are, thus, preferred instead. Using the LOCKRECORD and UNLOCKRECORD commands In order to completely understand how LOCKRECORD and UNLOCKRECORD work, it helps to remember that there are two basic ways in which the user can coedit a record: The user can make changes to a record that already exists, or the user can enter a brand new record that has not yet been posted to the table. To examine or modify an existing record under script control in a multi-user environment, the record is made current and then LOCKRECORD is executed. The user can then interrogate Retval to determine if the attempt at locking was successful. If the lock succeeded, Retval will be set to True; otherwise, it will be False. (The user can also use the RECORDSTATUS() function to test if a record is locked.) When a lock fails, the user can use the ERRORCODE() function to determine why. The most likely possibility is that some other user has already locked the record. Other possibilities are that another user has placed a write lock on the table or that the record the user tried to lock was deleted. The user can supplement the information returned by ERRORCODE() by using the ERRORMESSAGE() function, which returns an appropriate error message, such as "Record is locked by John". The user can also use the ERRORUSER() function to determine who locked the record. If the lock attempt is successful, on the other hand, the application can proceed to examine or change the record. When processing the record has completed, one should use POSTRECORD to post any changes and to specify whether or not the record should remain locked. Like LOCKRECORD, UNLOCKRECORD sets Retval to True or False depending on whether it succeeded. Unless key fields of the locked record were modified to match the key of some other existing record (in which case, a key conflict would arise), the UNLOCKRECORD command will succeed. Posting records When the user opens up a new blank record (e.g., by inserting a new record into a table) and begins entering data into it, the new record does not actually exist in the table until the user posts it. A record that has not yet been posted to a table cannot be locked. More particularly, if a record has not yet been posted, there is no need to lock it because other users cannot access it. When the user is ready to post the record to the table, he or she can post it with POSTRECORD, which includes the following syntax: POSTRECORD›NOPOST.linevert split.FORCEPOST.linevert split.KEYVIOL!›LEAVELOCKED ! If there is no key violation, the record is always posted. If there is a key violation, the first parameter specifies what action to take: NOPOST does not post a record if a key violation occurs. If the first parameter is omitted, NOPOST is assumed. FORCEPOST always posts a record, even when a key violation occurs, unless the record is on a master table and a detail depends upon it. If the record has a dependent detail, a script error will occur. KEYVIOL leaves the record in a key violation state (described in further detail below). If the LEAVELOCKED parameter is specified, the record remains locked and current after the post. If LEAVELOCKED is not specified, the record is unlocked and the system of the present invention may not keep it current (that is, the record may "fly away" to a new position in the index sequence). LEAVELOCKED is ignored if the post fails for any reason. If a record is posted with POSTRECORD, Retval is set to True; otherwise, Retval is set to False. POSTRECORD provides a high degree of control when coediting or posting records. A program can enter enough of a record to uniquely identify it on a network, one should post it to check for key violations, and then finish filling out the data. The user can also use LOCKRECORD and UNLOCKRECORD to post a record. In this context, the main difference between these two commands is that LOCKRECORD both posts and locks the new record, whereas UNLOCKRECORD just posts. In addition, in the case of a keyed table, LOCKRECORD makes the new locked record the current record in the table; this means that the cursor follows the record as it is posted in its key sequence. UNLOCKRECORD, on the other hand, only causes the new record to be posted; in the case of a keyed table, the record can "fly away" because the cursor moves to its rightful place in the key sequence of the table. In either case, one can use Retval and ERRORCODE() to determine whether the operation succeeded. A failure may occur in only two possible situations: either the table was write locked by another user or a key conflict occurred.
|
Same subclass Same class Consider this |
||||||||||
