Coverage for misc/fairlock/fairlock.py : 95%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from sm_typing import Any, Callable, Dict, Optional, override
3import os
4import socket
5import inspect
6import time
8SOCKDIR = "/run/fairlock"
9START_SERVICE_TIMEOUT_SECS = 2
11class SingletonWithArgs(type):
12 _instances: Dict[Any, Any] = {}
13 _init: Dict[type, Optional[Callable[..., None]]] = {}
15 def __init__(cls, name, bases, dct):
16 cls._init[cls] = dct.get('__init__', None)
18 @override
19 def __call__(cls, *args, **kwargs) -> Any:
20 init = cls._init[cls]
21 if init is not None: 21 ↛ 25line 21 didn't jump to line 25, because the condition on line 21 was never false
22 key: Any = (cls, frozenset(
23 inspect.getcallargs(init, None, *args, **kwargs).items()))
24 else:
25 key = cls
27 if key not in cls._instances:
28 cls._instances[key] = super(SingletonWithArgs, cls).__call__(*args, **kwargs)
29 return cls._instances[key]
31class FairlockDeadlock(Exception):
32 pass
34class FairlockServiceTimeout(Exception):
35 pass
37class Fairlock(metaclass=SingletonWithArgs):
38 def __init__(self, name):
39 self.name = name
40 self.sockname = os.path.join(SOCKDIR, name)
41 self.connected = False
42 self.sock = None
44 def _ensure_service(self):
45 service=f"fairlock@{self.name}.service"
46 os.system(f"/usr/bin/systemctl start {service}")
47 timeout = time.time() + START_SERVICE_TIMEOUT_SECS
48 time.sleep(0.1)
49 while os.system(f"/usr/bin/systemctl --quiet is-active {service}") != 0:
50 time.sleep(0.1)
51 if time.time() > timeout:
52 raise FairlockServiceTimeout(f"Timed out starting service {service}")
54 def _connect_and_recv(self):
55 while True:
56 self.sock.connect(self.sockname)
57 # Merely being connected is not enough. Read a small blob of data.
58 b = self.sock.recv(10)
59 if len(b) > 0: 59 ↛ 64line 59 didn't jump to line 64, because the condition on line 59 was never false
60 return True
61 # If we got a zero-length return, it means the service exited while we
62 # were waiting. Any timeout we put here would be a max wait time to acquire
63 # the lock, which is dangerous.
64 self._ensure_service()
66 def __enter__(self):
67 if self.connected:
68 raise FairlockDeadlock(f"Deadlock on Fairlock resource '{self.name}'")
70 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
71 self.sock.setblocking(True)
72 try:
73 self._connect_and_recv()
74 except (FileNotFoundError, ConnectionRefusedError):
75 self._ensure_service()
76 self._connect_and_recv()
78 self.sock.send(f'{os.getpid()} - {time.monotonic()}'.encode())
79 self.connected = True
80 return self
82 def __exit__(self, type, value, traceback):
83 self.sock.close()
84 self.sock = None
85 self.connected = False
86 return False