The s6lock library interface

General information

s6lock is a C interface to timed locks. Unix natively provides
locks, but the locking primitives are synchronous, so either they are
unbounded in execution time or they require polling. s6lock provides
poll-free locks that can timeout during attempted acquisition.

Programming

Check the s6/s6lock.h header
for the prototypes. The functions documented here are
often simplified macros, for instance relying on the STAMP global variable
to hold the current time. Fully reentrant functions with more control
options are usually available.

Given the nature of the s6lock library, it makes sense to use a
s6lockd service concurrently
accessed by several applications using such locks to gate shared
resources.

If you're not using a s6lockd service,
make sure your application is not disturbed by children it doesn't
know it has. Using nonblocking waits, ignoring pids you don't know, and
using a
self-pipe
if your application is built around an event loop, are good programming
practices.

s6lock_start_g starts a session by connecting to a s6lockd service
listening on path. The working directory is set by the administrator
of the service. s6lock_startf_g starts a session with a s6lockd process as a child,
using lockdir as its working directory.
a is a s6lock_t structure that must be declared in the stack and
initialized to S6LOCK_ZERO.
If the session initialization fails, the function returns 0 and errno is set;
else the function returns 1.

If the absolute time deadline is reached and the function
has not returned yet, it immediately returns 0 with errno set to ETIMEDOUT.
Only local interprocess communications are involved; unless your system is
heavily overloaded, the function should return near-instantly. One or two
seconds of delay between the current time and deadline should be
enough: if the function takes more than that to return, then there is a
problem with the underlying processes.

You can have more than one session open in parallel, by declaring
several distinct s6lock_t structures and calling
s6lock_startf_g (or s6lock_start_g) more than once.
However, one single session can handle
virtually as many concurrent locks as your application needs, so
opening several sessions is only useful if you need to acquire locks
in various distinct lock directories.

s6lock_end(&a) ;

s6lock_end frees all the resources used by the session. The
a structure is then reusable for another session.

s6lock_acquire_sh_g instructs the
s6lockd daemon, related to the open
session represented by the a handle, to try and acquire a
shared lock on the
file file located under that daemon's working directory
(typically /var/lock). file will be interpreted as
relative to the daemon's working directory even if it starts with a
slash; however, slashes in the middle of file are likely to
result in an error.

limit and deadline are two absolute dates.
deadline is a deadline for the execution of the
function: if by deadline the function has not returned,
then it instantly returns 0 and sets errno to ETIMEDOUT. The
function is normally near-instantaneous, so deadline can
be very close in the future and serves only as a protection against
malicious servers. limit is the acquisition deadline: if
by limit the daemon still has not been able to acquire a lock
on file, then it will report a timeout to the client.

The function returns 1 in case of success, or 0 if an error occurs,
with errno set to a suitable value. If it succeeds, then a 16-bit
number is stored into *id; this number serves as an identifier
for this lock.

s6lock_acquire_ex_g works just like s6lock_acquire_sh_g,
except that the daemon tries to acquire an exclusive lock.

s6lock_release_g releases the lock identified by id.
It normally returns 1. It can return 0 with errno set to a suitable
value if it fails. id is not valid after the corresponding
lock has been released. The function normally returns instantly, with
deadline as a safeguard.

Asynchronously waiting for locks

(from now on, the functions are listed with their prototypes instead
of usage examples.)

int s6lock_fd (s6lock_t const *a)

Returns a file descriptor to select on for reading. Do not
read() it though.

int s6lock_update (s6lock_t *a)

Call this function whenever the fd checks readability: it will
update a's internal structures with information from the
s6lockd daemon. It returns -1 if an error
occurs; in case of success, it returns the number of identifiers for
which something happened.

When s6lock_update returns,
genalloc_s(uint16_t, &a->list) points to an array of
genalloc_len(uint16_t, &a->list) 16-bit unsigned
integers. Those integers are ids waiting to be passed to
s6lock_check.

int s6lock_check (s6lock_t *a, uint16_t id, char *what)

Checks whether the lock identified by id has
been acquired. Use after a call to s6lock_update().

If an error occurred, returns -1 and sets errno. The error
number may have been transmitted from
s6lockd.

If the lock has not been acquired yet, returns 0.

If the lock has been acquired, returns 1.

Synchronously waiting for locks

int s6lock_wait_or_g (s6lock_t *a, uint16_t const *idlist, unsigned int n, tain_t const *deadline)
Synchronously waits for one of the locks represented by the array pointed to
by idlist of length n to be acquired. Returns -1 if it fails,
or a nonnegative number on success, which is the index in idlist of the
acquired lock's id. If no result has been obtained by deadline, the
function returns -1 ETIMEDOUT.

int s6lock_wait_and_g (s6lock_t *a, uint16_t const *idlist, unsigned int n, tain_t const *deadline)
Synchronously waits for all of the locks represented by the array pointed to
by idlist of length n to be acquired. Returns -1 if it fails and
0 if it succeeds. If no result has been obtained by deadline, the
function returns -1 ETIMEDOUT.