Hide keyboard shortcuts

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 

2 

3import os 

4import socket 

5import inspect 

6import time 

7 

8SOCKDIR = "/run/fairlock" 

9START_SERVICE_TIMEOUT_SECS = 2 

10 

11class SingletonWithArgs(type): 

12 _instances: Dict[Any, Any] = {} 

13 _init: Dict[type, Optional[Callable[..., None]]] = {} 

14 

15 def __init__(cls, name, bases, dct): 

16 cls._init[cls] = dct.get('__init__', None) 

17 

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 

26 

27 if key not in cls._instances: 

28 cls._instances[key] = super(SingletonWithArgs, cls).__call__(*args, **kwargs) 

29 return cls._instances[key] 

30 

31class FairlockDeadlock(Exception): 

32 pass 

33 

34class FairlockServiceTimeout(Exception): 

35 pass 

36 

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 

43 

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}") 

53 

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() 

65 

66 def __enter__(self): 

67 if self.connected: 

68 raise FairlockDeadlock(f"Deadlock on Fairlock resource '{self.name}'") 

69 

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() 

77 

78 self.sock.send(f'{os.getpid()} - {time.monotonic()}'.encode()) 

79 self.connected = True 

80 return self 

81 

82 def __exit__(self, type, value, traceback): 

83 self.sock.close() 

84 self.sock = None 

85 self.connected = False 

86 return False 

87