diff --git a/python/dune/common/locking.py b/python/dune/common/locking.py index 4371557abf6d5466db5f63246e3c69c389646d71..8f7de4c1fdfba7b64764332af35de5f9d6831166 100644 --- a/python/dune/common/locking.py +++ b/python/dune/common/locking.py @@ -1,7 +1,47 @@ -from portalocker import Lock as _Lock -from portalocker.constants import LOCK_EX, LOCK_SH +## By default use portalocker, if available. +## However, portalocker is not available on all systems +## and therefore a fallback implementation is also provided +try: + from portalocker import Lock as _Lock + from portalocker.constants import LOCK_EX, LOCK_SH + class Lock(_Lock): + def __init__(self, path, flags, *args, **kwargs): + _Lock.__init__(self,path,*args,flags=flags,timeout=None,**kwargs) +except ModuleNotFoundError: + ## Fallback implementation if portalocker was not found + ## This is still needed! Do not remove it. + import os + import fcntl + from fcntl import LOCK_EX, LOCK_SH + # file locking from fcntl + def lock_file(f, cmd=fcntl.LOCK_EX): + fcntl.flock(f, cmd) + return f + def unlock_file(f): + fcntl.flock(f, fcntl.LOCK_UN) + return f -class Lock(_Lock): - """A lock to prevent data races in situations with multiple processes""" - def __init__(self, path, flags, *args, **kwargs): - _Lock.__init__(self, path, *args, flags=flags, timeout=None, **kwargs) + # This file opener *must* be used in a "with" block. + class Lock: + # Open the file with arguments provided by user. Then acquire + # a lock on that file object (WARNING: Advisory locking). + def __init__(self, path, flags, *args, **kwargs): + # Open the file and acquire a lock on the file before operating + self.file = open(path, mode='w+', *args, **kwargs) + # Lock the opened file + self.file = lock_file(self.file, flags) # flags are either LOCK_EX or LOCK_SH + + # Return the opened file object (knowing a lock has been obtained). + def __enter__(self, *args, **kwargs): return self.file + + # Unlock the file and close the file object. + def __exit__(self, exc_type=None, exc_value=None, traceback=None): + # Flush to make sure all buffered contents are written to file. + self.file.flush() + os.fsync(self.file.fileno()) + # Release the lock on the file. + self.file = unlock_file(self.file) + self.file.close() + # Handle exceptions that may have come up during execution, by + # default any exceptions are raised to the user. + return exc_type == None