Skip to content

Commit a6678c4

Browse files
committed
Revert "Delete unused utils.py"
This reverts commit f2eb15d.
1 parent 37143ca commit a6678c4

1 file changed

Lines changed: 84 additions & 0 deletions

File tree

server/server/utils.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import sys
2+
from contextlib import suppress
3+
from logging import getLogger
4+
from time import sleep, time
5+
from uuid import uuid4
6+
7+
from redis.exceptions import WatchError
8+
9+
if sys.version_info[:2] < (3, 12):
10+
from collections.abc import Iterable, Iterator
11+
from itertools import islice
12+
from typing import TypeVar
13+
14+
LOG = getLogger("webcompatmanager.utils")
15+
16+
17+
class RedisLock:
18+
"""Simple Redis mutex lock.
19+
20+
based on: https://redislabs.com/ebook/part-2-core-concepts \
21+
/chapter-6-application-components-in-redis/6-2-distributed-locking \
22+
/6-2-3-building-a-lock-in-redis/
23+
24+
Not using RedLock because it isn't passable as a celery argument, so we can't
25+
release the lock in an async chain.
26+
"""
27+
28+
def __init__(self, conn, name, unique_id=None):
29+
self.conn = conn
30+
self.name = name
31+
if unique_id is None:
32+
self.unique_id = str(uuid4())
33+
else:
34+
self.unique_id = unique_id
35+
36+
def acquire(self, acquire_timeout=10, lock_expiry=None):
37+
end = time() + acquire_timeout
38+
while time() < end:
39+
if self.conn.set(self.name, self.unique_id, ex=lock_expiry, nx=True):
40+
LOG.debug("Acquired lock: %s(%s)", self.name, self.unique_id)
41+
return self.unique_id
42+
43+
sleep(0.05)
44+
45+
LOG.debug("Failed to acquire lock: %s(%s)", self.name, self.unique_id)
46+
return None
47+
48+
def release(self):
49+
with self.conn.pipeline() as pipe:
50+
while True:
51+
with suppress(WatchError):
52+
pipe.watch(self.name)
53+
existing = pipe.get(self.name)
54+
if not isinstance(existing, str):
55+
existing = existing.decode("ascii")
56+
57+
if existing == self.unique_id:
58+
pipe.multi()
59+
pipe.delete(self.name)
60+
pipe.execute()
61+
LOG.debug("Released lock: %s(%s)", self.name, self.unique_id)
62+
return True
63+
64+
pipe.unwatch()
65+
break
66+
67+
LOG.debug(
68+
"Failed to release lock: %s(%s) != %s", self.name, self.unique_id, existing
69+
)
70+
return False
71+
72+
73+
if sys.version_info[:2] < (3, 12):
74+
# generic type for `batched` below
75+
_T = TypeVar("_T")
76+
77+
# added to itertools in 3.12
78+
def batched(iterable: Iterable[_T], n: int) -> Iterator[tuple[_T, ...]]:
79+
# batched('ABCDEFG', 3) → ABC DEF G
80+
if n < 1:
81+
raise ValueError("n must be at least one")
82+
iterator = iter(iterable)
83+
while batch := tuple(islice(iterator, n)):
84+
yield batch

0 commit comments

Comments
 (0)