1+ from __future__ import annotations
2+
3+ import logging
14import os
25import sqlite3
36import threading
4- import logging
57import time
6- from _error import Timeout
7- from filelock ._api import AcquireReturnProxy , BaseFileLock
8- from typing import Literal , Any
98from contextlib import contextmanager
9+ from typing import Any , Literal
1010from weakref import WeakValueDictionary
1111
12+ from _error import Timeout
13+
14+ from filelock ._api import AcquireReturnProxy
15+
1216_LOGGER = logging .getLogger ("filelock" )
1317
1418# PRAGMA busy_timeout=N delegates to https://www.sqlite.org/c3ref/busy_timeout.html,
1519# which accepts an int argument, which has the maximum value of 2_147_483_647 on 32-bit
1620# systems. Use even a lower value to be safe. This 2 bln milliseconds is about 23 days.
1721_MAX_SQLITE_TIMEOUT_MS = 2_000_000_000 - 1
1822
23+
1924def timeout_for_sqlite (timeout : float , blocking : bool , already_waited : float ) -> int :
2025 if blocking is False :
2126 return 0
22-
27+
2328 if timeout == - 1 :
2429 return _MAX_SQLITE_TIMEOUT_MS
25-
30+
2631 if timeout < 0 :
27- raise ValueError ("timeout must be a non-negative number or -1" )
28-
32+ msg = "timeout must be a non-negative number or -1"
33+ raise ValueError (msg )
34+
2935 if timeout > 0 :
30- timeout = timeout - already_waited
31- if timeout < 0 :
32- timeout = 0
33-
36+ timeout -= already_waited
37+ timeout = max (timeout , 0 )
38+
3439 assert timeout >= 0
3540
3641 timeout_ms = int (timeout * 1000 )
@@ -42,9 +47,16 @@ def timeout_for_sqlite(timeout: float, blocking: bool, already_waited: float) ->
4247
4348class _ReadWriteLockMeta (type ):
4449 """Metaclass that redirects instance creation to get_lock() when is_singleton=True."""
45- def __call__ (cls , lock_file : str | os .PathLike [str ],
46- timeout : float = - 1 , blocking : bool = True ,
47- is_singleton : bool = True , * args : Any , ** kwargs : Any ) -> "ReadWriteLock" :
50+
51+ def __call__ (
52+ cls ,
53+ lock_file : str | os .PathLike [str ],
54+ timeout : float = - 1 ,
55+ blocking : bool = True ,
56+ is_singleton : bool = True ,
57+ * args : Any ,
58+ ** kwargs : Any ,
59+ ) -> ReadWriteLock :
4860 if is_singleton :
4961 return cls .get_lock (lock_file , timeout , blocking )
5062 return super ().__call__ (lock_file , timeout , blocking , is_singleton , * args , ** kwargs )
@@ -56,16 +68,22 @@ class ReadWriteLock(metaclass=_ReadWriteLockMeta):
5668 _instances_lock = threading .Lock ()
5769
5870 @classmethod
59- def get_lock (cls , lock_file : str | os .PathLike [str ],
60- timeout : float = - 1 , blocking : bool = True ) -> "ReadWriteLock" :
71+ def get_lock (cls , lock_file : str | os .PathLike [str ], timeout : float = - 1 , blocking : bool = True ) -> ReadWriteLock :
6172 """Return the one-and-only ReadWriteLock for a given file."""
6273 normalized = os .path .abspath (lock_file )
6374 with cls ._instances_lock :
6475 if normalized not in cls ._instances :
6576 cls ._instances [normalized ] = cls (lock_file , timeout , blocking )
6677 instance = cls ._instances [normalized ]
6778 if instance .timeout != timeout or instance .blocking != blocking :
68- raise ValueError ("Singleton lock created with timeout=%s, blocking=%s, cannot be changed to timeout=%s, blocking=%s" , instance .timeout , instance .blocking , timeout , blocking )
79+ msg = "Singleton lock created with timeout=%s, blocking=%s, cannot be changed to timeout=%s, blocking=%s"
80+ raise ValueError (
81+ msg ,
82+ instance .timeout ,
83+ instance .blocking ,
84+ timeout ,
85+ blocking ,
86+ )
6987 return instance
7088
7189 def __init__ (
@@ -85,7 +103,7 @@ def __init__(
85103 self ._internal_lock = threading .Lock ()
86104 self ._lock_level = 0 # Reentrance counter.
87105 # _current_mode holds the active lock mode ("read" or "write") or None if no lock is held.
88- self ._current_mode : Literal ["read" , "write" , None ] = None
106+ self ._current_mode : Literal ["read" , "write" ] | None = None
89107 # _lock_level is the reentrance counter.
90108 self ._lock_level = 0
91109 self .con = sqlite3 .connect (self .lock_file , check_same_thread = False )
@@ -101,21 +119,25 @@ def __init__(
101119 # acquire, so crashes cannot adversely affect the DB. Even journal_mode=OFF would probably
102120 # be fine, too, but the SQLite documentation says that ROLLBACK becomes *undefined behaviour*
103121 # with journal_mode=OFF which sounds scarier.
104- self .con .execute (' PRAGMA journal_mode=MEMORY;' )
122+ self .con .execute (" PRAGMA journal_mode=MEMORY;" )
105123
106124 def acquire_read (self , timeout : float = - 1 , blocking : bool = True ) -> AcquireReturnProxy :
107- """Acquire a read lock. If a lock is already held, it must be a read lock.
108- Upgrading from read to write is prohibited."""
109-
125+ """
126+ Acquire a read lock. If a lock is already held, it must be a read lock.
127+ Upgrading from read to write is prohibited.
128+ """
110129 # Attempt to re-enter already held lock.
111130 with self ._internal_lock :
112131 if self ._lock_level > 0 :
113132 # Must already be in read mode.
114133 if self ._current_mode != "read" :
115- raise RuntimeError (
116- f"Cannot acquire read lock on { self .lock_file } (lock id: { id (self )} ): "
117- "already holding a write lock (downgrade not allowed)"
118- )
134+ msg = (
135+ f"Cannot acquire read lock on { self .lock_file } (lock id: { id (self )} ): "
136+ "already holding a write lock (downgrade not allowed)"
137+ )
138+ raise RuntimeError (
139+ msg
140+ )
119141 self ._lock_level += 1
120142 return AcquireReturnProxy (lock = self )
121143
@@ -131,47 +153,54 @@ def acquire_read(self, timeout: float = -1, blocking: bool = True) -> AcquireRet
131153 with self ._internal_lock :
132154 if self ._lock_level > 0 :
133155 if self ._current_mode != "read" :
134- raise RuntimeError (
156+ msg = (
135157 f"Cannot acquire read lock on { self .lock_file } (lock id: { id (self )} ): "
136158 "already holding a write lock (downgrade not allowed)"
137159 )
160+ raise RuntimeError (
161+ msg
162+ )
138163 self ._lock_level += 1
139164 return AcquireReturnProxy (lock = self )
140-
165+
141166 waited = time .perf_counter () - start_time
142167 timeout_ms = timeout_for_sqlite (timeout , blocking , waited )
143-
144- self .con .execute (' PRAGMA busy_timeout=?;' , (timeout_ms ,))
145- self .con .execute (' BEGIN TRANSACTION;' )
168+
169+ self .con .execute (" PRAGMA busy_timeout=?;" , (timeout_ms ,))
170+ self .con .execute (" BEGIN TRANSACTION;" )
146171 # Need to make SELECT to compel SQLite to actually acquire a SHARED db lock.
147172 # See https://www.sqlite.org/lockingv3.html#transaction_control
148- self .con .execute (' SELECT name from sqlite_schema LIMIT 1;' )
173+ self .con .execute (" SELECT name from sqlite_schema LIMIT 1;" )
149174
150175 with self ._internal_lock :
151176 self ._current_mode = "read"
152177 self ._lock_level = 1
153-
178+
154179 return AcquireReturnProxy (lock = self )
155180
156181 except sqlite3 .OperationalError as e :
157- if ' database is locked' not in str (e ):
182+ if " database is locked" not in str (e ):
158183 raise # Re-raise unexpected errors.
159184 raise Timeout (self .lock_file )
160185 finally :
161186 self ._transaction_lock .release ()
162187
163188 def acquire_write (self , timeout : float = - 1 , blocking : bool = True ) -> AcquireReturnProxy :
164- """Acquire a write lock. If a lock is already held, it must be a write lock.
165- Upgrading from read to write is prohibited."""
166-
189+ """
190+ Acquire a write lock. If a lock is already held, it must be a write lock.
191+ Upgrading from read to write is prohibited.
192+ """
167193 # Attempt to re-enter already held lock.
168194 with self ._internal_lock :
169195 if self ._lock_level > 0 :
170196 if self ._current_mode != "write" :
171- raise RuntimeError (
197+ msg = (
172198 f"Cannot acquire write lock on { self .lock_file } (lock id: { id (self )} ): "
173199 "already holding a read lock (upgrade not allowed)"
174200 )
201+ raise RuntimeError (
202+ msg
203+ )
175204 self ._lock_level += 1
176205 return AcquireReturnProxy (lock = self )
177206
@@ -185,27 +214,30 @@ def acquire_write(self, timeout: float = -1, blocking: bool = True) -> AcquireRe
185214 with self ._internal_lock :
186215 if self ._lock_level > 0 :
187216 if self ._current_mode != "write" :
188- raise RuntimeError (
217+ msg = (
189218 f"Cannot acquire write lock on { self .lock_file } (lock id: { id (self )} ): "
190219 "already holding a read lock (upgrade not allowed)"
191220 )
221+ raise RuntimeError (
222+ msg
223+ )
192224 self ._lock_level += 1
193225 return AcquireReturnProxy (lock = self )
194-
226+
195227 waited = time .perf_counter () - start_time
196228 timeout_ms = timeout_for_sqlite (timeout , blocking , waited )
197-
198- self .con .execute (' PRAGMA busy_timeout=?;' , (timeout_ms ,))
199- self .con .execute (' BEGIN EXCLUSIVE TRANSACTION;' )
229+
230+ self .con .execute (" PRAGMA busy_timeout=?;" , (timeout_ms ,))
231+ self .con .execute (" BEGIN EXCLUSIVE TRANSACTION;" )
200232
201233 with self ._internal_lock :
202234 self ._current_mode = "write"
203235 self ._lock_level = 1
204-
236+
205237 return AcquireReturnProxy (lock = self )
206238
207239 except sqlite3 .OperationalError as e :
208- if ' database is locked' not in str (e ):
240+ if " database is locked" not in str (e ):
209241 raise # Re-raise if it is an unexpected error.
210242 raise Timeout (self .lock_file )
211243 finally :
@@ -216,7 +248,8 @@ def release(self, force: bool = False) -> None:
216248 if self ._lock_level == 0 :
217249 if force :
218250 return
219- raise RuntimeError (f"Cannot release a lock on { self .lock_file } (lock id: { id (self )} ) that is not held" )
251+ msg = f"Cannot release a lock on { self .lock_file } (lock id: { id (self )} ) that is not held"
252+ raise RuntimeError (msg )
220253 if force :
221254 self ._lock_level = 0
222255 else :
@@ -233,10 +266,11 @@ def release(self, force: bool = False) -> None:
233266 # (We provide two context managers as helpers.)
234267
235268 @contextmanager
236- def read_lock (self , timeout : float | None = None ,
237- blocking : bool | None = None ):
238- """Context manager for acquiring a read lock.
239- Attempts to upgrade to write lock are disallowed."""
269+ def read_lock (self , timeout : float | None = None , blocking : bool | None = None ):
270+ """
271+ Context manager for acquiring a read lock.
272+ Attempts to upgrade to write lock are disallowed.
273+ """
240274 if timeout is None :
241275 timeout = self .timeout
242276 if blocking is None :
@@ -248,10 +282,11 @@ def read_lock(self, timeout: float | None = None,
248282 self .release ()
249283
250284 @contextmanager
251- def write_lock (self , timeout : float | None = None ,
252- blocking : bool | None = None ):
253- """Context manager for acquiring a write lock.
254- Acquiring read locks on the same file while helding a write lock is prohibited."""
285+ def write_lock (self , timeout : float | None = None , blocking : bool | None = None ):
286+ """
287+ Context manager for acquiring a write lock.
288+ Acquiring read locks on the same file while helding a write lock is prohibited.
289+ """
255290 if timeout is None :
256291 timeout = self .timeout
257292 if blocking is None :
@@ -261,9 +296,7 @@ def write_lock(self, timeout: float | None = None,
261296 yield
262297 finally :
263298 self .release ()
264-
299+
265300 def __del__ (self ) -> None :
266301 """Called when the lock object is deleted."""
267302 self .release (force = True )
268-
269-
0 commit comments