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.


Starting and ending a session

s6lock_t a = S6LOCK_ZERO ;
tain_t deadline ;

tain_now_g() ;
tain_addsec_g(&deadline, 2)

char const *path = S6LOCK_IPCPATH ;
s6lock_start_g(&a, path, &deadline) ;
// char const *lockdir = "/tmp/lock" ;
// s6lock_startf_g(&a, lockdir, &deadline) ;

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.

Acquiring and releasing locks

uint16_t id ;
char const *file = "lockfile" ;
tain_t limit ;
tain_t deadline ;

int r = s6lock_acquire_sh_g (&a, &id, file, &limit, &deadline) ;
/* int r = s6lock_acquire_ex_g (&a, &id, file, &limit, &deadline) ; */
r = s6lock_release_g(&a, id, &deadline) ;

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().

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.