Skip to content

Commit c87b023

Browse files
authored
Merge pull request #1 from lutzfischer/cleanup
Add some automatic cleanup on exit
2 parents 0247187 + 58c74a8 commit c87b023

2 files changed

Lines changed: 353 additions & 8 deletions

File tree

dirlock/__init__.py

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,76 @@
11
import os
2+
import signal
23
import time
4+
import atexit
35
from datetime import datetime
6+
"""
7+
This module provides a directory-based locking mechanism.
8+
It allows for acquiring and releasing locks using directories,
9+
which can be useful in scenarios where file-based based
10+
operations can not be done atomically (e.g.
11+
distributed or cluster filesystem)
12+
13+
The DirLock class provides methods to acquire and release locks,
14+
and it supports context management for easy usage.
15+
16+
It also handles cleanup of locks on program exit or signal interrupts.
17+
"""
18+
19+
20+
# keep a list of currently acquired locks
21+
# so these can be cleaned up on exit
22+
_allActiveLocks = set()
23+
# we don't want to call the original handler
24+
original_sigint_handler = signal.getsignal(signal.SIGINT)
25+
original_sigterm_handler = signal.getsignal(signal.SIGTERM)
26+
27+
28+
# function to clean up all active locks
29+
def _clean_locks():
30+
global _allActiveLocks
31+
# Create a copy to avoid "Set changed size during iteration" error
32+
locks_to_release = list(_allActiveLocks)
33+
for dl in locks_to_release:
34+
dl.release()
35+
_allActiveLocks.remove(dl)
36+
37+
38+
def handle_sigint_cleanup(signum, frame):
39+
"""
40+
Handle SIGINT (Ctrl+C) cleanup.
41+
This function is called when a SIGINT signal is received.
42+
It cleans up all active locks and calls the original signal handler if it exists.
43+
"""
44+
global original_sigint_handler
45+
_clean_locks()
46+
if original_sigint_handler is not None:
47+
original_sigint_handler(signum, frame)
48+
49+
50+
def handle_sigterm_cleanup(signum, frame):
51+
"""
52+
Handle SIGTERM cleanup.
53+
This function is called when a SIGTERM signal is received.
54+
It cleans up all active locks and calls the original signal handler if it exists.
55+
"""
56+
global original_sigterm_handler
57+
_clean_locks()
58+
if original_sigterm_handler is not None:
59+
original_sigterm_handler(signum, frame)
60+
61+
62+
# normal exit clean up
63+
atexit.register(_clean_locks)
64+
65+
# ctrl+c cleanup
66+
signal.signal(signal.SIGINT, handle_sigint_cleanup)
67+
# sigterm cleanup
68+
signal.signal(signal.SIGTERM, handle_sigterm_cleanup)
469

570

671
class DirLock:
772
default_retry_interval: float = 0.1
73+
874
def __init__(self,
975
lock_dir: str,
1076
retry_interval: float = None,
@@ -30,12 +96,14 @@ def acquire(self):
3096
"""
3197
Acquire the lock by following the directory-based lock mechanism.
3298
"""
99+
global _allActiveLocks
33100
start_time = datetime.now()
34101

35102
while True:
36103
try:
37104
os.mkdir(self.lock_dir)
38105
self.acquired = True
106+
_allActiveLocks.add(self)
39107
break
40108
except FileExistsError:
41109
pass
@@ -51,11 +119,13 @@ def release(self):
51119
"""
52120
Release the lock if it is held by this instance.
53121
"""
54-
try:
55-
os.rmdir(self.lock_dir)
56-
except FileNotFoundError:
57-
pass
58-
self.acquired = False
122+
if self.acquired:
123+
try:
124+
os.rmdir(self.lock_dir)
125+
_allActiveLocks.remove(self)
126+
except FileNotFoundError:
127+
pass
128+
self.acquired = False
59129

60130
def __enter__(self):
61131
"""
@@ -70,6 +140,8 @@ def __exit__(self, exc_type, exc_value, traceback):
70140
"""
71141
self.release()
72142

143+
73144
class LockTimeoutException(Exception):
74145
def __init__(self, message="acquiring lock timed out"):
75-
super().__init__(message)
146+
super().__init__(message)
147+

0 commit comments

Comments
 (0)