Login | Register

Documentation

Documentation -> Development Manual 3.1 -> Locking API

This page has been visited 1093 times.


Pages for other versions: devel 3.5 3.4 Older versions: 3.3 3.2 3.1


Locking API

OpenSIPS has it's own locking API, and it is recommended to use it instead of the system exposed locks, since they offer greater flexibility - depending on the usage case and the menuconfig provided compilation flags, the OpenSIPS generic locks can be converted either to busy locks, futexes, SysV locks, etc.
The Locking API offers two distinct functionalities, one for using single lock elements, and another for operating on entire sets of locks.

1.  Single Lock API

The API can be used by including “locking.h” . The OpenSIPS generic lock is defined by the gen_lock_t structure.
Allocating a new lock is done by calling lock_alloc :

/*
Returns :
      A shared memory allocated lock, or NULL in case of an error.
*/

gen_lock_t *lock_alloc(void);


Since the locks usually have to be reachable by all processes fighting on some resource, the locks are allocated by default in shared memory. Also, note that it is not necessary to always allocate the lock separately - if the lock is embedded within a structure which is allocated in SHM, the effect is the same.
Before any operation on the lock, it must be initialized :

/*
Parameters :
      lock - the lock instance to be initialized
Returns :
      The initialized lock in case of success, or NULL in case of error.
*/

gen_lock_t* lock_init(gen_lock_t* lock);


In order to acquire a lock, one must use the lock_get function :

/*
Parameters :
      lock - the lock to be acquired
*/

void lock_get(gen_lock_t *lock);


The function will block if the lock is acquired by another process, and will only return once the lock has been acquired by the current process.
For releasing a lock, lock_release should be used :

/*
Parameters :
      lock - the lock to be released.
*/

void lock_release(gen_lock_t *lock);


Once a lock is no longer needed, one must first destroy the lock, and then the lock can be safely deallocated.

/*
Parameters :
      lock - the lock to be destroyed
*/

void lock_destroy(gen_lock_t *lock);
/*
Parameters :
      lock - the lock to be deallocated
*/

void lock_dealloc(gen_lock_t *lock);


Here is a code snippet showing the typical code used when dealing with single lock instances :


gen_lock_t *my_lock;

int init_function(void)
{
        /* … */

        my_lock = lock_alloc();
        if (my_lock == NULL) {
                LM_ERR(“Failed to allocate lock \n”);
                return -1;
        }

        if (lock_init(my_lock) == NULL) {
                LM_ERR(“Failed to init lock \n”);
                return -1;
        }

        /* … */
        return 0;
}


int do_work(void)
{

        /* … */
        lock_get(my_lock)


        /* critical region protected by our lock
        generally recommended to keep critical regions short
        I/O operations to be avoided in such critical regions */


        lock_release(my_lock)
        /* … */
}

void destroy_function(void) {

        /* … */

        lock_destroy(my_lock);
        lock_dealloc(my_lock);

        /* … */
}

2.  Lock Set API

Operating on an entire array of locks can become very useful when dealing with structures like hashes, where you would need a lock per each hash entry.
The API can be used by including locking.h . The OpenSIPS generic array of locks is defined by the gen_lock_set_t structure, and working with it is very similar in concept to operating a single lock entry.
Allocating a new lock set is done by calling lock_set_alloc :

/*
Returns :
      A shared memory allocated lock set, or NULL in case of an error.
*/

gen_lock_set_t *lock_set_alloc(void);


Since the locks usually have to be reachable by all processes fighting on some resource, the locks are allocated by default in shared memory. Also, note that it is not necessary to always allocate the lock set separately - if the lock set is embedded within a structure which is allocated in SHM, the effect is the same.
Before any operation on the lock set, it must be initialized :

/*
Parameters :
      lock - the lock set instance to be initialized
Returns :
      The initialized lock in case of success, or NULL in case of error.
*/

gen_lock_set_t* lock_set_init(gen_lock_set_t* lock);


In order to acquire a lock in a lock set, one must use the lock_set_get function :

/*
Parameters :
      lock - the lock to be acquired
      entry - the entry in the lock set that needs to be acquired
*/

void lock_set_get(gen_lock_set_t *lock,int entry);


The function will block if the lock is acquired by another process, and will only return once the lock has been acquired by the current process.
For releasing a lock, lock_set_release should be used :

/*
Parameters :
      lock - the lock to be released.
      entry - the entry in the lock set that needs to be released
*/

void lock_set_release(gen_lock_set_t *lock,int entry);


Once a lock set is no longer needed, one must first destroy the lock set, and then the lock set can be safely deallocated.

/*
Parameters :
      lock - the lock set to be destroyed
*/

void lock_set_destroy(gen_lock_set_t *lock);
/*
Parameters :
      lock - the lock set to be deallocated
*/

void lock_set_dealloc(gen_lock_set_t *lock);


Here is a code snippet showing the typical code used when dealing with single lock instances :

gen_lock_set_t *my_lock;

int init_function(void)
{
        /* … */

        /* allocate lock set with 32 entries */
        my_lock = lock_set_alloc(32);
        if (my_lock == NULL) {
                LM_ERR(“Failed to allocate lock set \n”);
                return -1;
        }

        if (lock_set_init(my_lock) == NULL) {
                LM_ERR(“Failed to init lock set \n”);
                return -1;
        }

        /* … */
        return 0;
}

int do_work(void)
{

        /* … */

        /* acquire entry 5 in the lock set */
        lock_set_get(my_lock,5)

        /* also acquire entry 21 in the lock set */
        lock_set_get(my_lock,21);

        /* critical region protected by our lock
        generally recommended to keep critical regions short
        I/O operations to be avoided in such critical regions */


        lock_set_release(my_lock,21);
        lock_set_release(my_lock,5);
        /* … */
}

void destroy_function(void)
{
        /* … */

        lock_set_destroy(my_lock);
        lock_set_dealloc(my_lock);

        /* … */
}

3.  Readers-Writers Locking API

A readers-writer lock is like a mutex, in that it controls access to a shared resource, allowing concurrent access to multiple threads for reading but restricting access to a single thread for writes (or other changes) to the resource.
This can prove very useful when having an use case where all the OpenSIPS processes need read-only access to a resource, but you need to have an MI command to reload that resource ( eg. from a database ). In such scenarios, using a readers-writers lock can improve performance by a considerable margin.
The API can be used by including “rw_locking.h” . The OpenSIPS generic lock is defined by the rw_lock_t structure.

Allocating a new readers-writers lock into shared memory and initializing it is done by calling lock_init_rw :

/*
Returns :
      A shared memory allocated rw lock, or NULL in case of an error.
*/

inline static rw_lock_t * lock_init_rw(void);


In order to acquire the lock for reading purpose, one should use lock_start_read :

/*
Parameters :
      lock - the lock to be acquired
*/

void lock_start_read(rw_lock_t * lock);


In case there is currently a write access ongoing, the lock will block until the write is done. Otherwise, the lock will be acquired immediately.
After the read is finished, you must call lock_stop_read :

/*
Parameters :
      lock - the lock to be released
*/

void lock_stop_read(rw_lock_t * lock);


For requesting a write access, you can use lock_start_write :

/*
Parameters :
      lock - the lock to be acquired
*/

void lock_start_write(rw_lock_t * lock);


In case there are other ongoing writes, the op will block until the other writes complete. Otherwise, the lock will block until all the existing readers finish reading.
After the write operation is finished, call lock_stop_write :

/*
Parameters :
      lock - the lock to be release
*/

void lock_stop_write(rw_lock_t * lock);


Upon calling the above function, readers will again be allowed in the critical section.
For destroying and deallocating a rw lock, use lock_destroy_rw :

/*
Parameters :
      lock - the lock to be destroyed
*/

void lock_destroy_rw(rw_lock_t * lock);

Page last modified on December 11, 2019, at 06:55 PM