Skip to content
Advertisement

Same program/process acquiring lock every time when using LOCK_NB with LOCK_EX flag

I have a requirement, where two separate processes/programs running in parallel (One written in Python and one written in C++) need to get exclusive access, modifying a hardware related value.

I’m trying to attain synchronization between them using flock.

The code for the same is as below,

Python Code

#!/usr/bin/python

import fcntl
import time
import datetime
import os

SLICE_SLEEP = 5
LOCK_HOLD_TIME = 20
TOTAL_WAIT_TIME = 300

class LockUtil(object):
   FILE_PATH = "/tmp/sync.lock"
   fd = -1

   @staticmethod
   def acquireLock(totalWaitTime=TOTAL_WAIT_TIME):
      try:
         LockUtil.fd = os.open(LockUtil.FILE_PATH,os.O_WRONLY|os.O_CREAT)
         print('Trying to acquire lock')
         retryTimes = (totalWaitTime/SLICE_SLEEP)
         currentCounter = 0
         while currentCounter < retryTimes:
            try:
                fcntl.flock(LockUtil.fd,fcntl.LOCK_EX|fcntl.LOCK_NB)
                print('Lock acquired successfully')
                return
            except IOError:
                print('Failed to acquire the lock, sleeping for {} secs'.format(SLICE_SLEEP))
                time.sleep(SLICE_SLEEP)
                currentCounter += 1
         print('Tried {} times, now returning'.format(retryTimes))
      except IOError:
         print('Can not access file at path: {}'.format(FILE_PATH))

   @staticmethod
   def releaseLock():
      fcntl.flock(LockUtil.fd,fcntl.LOCK_UN)
      print('Lock released successfully')

class LockHelper(object):
   def __init__(self):
      LockUtil.acquireLock()
   def __del__(self):
      LockUtil.releaseLock()

def createObjAndSleep():
   lock = LockHelper()
   time.sleep(LOCK_HOLD_TIME)

def main():
   while True:
      createObjAndSleep()

if __name__ == '__main__':
   main()

C++ Code

#include <iostream>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/file.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <ctime>
#include <memory>

int SLICE_SLEEP = 6;
int LOCK_HOLD_TIME = 20;
int TOTAL_WAIT_TIME = 300;
int SUCCESS = 0;

class LockUtil {
   public:
      static std::string path;
      static int fd;
      static int acquireLock(int totalWaitTime=TOTAL_WAIT_TIME);
      static int releaseLock();
};

std::string LockUtil::path = "/tmp/sync.lock";
int LockUtil::fd = -1;

int LockUtil::acquireLock(int totalWaitTime) {
   fd = open(path.c_str(), O_WRONLY|O_CREAT, 0666);
   if(fd != -1)
   {
      auto retryTimes = (totalWaitTime/SLICE_SLEEP);
      auto currentCounter = 0;
      while(currentCounter < retryTimes)
      {
        std::cout << "Trying to acquire lock" << std::endl;
        auto lockStatus = flock(fd,LOCK_EX|LOCK_NB);
        if(lockStatus == SUCCESS)
        {
            std::cout << "Lock acquired successfully" << std::endl;
            return 0;
        } else {
            std::cout << "Failed to acquire the lock, sleeping for " << SLICE_SLEEP << " secs" << std::endl;
            sleep(SLICE_SLEEP);
            currentCounter += 1;
        }
      }
   } else {
      std::cout << "Unable to open the file!" << std::endl;
      std::cout << strerror(errno) << std::endl;
      return -1;
   }
}

int LockUtil::releaseLock() {
   if(fd != -1)
   {
      flock(fd,LOCK_UN);
      std::cout << "Lock released successfully" <<  std::endl;
      return 0;
   } else {
      return -1;
   }
}

class LockHelper {
   public:
      LockHelper() {
         LockUtil::acquireLock();
      }
      ~LockHelper() {
         LockUtil::releaseLock();
      }
};

void createObjAndSleep()
{
   std::unique_ptr<LockHelper> lockObj(new LockHelper());
   sleep(LOCK_HOLD_TIME);
}

int main(void) {
   while (true) {
      createObjAndSleep();
   }
}

But, when I run these two programs in parallel, it is observed that the process which first got the lock on the file, keeps getting it always and the other process starves.

But, if I change the flags in both the programs to just use LOCK_EX and remove LOCK_NB, the lock is shared between the processes in round-robin fashion.

I would like to understand what is the bug in the program when using LOCK_NB flag.

OS

uname -a

Linux 0000000000002203 4.4.43-hypriotos-v7+ #1 SMP PREEMPT Thu Jan 19 20:54:06 UTC 2017 armv7l GNU/Linux

Python Version – 2.7

C++ Version – C++11

Advertisement

Answer

I do not think it is a bug per-se, but likely an unintended consequence. When you use a blocking flock, you process is put into internal Linux kernel queue, which is expected to wake up the process once the lock has been released.

While Linux flock has no guarantee of fair scheduling, it looks like the sequence of events distribute the locking in more or less fair schedule.

On the other hands, with non-blocking lock, your processes keep trying to lock it. As a result, there is no lock queue, instead, there are processes which are constantly competing for the lock in real-time. In order to achieve this lock, a process needs to be on-cpu when the lock becomes available, and it seems like scheduler just doesn’t give a process a chance to be there at this time.

The scheduling policy is extremely complicated, so I would not speculate is to what exactly causes this scheduler behavior.

Last but not least, what is your ultimate goal when it comes to non-blocking locks? Why do you want them?

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement