Omnis Technical Note TNDF0003
Locks in Omnis Datafiles
for Omnis Classic 7.3.7.1 and Studio
by Omnis Technical Support
Due to its publication date, this technote contains information which may no longer be accurate or applicable on one or more of the platforms it refers to. Please refer to the index page which may contain updated information.
About Locks and Semaphores
When two clients that are connected to the same record, attempt to edit
the same record, Omnis will display a lock on the screen. When editing
a record a so-called 'semaphore' is set for that record.
If a semaphore is set for a record, subsequent users will see the set
semaphore on their screen in the form of an icon displaying a lock until
the first user releases the semaphore.
Omnis has two methods of reacting to a semaphore:
The 'Wait for Semaphores' command
The default setting is 'Wait for semaphores'. In this case a user who
attempts to edit a locked record will have a lock icon displayed on the
screen until the user that originally locked the record removes the lock
by issuing an 'Update files' command or a 'Cancel prepare for update'
command. The semaphore is then reset and the second user can access the
record.
The commands that set semaphores are Prepare for edit, Prepare for insert, Update files and Delete, and also, if prepare for update mode is on and the file acted upon is Read/Write, Single file find, Load connected records, Set read/write files, all types of Find, Next, and Previous. Update files commands lock the whole data file while indexes are re-sorted.
Omnis has two methods of setting the semaphores in a datafile. There is the so-called file locking and the unique record locking. Unique record locking sets the semaphore of the current record only. File locking, on the other hand, not only sets the semaphore of the current record, but each 240th record in that file. The default setting of a datafile slot is file locking. File locking was introduced in the old days when computers had little memory to work with, in order to speed up the setting of locks. With normal use one never realised that the slot one was working on had file-locking set. Later as computers became faster there was no longer a visible difference in the speed of file locking and record locking. The default setting has stayed on though. It is advisable to explicitly set the file formats/slots to unique record locking to avoid possible locks occurring due to file locking. Possible code to perform this action is:
Do $cdata.$slots.$sendall($ref.$uniquelocks.$assign(kTrue))
In some cases a multi-user Omnis application will display locks on the screen. This can be problematic as an end user might not know what is happening in the application and think the lock is an Omnis fault. One can avoid visible locks displayed on the screen altogether by using the 'Do not wait for semaphores' command. Have a look at the comments below for more information. Normally though, as long as you set your file formats to read-only at startup and only set them to read-write when you actually do some writing to the datafile or use unique record locking in your datafile, you ought not see locks any more. It is generally not necessary to use the 'Do not wait for semaphores' command.
There are a couple of points to bear in mind when writing to a datafile. When a lock occurs on the screen of a user, for example when the 'Prepare for edit' command is executed and another user is accessing that record, the user can cancel the action by pressing Ctrl-Pause. You must test for flag true or flag false after each command that might cause a lock and react appropriately in the code. If the user quits a process where a record is locked and the code does not take this into account, the data in the datafile could become inconsistent.
Another cause of locks is the network connection. This is easy to reproduce: Log on to a datafile from a client that has been placed on a server. Break the connection to the client. Attempt to edit a record in the now disconnected datafile. The Omnis client will display a lock. If, for example, a network is overloaded or the connection is unstable, locks can be displayed. On the Mac an error message will be displayed on the screen that the connection has been lost, on Windows machines however, this is not the case. Screen savers also have to be mentioned when one speaks of network connections; many screen savers disconnect from the server when the screen saver is operational. If the screen saver detects a user action the screen saver switches itself off. This disconnection from the server and reconnection often does not work correctly and sometimes this can be the cause of a lock being displayed in an application. Turning off the screen saver or reconfiguring it not to disconnect the network connection can resolve this problem.
It has been reported that opportunistic locking on an NT server can be
the cause of locks being displayed in an Omnis application, although it
is not clear how opportunistic locking on NT would cause locks in Omnis.
The tech note TNDM0002 describes how to switch
off opportunistic locking.
Lastly, ensure the datafile is not corrupt. I have never experienced this
before and this is only speculation, but it would be an idea to export
and import the data to a new datafile once, if this is feasible, just
to make sure this is not the cause.
The 'Do not wait for semaphores' command
Normally when two users try to access the same record in a datafile,
a lock or the hourglass will be displayed on one of the machines. The
lock will go as soon as the other user has removed the lock with 'Update
files' or 'Cancel prepare for update'. It is possible to code in Omnis
so that the lock is not displayed. One could display a working message
while the record is locked and allow the user to quit the EDIT manually,
or one could quit after a set period of time has elapsed. Standard Omnis
behaviour is to display the lock. This is the case when one uses the command:
"Wait for semaphores" (wait until lock is removed). Another option is
to use the command: "Do not wait for semaphores". This command will return
a Flag false if an operation that requires a lock fails. You have to create
error handlers in each of the cases where a locking attempt fails. The
text below describes what steps are necessary to ensure data is consistently
written to the datafile. First steps
In the Startup_Task you should set all file formats/classes to read-only.
See the following code:
; Studio (or Omnis
7)
Set current list #L1
Do $files.$makelist($ref.$name) Returns #L1
; or for Omnis 7
; Calculate #L1 as $files.$makelist($ref.$name)
Redefine list {#S1}
For each line in list from 1 to #LN step 1
Load from list
If mid($ref.$name,1,1)<>'#'
Set read-only files{[#S1]}
End if
End For
; Studio only
Do $files.$sendall($ref.$filemode.$assign(kReadonly),
mid($ref.$name,1,1)<>'#'
; The second parameter in the $sendall method tells
; the command to ignore all system file classes
The reason for this is to ensure that writing to a datafile is only done when explicitly set. Before the 'Update files' command is executed the appropriate File is set to Read-Write, after writing it is again set to Read-Only.
Another useful setting to set is $uniquelocks. The default setting
in Omnis is that a slot uses a block level record locking in a slot, which
basically means that when one generates a lock in a slot, not just that
record is locked but other records in that slot as well. This was done
in the early days of Omnis computing to speed up the locking of records
in a slot. Nowadays, with faster machines, there is no noticeable difference
between unique record locking (the other option) or so-called file-locking.
With unique record locking only the record in the file that one is actually
accessing is locked. The following code will set all the slots in a data
file to unique record locking:
; Studio (or
Omnis 7)
Set current list #L1
Do $cdata.$makelist($ref.$name) Returns #L1
; or for Omnis 7
; Calculate #L1 as $cdata.$makelist($ref.$name)
Redefine list {#S1}
For each line in list from 1 to #LN step 1
Load from list
If mid($ref.$name,1,1)<>'#'
Calculate #F as $cdata.$slots.[#S1].$uniquelocks.$assign(kTrue)
End if
End For
; Studio only
Do $cdata.$slots.$sendall($ref.$uniquelocks.$assign(kTrue),
mid($ref.$name,1,1)<>'#'
; The second parameter in the $sendall method tells
; the command to ignore all system file classes
Creating a user-defined Edit button
The following code is for a user-defined Edit button. Each step documents
why the code is where it is and what purpose it has. The code is almost
identical for Omnis Studio as for Omnis 7, even though the code below
is for Omnis Studio. In Omnis 7 you could place the code behind a button
and trap the #CLICK event.
##### Method '$event' #####
On evClick
; or for Omnis 7
; If #CLICK
; Remove any locks
that might be present in the datafile.
Cancel prepare for update
; Do not use standard locking behavior.
Do not wait for semaphores
Prepare for edit
; This will try to lock the record in the CRB
While flag false
; if the record is locked, this command will return a flag false.
; Normally one would see a lock here.
; Instead the programmer has to handle the resulting errors.
; This method could be cancelled after a specified period
; of time has elapsed or a specified number of times that
; this loop is executed. In the case below a working message
; with a cancel button is used.
Working message (Cancel button);{This record is in use by another user//Press 'Cancel' to break}
If cancelled
Close working message
Wait for semaphores
;; reset standard locking behaviour
Quit method
End If
Prepare for edit
;; try to lock the current record once more
End While ;; keep trying to update until flag is true
; flag is now true and the record is locked by this user
Enter data
If flag true ;; user has pressed Enter or Return
Set read/write files {[sys(82)]} ;; sys(82) returns the main file
Update files
If flag false ;; See the comments below.
Repeat ;; The 'Update files' command can fail in certain cases
Working message {File locked by another user ...}
Update files
Until flag true
End If
Set read-only files {[sys(82)]}
Else ;; user has pressed 'Cancel' button or 'Esc' key
Cancel prepare for update ;; clears the locks in the datafile
; If the user has edited values on the screen he no longer
; sees the values in the current record buffer. To
; view these values the screen has to be redrawn.
Do $cinst.$redraw() ;; or the line below for Omnis 7
; Redraw windows
End If
; reset to standard behavior
Wait for semaphores
Quit method
Cases can occur when one user requires exclusive access to a slot.
This exclusive access does not take long but another user who has a valid
lock in this datafile, as in the case above, can be locked out of the
slot momentarily. It is therefore imperative to check to see if the update
has succeeded or not by checking the flag. If a Flag false is returned
one has to repeat the update, which explains the 'Repeat ... Until flag
true' loop.