Skip to content

Commit 4d915a8

Browse files
[FIX] Also allow errno.EBUSY during emptydirs on NFS (#3357)
* Also allow `errno.EBUSY` during `emptydirs` on NFS - Can occur if a file is still open somewhere, so NFS will rename it to a hidden file in the same directory - When `shutil` tries to delete that hidden file, we get an `OSError` with `errno.EBUSY` * Ignore `.nfs` placeholder files when catching the error - I forgot that `os.listdir` also lists hidden files in the previous commit * Add unit test for `emptydirs` on NFS - With mock for NFS silly-rename (yes, it's really called that) behavior * Run `black` * Update nipype/utils/tests/test_filemanip.py Handle mock test case when no `dir_fd` is passed
1 parent 4e10801 commit 4d915a8

File tree

2 files changed

+32
-3
lines changed

2 files changed

+32
-3
lines changed

nipype/utils/filemanip.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -780,8 +780,13 @@ def emptydirs(path, noexist_ok=False):
780780
try:
781781
shutil.rmtree(path)
782782
except OSError as ex:
783-
elcont = os.listdir(path)
784-
if ex.errno == errno.ENOTEMPTY and not elcont:
783+
elcont = [
784+
Path(root) / file
785+
for root, _, files in os.walk(path)
786+
for file in files
787+
if not file.startswith(".nfs")
788+
]
789+
if ex.errno in [errno.ENOTEMPTY, errno.EBUSY] and not elcont:
785790
fmlogger.warning(
786791
"An exception was raised trying to remove old %s, but the path"
787792
" seems empty. Is it an NFS mount?. Passing the exception.",
@@ -793,7 +798,7 @@ def emptydirs(path, noexist_ok=False):
793798
else:
794799
raise ex
795800

796-
os.makedirs(path)
801+
os.makedirs(path, exist_ok=True)
797802

798803

799804
def silentrm(filename):

nipype/utils/tests/test_filemanip.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
savepkl,
3232
path_resolve,
3333
write_rst_list,
34+
emptydirs,
3435
)
3536

3637

@@ -670,3 +671,26 @@ def test_write_rst_list(tmp_path, items, expected):
670671
else:
671672
with pytest.raises(expected):
672673
write_rst_list(items)
674+
675+
676+
def nfs_unlink(pathlike, *, dir_fd=None):
677+
if dir_fd is None:
678+
path = Path(pathlike)
679+
deleted = path.with_name(".nfs00000000")
680+
path.rename(deleted)
681+
else:
682+
os.rename(pathlike, ".nfs1111111111", src_dir_fd=dir_fd, dst_dir_fd=dir_fd)
683+
684+
685+
def test_emptydirs_dangling_nfs(tmp_path):
686+
busyfile = tmp_path / "base" / "subdir" / "busyfile"
687+
busyfile.parent.mkdir(parents=True)
688+
busyfile.touch()
689+
690+
with mock.patch("os.unlink") as mocked:
691+
mocked.side_effect = nfs_unlink
692+
emptydirs(tmp_path / "base")
693+
694+
assert Path.exists(tmp_path / "base")
695+
assert not busyfile.exists()
696+
assert busyfile.parent.exists() # Couldn't remove

0 commit comments

Comments
 (0)