44import stat
55import tempfile
66import time
7+ import warnings
78from functools import partial
89from pathlib import Path
910from typing import Callable , Generator , Optional , Union
1011
1112import yaml
1213from filelock import BaseFileLock , FileLock , SoftFileLock , Timeout
14+ from packaging import version as packaging_version
1315
1416from .. import constants
1517from . import logging
18+ from ._runtime import get_filelock_version
1619
1720
1821logger = logging .get_logger (__name__ )
@@ -73,6 +76,32 @@ def _set_write_permission_and_retry(func, path, excinfo):
7376 func (path )
7477
7578
79+ # `mode=` parameter was added in filelock 3.10.0. We use it to create group-writable lock files (0o664).
80+ # If filelock is too old, we skip the `mode=` parameter and warn the user.
81+ _FILELOCK_SUPPORTS_MODE = packaging_version .parse (get_filelock_version ()) >= packaging_version .parse ("3.10.0" )
82+ _FILELOCK_MODE_WARNING_EMITTED = False
83+
84+
85+ def _create_filelock (lock_cls : type , lock_file : Union [str , Path ], ** kwargs ) -> BaseFileLock :
86+ """Create a filelock instance, passing `mode=0o664` if supported by the installed filelock version."""
87+ global _FILELOCK_MODE_WARNING_EMITTED
88+ if _FILELOCK_SUPPORTS_MODE :
89+ return lock_cls (lock_file , mode = 0o664 , ** kwargs )
90+ else :
91+ if not _FILELOCK_MODE_WARNING_EMITTED :
92+ warnings .warn (
93+ f"filelock version { get_filelock_version ()} does not support the `mode=` parameter. "
94+ "Lock files will be created with the default mode. "
95+ "Consider upgrading filelock to 3.10.0+ (`pip install -U filelock`) if you encounter "
96+ "lock permission issues in a multi-user environment. "
97+ "See https://github.com/huggingface/huggingface_hub/issues/3714 for more details." ,
98+ UserWarning ,
99+ stacklevel = 3 ,
100+ )
101+ _FILELOCK_MODE_WARNING_EMITTED = True
102+ return lock_cls (lock_file , ** kwargs )
103+
104+
76105@contextlib .contextmanager
77106def WeakFileLock (
78107 lock_file : Union [str , Path ], * , timeout : Optional [float ] = None
@@ -89,7 +118,7 @@ def WeakFileLock(
89118 If a timeout is provided, a `filelock.Timeout` exception is raised if the lock is not acquired within the timeout.
90119 """
91120 log_interval = constants .FILELOCK_LOG_EVERY_SECONDS
92- lock = FileLock ( lock_file , timeout = log_interval , mode = 0o664 )
121+ lock = _create_filelock ( FileLock , lock_file , timeout = log_interval )
93122 start_time = time .time ()
94123
95124 while True :
@@ -108,7 +137,7 @@ def WeakFileLock(
108137 logger .warning (
109138 "FileSystem does not appear to support flock. Falling back to SoftFileLock for %s" , lock_file
110139 )
111- lock = SoftFileLock ( lock_file , timeout = log_interval )
140+ lock = _create_filelock ( SoftFileLock , lock_file , timeout = log_interval )
112141 continue
113142 else :
114143 break
0 commit comments