0
\$\begingroup\$

I have two python scripts (or one bash script + one python script) which are working independently from another, one script puts data into a folder, the other one reads from it.

Since I don't want to accidentally read from the folder at the same time it's updated, I created this simple folder lock using a dummy file.

In the bash script a similar thing is done, by checking whether that lock.file exists, and if not create it, put the current PID into it, and after it's done delete it.

import os
import sys
import time
import logging

LOGGER = logging.getLogger()

class SimpleFolderLock():
    def __init__(self, folder_path, time_out=5):
        self._folder_to_lock = folder_path
        self._lock_file_path = os.path.join(self._folder_to_lock, "folder.lock")
        self._lock_time_out = time_out
        
    def __enter__(self):
        start_time = time.time()
        while os.path.exists(self._lock_file_path):
            LOGGER.debug(f"Waiting for {self._lock_file_path} to clear")
            time.sleep(5)
            if (time.time() - start_time) > self._lock_time_out:
                LOGGER.error(
                    f"Time-out for waiting for {self._lock_file_path} to clear")
                raise TimeoutError(f"Can't lock {self._folder_to_lock} after {self._lock_time_out} seconds")
    
        LOGGER.debug(f"Locking with {self._lock_file_path}")
        with open(self._lock_file_path, "w") as fn:
            fn.write(str(os.getpid()))
            
        return self._lock_file_path
    
    def __exit__(self, type, value, traceback):
        try:
            LOGGER.debug(f"Clearing {self._lock_file_path}")
            os.remove(self._lock_file_path)
        except FileNotFoundError:
            LOGGER.error(f"File {self._lock_file_path} already got deleted")

And for a simple test:

# Just some dummy logging configuration
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
test_folder = "/tmp/test_dir/"
os.makedirs(test_folder, exist_ok=True)
with SimpleFolderLock(test_folder) as lock:
    lock_path = lock
    LOGGER.info(f"Locked with {lock_path}")
    assert os.path.exists(lock_path)
    fiddle_around_in_folder(test_folder)

    
assert not os.path.exists(lock_path)
\$\endgroup\$
1
  • \$\begingroup\$ Welcome to Code Review. I have rolled back your last edit. Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$
    – Heslacher
    Commented Mar 4, 2021 at 9:22

1 Answer 1

0
\$\begingroup\$

There is a race condition here:

while os.path.exists(self._lock_file_path):
...
with open(self._lock_file_path, "w") as fn:

The file could have been created by the other process in between. You need to check file existence and create the file in one operation.

Since python 3.3 there is a special open mode x for this use case:

open for exclusive creation, failing if the file already exists

For example:

while (time.time() - start_time) < self._lock_time_out:
    try:
        with open(self._lock_file_path, "x") as fn:
            fn.write(str(os.getpid()))
    except FileExistsError:
        time.sleep(5)
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.