Skip to content

Commit b6c429c

Browse files
committed
Replace filelock with fasteners
1 parent 5ee8733 commit b6c429c

File tree

6 files changed

+54
-52
lines changed

6 files changed

+54
-52
lines changed

docs/requirements.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ archspec==0.2.5
22
ClusterShell==1.9.3
33
docutils==0.18.1; python_version < '3.9'
44
docutils==0.21.2; python_version >= '3.9'
5-
filelock==3.4.1; python_version == '3.6'
6-
filelock==3.12.2; python_version == '3.7'
7-
filelock==3.16.1; python_version == '3.8'
8-
filelock==3.18.0; python_version > '3.8'
5+
fasteners==0.19
96
jinja2==3.0.3; python_version == '3.6'
107
jinja2==3.1.6; python_version >= '3.7'
118
jsonschema==3.2.0

reframe/core/logging.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ def __init__(self, prefix, mode='a', encoding=None, fmt=None,
168168
self.__ignore_keys = set(ignore_keys) if ignore_keys else set()
169169
self.__use_locking = use_locking
170170
self.__lockfile_mode = lockfile_mode
171+
self.__locks = {}
171172

172173
def __generate_header(self, record):
173174
# Generate the header from the record and fmt
@@ -212,6 +213,14 @@ def __generate_header(self, record):
212213

213214
return header
214215

216+
def __lock_file_name(self, logfile=None):
217+
if logfile is None:
218+
logfile = self.baseFilename
219+
220+
prefix = os.path.dirname(logfile)
221+
basename, _ = os.path.splitext(os.path.basename(logfile))
222+
return os.path.join(prefix, f'.{basename}.lock')
223+
215224
def _emit_header(self, record):
216225
if self.baseFilename in self.__streams:
217226
return
@@ -241,14 +250,16 @@ def _emit_header(self, record):
241250
if self.__use_locking:
242251
# When using locking, we need to open, append and write to
243252
# the file at once
244-
lockfile = os.path.join(f'{self.baseFilename}.lock')
245-
with osext.flock(lockfile, self.__lockfile_mode):
253+
rwlock = osext.ReadWriteFileLock(self.__lock_file_name(),
254+
self.__lockfile_mode)
255+
with rwlock.write_lock():
246256
with open(self.baseFilename, mode=self.mode,
247257
encoding=self.encoding) as fp:
248258
if record_header != header:
249259
fp.write(f'{record_header}\n')
250260

251261
self.__streams[self.baseFilename] = None
262+
self.__locks[self.baseFilename] = rwlock
252263
else:
253264
# Open the file for writing and write the header
254265
fp = open(self.baseFilename,
@@ -272,8 +283,7 @@ def emit(self, record):
272283
self.baseFilename = os.path.join(dirname, f'{check_basename}.log')
273284
self._emit_header(record)
274285
if self.__use_locking:
275-
lockfile = os.path.join(dirname, f'.{check_basename}.lock')
276-
with osext.flock(lockfile, self.__lockfile_mode):
286+
with self.__locks[self.baseFilename].write_lock():
277287
with open(self.baseFilename, mode=self.mode,
278288
encoding=self.encoding) as fp:
279289
self.stream = fp
@@ -486,6 +496,9 @@ def _create_filelog_handler(site_config, config_prefix):
486496
ignore_keys = site_config.get(f'{config_prefix}/ignore_keys')
487497
use_locking = site_config.get(f'{config_prefix}/locking_enable')
488498
lockfile_mode = site_config.get(f'{config_prefix}/locking_file_mode')
499+
if lockfile_mode is not None:
500+
lockfile_mode = int(lockfile_mode, base=8)
501+
489502
return MultiFileHandler(filename_patt, mode='a+' if append else 'w+',
490503
fmt=format, perffmt=format_perf,
491504
ignore_keys=ignore_keys,

reframe/frontend/reporting/storage.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ def __init__(self):
8686
else:
8787
self.__db_file_mode = mode
8888

89+
self.__db_lock = osext.ReadWriteFileLock(
90+
os.path.join(os.path.dirname(self.__db_file), '.db.lock'),
91+
self.__db_file_mode
92+
)
93+
8994
def _db_file(self):
9095
prefix = os.path.dirname(self.__db_file)
9196
if not os.path.exists(self.__db_file):
@@ -122,8 +127,7 @@ def _db_connect(self, *args, **kwargs):
122127
return sqlite3.connect(*args, **kwargs)
123128

124129
def _db_lock(self):
125-
lockfile = os.path.join(os.path.dirname(self.__db_file), '.db.lock')
126-
return osext.flock(lockfile, self.__db_file_mode)
130+
return self.__db_lock.write_lock()
127131

128132
def _db_create(self):
129133
clsname = type(self).__name__

reframe/schemas/config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"items": {"type": "string"}
8888
},
8989
"locking_enable": {"type": "boolean"},
90-
"locking_file_mode": {"type": "string"}
90+
"locking_file_mode": {"type": ["string", "null"]}
9191
},
9292
"required": ["prefix"]
9393
}
@@ -636,7 +636,7 @@
636636
"logging/handlers_perflog/filelog_basedir": "./perflogs",
637637
"logging/handlers_perflog/filelog_ignore_keys": [],
638638
"logging/handlers_perflog/filelog_locking_enable": false,
639-
"logging/handlers_perflog/filelog_locking_file_mode": "644",
639+
"logging/handlers_perflog/filelog_locking_file_mode": null,
640640
"logging/handlers_perflog/graylog_extras": {},
641641
"logging/handlers_perflog/httpjson_extras": {},
642642
"logging/handlers_perflog/httpjson_ignore_keys": [],

reframe/utility/osext.py

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import collections.abc
1111
import errno
12-
import filelock
12+
import fasteners
1313
import getpass
1414
import grp
1515
import os
@@ -864,43 +864,34 @@ def unique_abs_paths(paths, prune_children=True):
864864
return list(unique_paths - children)
865865

866866

867-
def flock(name, mode=None):
868-
'''Obtain a file lock
867+
class temp_umask:
868+
'''Temporarily change the umask'''
869+
def __init__(self, mask):
870+
self.__new_mask = mask
871+
self.__old_mask = None
869872

870-
:arg name: the name of the lock file
871-
:arg mode: the file mode of the lock file
872-
:returns: a context manager that holds the lock.
873-
Example usage:
874-
875-
.. code-block:: python
876-
877-
with flock('file.lock'):
878-
# do something
879-
880-
.. versionadded:: 4.8.3
881-
882-
:meta private:
883-
'''
884-
885-
if not isinstance(mode, int):
886-
mode = int(mode, base=8)
873+
def __enter__(self):
874+
self.__old_mask = os.umask(self.__new_mask)
887875

888-
if mode and sys.version_info >= (3, 7):
889-
kwargs = {'mode': mode}
890-
else:
891-
# Python 3.6 forces us to use an older filelock version that does
892-
# not support file modes. File modes where introduced in
893-
# filelock 3.10
894-
kwargs = {}
895-
896-
# Create parent directories of the lock file
897-
#
898-
# NOTE: This is not necessary for filelock >= 3.12.3 and Python >= 3.8
899-
# However, we do create it here, in order to support the older Python
900-
# versions.
901-
prefix = os.path.dirname(name)
902-
os.makedirs(prefix, exist_ok=True)
903-
return filelock.FileLock(name, **kwargs)
876+
def __exit__(self, exc_type, exc_val, exc_tb):
877+
os.umask(self.__old_mask)
878+
879+
880+
class ReadWriteFileLock(fasteners.InterProcessReaderWriterLock):
881+
def __init__(self, path, mode=None):
882+
super().__init__(path)
883+
self._mode = mode
884+
885+
def _do_open(self, *args, **kwargs):
886+
if self._mode is not None:
887+
# We create the directory structure ourselves, so that the mask
888+
# applies strictly to the lock file if the parent directories do
889+
# not exist
890+
os.makedirs(os.path.dirname(self.path), exist_ok=True)
891+
with temp_umask(0o0777 ^ self._mode):
892+
return super()._do_open(*args, **kwargs)
893+
else:
894+
return super()._do_open(*args, **kwargs)
904895

905896

906897
def cray_cdt_version():

requirements.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ archspec==0.2.5
22
argcomplete==3.1.2; python_version < '3.8'
33
argcomplete==3.6.1; python_version >= '3.8'
44
ClusterShell==1.9.3
5-
filelock==3.4.1; python_version == '3.6'
6-
filelock==3.12.2; python_version == '3.7'
7-
filelock==3.16.1; python_version == '3.8'
8-
filelock==3.18.0; python_version > '3.8'
5+
fasteners==0.19
96
importlib_metadata==4.0.1; python_version < '3.8'
107
jinja2==3.0.3; python_version == '3.6'
118
jinja2==3.1.6; python_version >= '3.7'

0 commit comments

Comments
 (0)