Lock

Prevent multiple processes from doing the same operation on a host, using good old Linux fnctl locking.

fcntl’s flock is a great choice to prevent deadlocks if/when your process crashes because the system will release any locks of a process when it dies. man fcntl for details.

Anyway, we provide a Lock class that acts as a context manager for both blocking and non-blocking fcntl locks.

It’s especially useful to orchestrate Ansible Action plugins.

Blocking locks

Consider this little program:

import cli2
import os
os.environ['LOG_LEVEL'] = 'DEBUG'
with cli2.Lock('/tmp/mylock') as lock:
    input('say hello')
  • Run it in a first terminal: it will log “Acquired” and show the “say hello” input.

  • Run it in another terminal: it will log “Waiting”

  • Type enter in the first terminal: it will release the lock and exit

  • Then the second terminal will log “Acquired” and display the say hello input

You got this: two programs cannot enter the same blocking lock at the same time.

Non blocking locks

You’re starting a bunch of processes that potentially want to do the same thing at the same time, ie. download a file to cache locally prior to sending it.

Only one process must do the caching download, the first one that gets the lock, all others will sit there and wait.

This is possible with a non-blocking lock that we later convert into a blocking lock.

with cli2.Lock('/tmp/mylock', blocking=False) as lock:
    if lock.acquired:
        # we got the lock, proceed to downloading safely
        do_download()
    else:
        # couldn't acquired the lock because another process got it
        # let's just wait for that other process to finish by converting
        # the non-blocking lock into a blocking one
        lock.block()

# all processes can safely process to uploading
do_upload()
class cli2.lock.Lock(lock_path, blocking=True, prefix=None)[source]

fcntl flock context manager, blocking and non blocking modes

In doubt? set LOG_LEVEL to DEBUG and you will see exactly what’s happening.

lock_path

Path to the file that we’re going to use with flock.

blocking

If True, the locker automatically blocks. Otherwise, you need to check acquired and call :py:method:`block` yourself.

prefix

Arbitrary string that will be added to logs.

block()[source]

Basically converts a non-blocking lock into a blocking one