s6: the s6lock library interface
Keywords: s6 timed lock s6lock libs6 library interface

libs6
s6
Software
skarnet.org

# 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.

### 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 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, &a->list)` points to an array of `genalloc_len(uint16, &a->list)` 16-bit unsigned integers. Those integers are ids waiting to be passed to `s6lock_check`.

    int s6lock_check (s6lock_t *a, uint16 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 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 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.