Skip to content

Commit 2162c84

Browse files
authored
Merge pull request #3025 from satra/enh/filelock
ENH: replace portalocker with filelock
2 parents 0470641 + fdf12b7 commit 2162c84

File tree

7 files changed

+54
-222
lines changed

7 files changed

+54
-222
lines changed

nipype/algorithms/misc.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -799,11 +799,11 @@ def _run_interface(self, runtime):
799799
'(http://pandas.pydata.org/) to run.'), e)
800800

801801
try:
802-
import lockfile as pl
802+
from filelock import SoftFileLock
803803
self._have_lock = True
804804
except ImportError:
805805
from warnings import warn
806-
warn(('Python module lockfile was not found: AddCSVRow will not be'
806+
warn(('Python module filelock was not found: AddCSVRow will not be'
807807
' thread-safe in multi-processor execution'))
808808

809809
input_dict = {}
@@ -822,7 +822,7 @@ def _run_interface(self, runtime):
822822
df = pd.DataFrame([input_dict])
823823

824824
if self._have_lock:
825-
self._lock = pl.FileLock(self.inputs.in_file)
825+
self._lock = SoftFileLock('%s.lock' % self.inputs.in_file)
826826

827827
# Acquire lock
828828
self._lock.acquire()
@@ -837,13 +837,6 @@ def _run_interface(self, runtime):
837837
if self._have_lock:
838838
self._lock.release()
839839

840-
# Using nipype.external.portalocker this might be something like:
841-
# with pl.Lock(self.inputs.in_file, timeout=1) as fh:
842-
# if op.exists(fh):
843-
# formerdf = pd.read_csv(fh, index_col=0)
844-
# df = pd.concat([formerdf, df], ignore_index=True)
845-
# df.to_csv(fh)
846-
847840
return runtime
848841

849842
def _list_outputs(self):

nipype/external/cloghandler.py

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@
3636
testing, performance was more than adequate, but if you need a high-volume or
3737
low-latency solution, I suggest you look elsewhere.
3838
39-
This module currently only support the 'nt' and 'posix' platforms due to the
40-
usage of the portalocker module. I do not have access to any other platforms
41-
for testing, patches are welcome.
42-
4339
See the README file for an example usage of this module.
4440
4541
"""
@@ -63,13 +59,7 @@
6359
except ImportError:
6460
codecs = None
6561

66-
# Question/TODO: Should we have a fallback mode if we can't load portalocker /
67-
# we should still be better off than with the standard RotattingFileHandler
68-
# class, right? We do some rename checking... that should prevent some file
69-
# clobbering that the builtin class allows.
70-
71-
# sibling module than handles all the ugly platform-specific details of file locking
72-
from .portalocker import lock, unlock, LOCK_EX, LOCK_NB, LockException
62+
from filelock import SoftFileLock
7363

7464
# A client can set this to true to automatically convert relative paths to
7565
# absolute paths (which will also hide the absolute path warnings)
@@ -168,11 +158,8 @@ def __init__(self,
168158
self.maxBytes = maxBytes
169159
self.backupCount = backupCount
170160
# Prevent multiple extensions on the lock file (Only handles the normal "*.log" case.)
171-
if filename.endswith(".log"):
172-
lock_file = filename[:-4]
173-
else:
174-
lock_file = filename
175-
self.stream_lock = open(lock_file + ".lock", "w")
161+
self.lock_file = '%s.lock' % filename
162+
self.stream_lock = SoftFileLock(self.lock_file)
176163

177164
# For debug mode, swap out the "_degrade()" method with a more a verbose one.
178165
if debug:
@@ -189,7 +176,7 @@ def acquire(self):
189176
in 'degraded' mode. """
190177
# handle thread lock
191178
Handler.acquire(self)
192-
lock(self.stream_lock, LOCK_EX)
179+
self.stream_lock.acquire()
193180
if self.stream.closed:
194181
self._openFile(self.mode)
195182

@@ -206,7 +193,7 @@ def release(self):
206193
self.stream.close()
207194
finally:
208195
try:
209-
unlock(self.stream_lock)
196+
self.stream_lock.release()
210197
finally:
211198
# release thread lock
212199
Handler.release(self)

nipype/external/portalocker.py

Lines changed: 0 additions & 145 deletions
This file was deleted.

nipype/info.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ def get_nipype_gitversion():
164164
'scipy>=%s,<%s ; python_version <= "3.4"' % (SCIPY_MIN_VERSION, SCIPY_MAX_VERSION_34),
165165
'simplejson>=%s' % SIMPLEJSON_MIN_VERSION,
166166
'traits>=%s,!=5.0' % TRAITS_MIN_VERSION,
167+
'filelock>=3.0.0'
167168
]
168169

169170
# neurdflib has to come after prov

nipype/utils/config.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from future import standard_library
2626

2727
from .misc import str2bool
28-
from ..external import portalocker
28+
from filelock import SoftFileLock
2929

3030
standard_library.install_aliases()
3131

@@ -209,9 +209,9 @@ def get_data(self, key):
209209
"""Read options file"""
210210
if not os.path.exists(self.data_file):
211211
return None
212-
with open(self.data_file, 'rt') as file:
213-
portalocker.lock(file, portalocker.LOCK_EX)
214-
datadict = load(file)
212+
with SoftFileLock('%s.lock' % self.data_file):
213+
with open(self.data_file, 'rt') as file:
214+
datadict = load(file)
215215
if key in datadict:
216216
return datadict[key]
217217
return None
@@ -220,17 +220,17 @@ def save_data(self, key, value):
220220
"""Store config flie"""
221221
datadict = {}
222222
if os.path.exists(self.data_file):
223-
with open(self.data_file, 'rt') as file:
224-
portalocker.lock(file, portalocker.LOCK_EX)
225-
datadict = load(file)
223+
with SoftFileLock('%s.lock' % self.data_file):
224+
with open(self.data_file, 'rt') as file:
225+
datadict = load(file)
226226
else:
227227
dirname = os.path.dirname(self.data_file)
228228
if not os.path.exists(dirname):
229229
mkdir_p(dirname)
230-
with open(self.data_file, 'wt') as file:
231-
portalocker.lock(file, portalocker.LOCK_EX)
232-
datadict[key] = value
233-
dump(datadict, file)
230+
with SoftFileLock('%s.lock' % self.data_file):
231+
with open(self.data_file, 'wt') as file:
232+
datadict[key] = value
233+
dump(datadict, file)
234234

235235
def update_config(self, config_dict):
236236
"""Extend internal dictionary with config_dict"""

nipype/utils/filemanip.py

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import contextlib
2222
import posixpath
2323
import simplejson as json
24+
from filelock import SoftFileLock
2425

2526
from builtins import str, bytes, open
2627

@@ -684,36 +685,33 @@ def loadpkl(infile):
684685

685686
unpkl = None
686687
with indirectory(infile.parent):
687-
pkl_file = pklopen(infile.name, 'rb')
688-
689-
try: # Look if pkl file contains version file
690-
pkl_metadata_line = pkl_file.readline()
691-
pkl_metadata = json.loads(pkl_metadata_line)
692-
except (UnicodeDecodeError, json.JSONDecodeError):
693-
# Could not get version info
694-
pkl_file.seek(0)
695-
696-
try:
697-
unpkl = pickle.load(pkl_file)
698-
except UnicodeDecodeError:
699-
# Was this pickle created with Python 2.x?
700-
unpkl = pickle.load(pkl_file, fix_imports=True, encoding='utf-8')
701-
fmlogger.info('Successfully loaded pkl in compatibility mode.')
702-
# Unpickling problems
703-
except Exception as e:
704-
if pkl_metadata and 'version' in pkl_metadata:
705-
from nipype import __version__ as version
706-
if pkl_metadata['version'] != version:
707-
fmlogger.error("""\
688+
with SoftFileLock('%s.lock' % infile.name):
689+
with pklopen(infile.name, 'rb') as pkl_file:
690+
try: # Look if pkl file contains version file
691+
pkl_metadata_line = pkl_file.readline()
692+
pkl_metadata = json.loads(pkl_metadata_line)
693+
except (UnicodeDecodeError, json.JSONDecodeError):
694+
# Could not get version info
695+
pkl_file.seek(0)
696+
try:
697+
unpkl = pickle.load(pkl_file)
698+
except UnicodeDecodeError:
699+
# Was this pickle created with Python 2.x?
700+
unpkl = pickle.load(pkl_file, fix_imports=True, encoding='utf-8')
701+
fmlogger.info('Successfully loaded pkl in compatibility mode.')
702+
# Unpickling problems
703+
except Exception as e:
704+
if pkl_metadata and 'version' in pkl_metadata:
705+
from nipype import __version__ as version
706+
if pkl_metadata['version'] != version:
707+
fmlogger.error("""\
708708
Attempted to open a results file generated by Nipype version %s, \
709709
with an incompatible Nipype version (%s)""", pkl_metadata['version'], version)
710-
raise e
711-
fmlogger.error("""\
710+
raise e
711+
fmlogger.error("""\
712712
No metadata was found in the pkl file. Make sure you are currently using \
713713
the same Nipype version from the generated pkl.""")
714-
raise e
715-
finally:
716-
pkl_file.close()
714+
raise e
717715

718716
if unpkl is None:
719717
raise ValueError('Loading %s resulted in None.' % infile)
@@ -754,20 +752,17 @@ def read_stream(stream, logger=None, encoding=None):
754752

755753

756754
def savepkl(filename, record, versioning=False):
757-
if filename.endswith('pklz'):
758-
pkl_file = gzip.open(filename, 'wb')
759-
else:
760-
pkl_file = open(filename, 'wb')
761-
762-
if versioning:
763-
from nipype import __version__ as version
764-
metadata = json.dumps({'version': version})
755+
pklopen = gzip.open if filename.endswith('.pklz') else open
756+
with SoftFileLock('%s.lock' % filename):
757+
with pklopen(filename, 'wb') as pkl_file:
758+
if versioning:
759+
from nipype import __version__ as version
760+
metadata = json.dumps({'version': version})
765761

766-
pkl_file.write(metadata.encode('utf-8'))
767-
pkl_file.write('\n'.encode('utf-8'))
762+
pkl_file.write(metadata.encode('utf-8'))
763+
pkl_file.write('\n'.encode('utf-8'))
768764

769-
pickle.dump(record, pkl_file)
770-
pkl_file.close()
765+
pickle.dump(record, pkl_file)
771766

772767

773768
rst_levels = ['=', '-', '~', '+']

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ python-dateutil>=2.2
1616
scipy>=0.14
1717
simplejson>=3.8.0
1818
traits>=4.6
19+
filelock>= 3.0.0

0 commit comments

Comments
 (0)