What is a Semaphore Lock?

A long time ago I fell over the concept of signaling a lock across a network without actually locking a file or record. I forget the programming language, but thought how useful that would be in some of the programs I was developing at the time.

image courtesy of killrbeez

It is sometimes useful be able to indicate when a process is in progress without explicitly locking files or records that it relies on. For example, let’s assume that you wish to import a file, but that you need to ensure that no other user is attempting to import the same file.

Although there are any number of ways to accommodate this need, the one I favour is the use of a table whose sole reason for existence is to allow us to keep track of the process in question. The table I use is called ‘semaphore.dbf’ and has a single 20 character field called key. This field carries one index tag called key, which is based on the upper case value of the field content.

Index on UPPER(key) tag key

I have also written a small function (usually contained within the database stored procedures) that I invoke whenever I wish to apply a semaphore lock for a specified form or process.

While this particular piece of code does in fact utilise a record lock, it has proved its weight in gold in many recent projects.

    *************************************
    * Place a lock on a file or process    *
    * without actually locking them        *
    * Requires the table 'semaphore.dbf'*
    *************************************
    FUNCTION Semaphore
        LPARAMETERS cOpt,cKey
        LOCAL lcLockfile,llReturn,lnArea
        lnArea=SELECT(0)                    && Where are we
        cOpt=IIF(EMPTY(cOpt),"",LOWER(ALLTRIM(cOpt)))
        cKey=IIF(EMPTY(cKey),"",UPPER(ALLTRIM(cKey)))
        lcLockfile="semaphore"                           && Replace this with your table name
        IF NOT USED(lcLockfile)                && If not Open
            USE (lcLockfile) IN 0             && Open and leave open
        ENDIF

        llReturn=.T.
        DO CASE
            CASE "clear"$cOpt OR EMPTY(ckey)        && Try and remove any semaphore locks
                SELECT (lcLockfile)
                SCAN
                    IF RLOCK(lcLockfile)
                        DELETE
                    ENDIF
                ENDSCAN
                UNLOCK IN (lcLockfile)
            CASE "unlock"$cOpt
                IF SEEK(cKey,lcLockfile,"key")
                    DELETE IN (lcLockfile)    && Self maintaining
                ENDIF
                UNLOCK IN (lcLockfile)
            OTHERWISE
                DO CASE
                    CASE NOT SEEK(cKey,lcLockfile,"key")
                        INSERT INTO (lcLockfile) (KEY) VALUES (cKey)
                        IF NOT RLOCK(lcLockfile)
                            llReturn=.F.
                        ENDIF
                    CASE NOT RLOCK(lcLockfile)
                        llReturn=.F.
                ENDCASE
        ENDCASE
        SELECT (lnArea)
        RETURN llReturn

    ENDFUNC
    

The code below serves as an example of how to invoke and clear the lock:

    do case
       case not semaphore('lock','uniquekey')               && apply lock using this key
         =messagebox('process is already in progress',16,'Try again later')
       otherwise
         <do whatever process required>
         =semaphore('unlock','uniquekey')                    && release specified semaphore
         =semaphore('clear')                                 && Release multiple locks if possible
    endcase