fix(auth): serialise the token refresh across processes
The login cache is shared by every mdrs process, but the refresh was guarded by a lock that only reaches inside one. Concurrent runs each sent the same refresh token, and a provider that rotates them accepts the first and refuses the rest. - hold a lock that spans processes across the whole read-refresh- write, checking cheaply first so ordinary requests never take it - write the cache through a temporary file: opening it for writing truncates it, and a reader landing in that window found it empty and threw the session away - take the lock for every write, not just the refresh, so a login running beside one cannot be silently reverted - wait for a busy lock on Windows rather than giving up after the ten attempts msvcrt allows, and retry the rename it refuses while a reader still holds the file open - bound the refresh request on its own, so a provider that goes quiet cannot hold the lock indefinitely - accept bare hostnames such as localhost, store URLs without the trailing slash, and join download paths through one helper
This commit is contained in:
+16
-2
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
from typing import IO, Any
|
||||
import time
|
||||
from typing import IO, Any, Final
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
if os.name == "nt":
|
||||
@@ -9,10 +10,23 @@ elif os.name == "posix":
|
||||
|
||||
|
||||
class FileLock:
|
||||
# Long enough to outlast a token refresh, which is what the lock is held across.
|
||||
WAIT_SECONDS: Final[float] = 60.0
|
||||
|
||||
@staticmethod
|
||||
def lock(file: IO[Any]) -> None:
|
||||
if os.name == "nt":
|
||||
msvcrt.locking(file.fileno(), msvcrt.LK_LOCK, 1)
|
||||
# msvcrt.LK_LOCK gives up after ten one-second attempts, which is shorter
|
||||
# than the refresh it now has to wait for, so do the waiting here instead.
|
||||
deadline = time.monotonic() + FileLock.WAIT_SECONDS
|
||||
while True:
|
||||
try:
|
||||
msvcrt.locking(file.fileno(), msvcrt.LK_NBLCK, 1)
|
||||
return
|
||||
except OSError:
|
||||
if time.monotonic() >= deadline:
|
||||
raise
|
||||
time.sleep(0.1)
|
||||
elif os.name == "posix":
|
||||
fcntl.flock(file.fileno(), fcntl.LOCK_EX)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user