diff options
author | Frederick Yin <fkfd@fkfd.me> | 2021-10-27 09:31:04 +0800 |
---|---|---|
committer | Frederick Yin <fkfd@fkfd.me> | 2021-10-27 10:10:06 +0800 |
commit | 09bd022f5a713c24ab376f0d0901aba436dc151a (patch) | |
tree | 07aea89c37f1dd0927da9c56df36a7bb4e97932f /jimbrella | |
parent | cbf419cb79f4a0d06c65167f81f10f503fff391d (diff) |
Separate Lockfile into new class
Diffstat (limited to 'jimbrella')
-rw-r--r-- | jimbrella/admin_log.py | 41 | ||||
-rw-r--r-- | jimbrella/database.py | 35 | ||||
-rw-r--r-- | jimbrella/lockfile.py | 33 |
3 files changed, 55 insertions, 54 deletions
diff --git a/jimbrella/admin_log.py b/jimbrella/admin_log.py index 811d722..4cc9b16 100644 --- a/jimbrella/admin_log.py +++ b/jimbrella/admin_log.py @@ -1,6 +1,7 @@ import csv import os from datetime import datetime +from .lockfile import Lockfile class AdminLog: @@ -33,25 +34,7 @@ class AdminLog: def __init__(self, path: str): self.path = path - - def _lock(self) -> int: - """Create a lockfile for admin log. Operates in the sameway as `Database._lock`.""" - try: - f = open(self.path + ".lock", "x") - f.close() - return 1 - except FileExistsError: - return 0 - - def _unlock(self) -> None: - """Remove lockfile created by `_lock`. - - If there is no lockfile, simply ignore. - """ - try: - os.remove(self.path + ".lock") - except FileNotFoundError: - pass + self.lockfile = Lockfile(self.path) def _read(self) -> list: """Deserialize admin log.""" @@ -102,7 +85,7 @@ class AdminLog: `logs` is a list of log entries, to be appended at the end of the log file. """ # wait until database is locked for this write - while not self._lock(): + while not self.lockfile.lock(): continue with open(self.path, "a") as f: # append only @@ -112,9 +95,21 @@ class AdminLog: line = [event, entry["date"].isoformat()] info = [] if event in ("TAKEAWAY", "GIVEBACK", "OVERDUE"): - info = [entry["serial"], entry["tenant_name"], entry["tenant_id"], entry["tenant_phone"], entry["tenant_email"]] + info = [ + entry["serial"], + entry["tenant_name"], + entry["tenant_id"], + entry["tenant_phone"], + entry["tenant_email"], + ] elif event == "ADMIN_MODIFY_DB": - info = [entry["admin_name"], entry["serial"], entry["column"], entry["past_value"], entry["new_value"]] + info = [ + entry["admin_name"], + entry["serial"], + entry["column"], + entry["past_value"], + entry["new_value"], + ] line.extend(info) line.append(entry["note"]) @@ -122,4 +117,4 @@ class AdminLog: f.close() - self._unlock() + self.lockfile.unlock() diff --git a/jimbrella/database.py b/jimbrella/database.py index 37f6c82..fd8f35c 100644 --- a/jimbrella/database.py +++ b/jimbrella/database.py @@ -1,6 +1,7 @@ import csv import os from datetime import datetime, timedelta +from .lockfile import Lockfile from .utils import human_datetime, human_timedelta from .config import DUE_HOURS from .exceptions import * @@ -43,33 +44,7 @@ class Database: | jForm answer sheet. is blank otherwise. """ self.path = path - - def _lock(self) -> int: - """Create a lockfile for database. - - If the database is called "db.csv", the lockfile, located in the same - directory, would be called "db.csv.lock". - - Returns 1 if the lockfile is successfully created, 0 if the lockfile - already exists (strong hint of unrecovered crash or potential race - condition - caller of this method should abort whatever it is doing) - """ - try: - f = open(self.path + ".lock", "x") - f.close() - return 1 - except FileExistsError: - return 0 - - def _unlock(self) -> None: - """Remove lockfile created by _lock. - - If there is no lockfile, simply ignore. - """ - try: - os.remove(self.path + ".lock") - except FileNotFoundError: - pass + self.lockfile = Lockfile(self.path) def _read(self) -> list: """Deserialize database.""" @@ -106,9 +81,7 @@ class Database: backup = f.read() f.close() - # wait until database is locked for this write - while not self._lock(): - continue + self.lockfile.lock() f = open(self.path, "w") try: @@ -140,7 +113,7 @@ class Database: f.close() raise e - self._unlock() + self.lockfile.unlock() def _update(self, update: dict) -> list: """Update status of one umbrella, and return the entire updated database.""" diff --git a/jimbrella/lockfile.py b/jimbrella/lockfile.py new file mode 100644 index 0000000..b898142 --- /dev/null +++ b/jimbrella/lockfile.py @@ -0,0 +1,33 @@ +import os + + +class Lockfile: + """Prevent unwanted concurrency for file I/O. + + For a file named "<file>", create or remove a lockfile named "<file>.lock". + + When a process is modifying the file, call `lock(). When the modification + is done, call `unlock()`. + """ + + def __init__(self, filepath): + self.filepath = filepath + self.lockpath = filepath + ".lock" + + def lock(self): + """Continue attempting to lock until locked.""" + locked = False + while not locked: + try: + f = open(self.lockpath, "x") + f.close() + locked = True + except FileExistsError: + continue + + def unlock(self): + """Remove lockfile.""" + try: + os.remove(self.lockpath) + except FileNotFoundError: + return |