File Locking API

API Reference


File locking is a form of IPC used to synchronize multiple processes' access to common files.

This API provides a co-operative file locking mechanism that can be used by multiple processes and/or threads to sychronize reads and writes to common files.

This API only supports regular files. Attempts to use this API on sockets, devices, etc. results in undefined behaviour.

Co-operative File Locking

Co-operative file locks (also known as advisory file locks) means that the processes and threads must co-operate to synchronize their access to the file. If a process or thread simply ignores the lock and accesses the file then access synchronization errors may occur.

Locking Files

There are two types of locks that can be applied: read lock and write lock. A file can have multiple simultaneous read locks, but can only have one write lock. Also, a file can only have one type of lock on it at one time. A file may be locked for reading if the file is unlocked or if there are read locks on the file, but to lock a file for writing the file must be unlocked.

Use le_flock_Open() to lock a file and open it for access. When attempting to lock a file that already has an incompatible lock on it, le_flock_Open() will block until it can obtain the lock. Call le_flock_Close() to close the file and remove the lock on the file.

This code sample shows four processes attempting to access the same file. Assume that all the calls to le_flock_Open() in the example occur in chronological order as they appear:

// Code in Process 1.
 
// Lock the file for reading.
int fd = le_flock_Open("foo", LE_FLOCK_READ); // This call will not block.
 
// Read from the file.
...
 
// Close the file and release the lock.
-------------------------------------------------------------------------------------------------
 
// Code in Process 2.
 
// Lock the file for reading.
int fd = le_flock_Open("foo", LE_FLOCK_READ); // This call will not block.
 
// Read from the file.
...
 
// Close the file and release the lock.
-------------------------------------------------------------------------------------------------
 
// Code in Process 3.
 
// Lock the file for writing.
int fd = le_flock_Open("foo", LE_FLOCK_WRITE); // This call will block until both Process 1
// and Process 2 removes their locks.
 
// Write to the file.
...
 
// Close the file and release the lock.

This sample shows that Process 2 obtains the read lock even though Process 1 already has a read lock on the file. Process 3 is blocked because it's attempting a write lock on the file. Process 3 is blocked until both Process 1 and 2 remove their locks.

When multiple processes are blocked waiting to obtain a lock on the file, it's unspecified which process will obtain the lock when the file becomes available.

The le_flock_Create() function can be used to create, lock and open a file in one function call.

Streams

The functions le_flock_OpenStream() and le_flock_CreateStream() can be used to obtain a file stream to a locked file. le_flock_CloseStream() is used to close the stream and remove the lock. These functions are analogous to le_flock_Open(), le_flock_Create() and le_flock_Close() except that they return file streams rather than file descriptors.

Non-blocking

Functions le_flock_Open(), le_flock_Create(), le_flock_OpenStream() and le_flock_CreateStream() always block if there is an incompatible lock on the file. Functions le_flock_TryOpen(), le_flock_TryCreate(), le_flock_TryOpenStream() and le_flock_TryCreateStream() are their non-blocking counterparts.

Multiple Threads

All functions in this API are thread-safe; processes and threads can use this API to synchronize their access to files.

Replicating File Descriptors

File locks are contained in the file descriptors that are returned by le_flock_Open() and le_flock_Create() and in the underlying file descriptors of the file streams returned by le_flock_OpenStream() and le_flock_CreateStream().

File descriptors are closed the locks are automatically removed. Functions le_flock_Close() and le_flock_CloseStream() are provided as a convenience. When a process dies, all of its file descriptors are closed and any file locks they may contain are removed.

If a file descriptor is replicated either through dup() or fork(), the file lock will also be replicated in the new file descriptor:

int oldfd = le_flock_Open("foo", LE_FLOCK_READ); // Place a read lock on the file "foo".
int newfd = dup(oldfd);
 
le_flock_Close(oldfd); // Closes the fd and removes the lock.

There must still be a read lock on the file "foo" because newfd has not been closed.

This behaviour can be used to pass file locks from a parent to a child through a fork() call. The parent can obtain the file lock, fork() and close its file descriptor. Now the child has exclusive possession of the file lock.

Limitations

Here are some limitations to the file locking mechanisms in this API:

The file locks in this API are advisory only, meaning that a process may simply ignore the lock and access the file anyways.

This API does not detect deadlocks and a process may deadlock itself. For example:

int fd1 = le_flock_Open("foo", LE_FLOCK_READ); // Obtains a read lock on the file.
int fd2 = le_flock_Open("foo", LE_FLOCK_WRITE); // This call will block forever.

This API only permits whole files to be locked, not portions of a file.

Many NFS implementations don't recognize locks used by this API.