add a fairly basic file locking mechanism with src/LockFile.py
closes #96
This commit is contained in:
parent
0331b763ff
commit
2c19bdb949
4 changed files with 57 additions and 6 deletions
|
@ -36,7 +36,7 @@ class ListLambdaPollHook(PollHook.PollHook):
|
||||||
|
|
||||||
class Bot(object):
|
class Bot(object):
|
||||||
def __init__(self, directory, args, cache, config, database, events,
|
def __init__(self, directory, args, cache, config, database, events,
|
||||||
exports, log, modules, timers):
|
exports, log, modules, timers, lock_file):
|
||||||
self.directory = directory
|
self.directory = directory
|
||||||
self.args = args
|
self.args = args
|
||||||
self.cache = cache
|
self.cache = cache
|
||||||
|
@ -71,6 +71,7 @@ class Bot(object):
|
||||||
self._poll_timeouts = [] # typing.List[PollHook]
|
self._poll_timeouts = [] # typing.List[PollHook]
|
||||||
self._poll_timeouts.append(self._timers)
|
self._poll_timeouts.append(self._timers)
|
||||||
self._poll_timeouts.append(self.cache)
|
self._poll_timeouts.append(self.cache)
|
||||||
|
self._poll_timeouts.append(lock_file)
|
||||||
|
|
||||||
self._poll_timeouts.append(ListLambdaPollHook(
|
self._poll_timeouts.append(ListLambdaPollHook(
|
||||||
lambda: self.servers.values(),
|
lambda: self.servers.values(),
|
||||||
|
|
40
src/LockFile.py
Normal file
40
src/LockFile.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import datetime, os
|
||||||
|
from src import PollHook, utils
|
||||||
|
|
||||||
|
EXPIRATION = 60 # 1 minute
|
||||||
|
|
||||||
|
class LockFile(PollHook.PollHook):
|
||||||
|
def __init__(self, database_location: str):
|
||||||
|
self._database_location = database_location
|
||||||
|
self._lock_location = "%s.lock" % database_location
|
||||||
|
self._next_lock = None
|
||||||
|
|
||||||
|
def available(self):
|
||||||
|
now = utils.datetime_utcnow()
|
||||||
|
if os.path.exists(self._lock_location):
|
||||||
|
with open(self._lock_location, "r") as lock_file:
|
||||||
|
timestamp_str = lock_file.read().strip().split(" ", 1)[0]
|
||||||
|
|
||||||
|
timestamp = utils.iso8601_parse(timestamp_str)
|
||||||
|
|
||||||
|
if (now-timestamp).total_seconds() < EXPIRATION:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def lock(self):
|
||||||
|
with open(self._lock_location, "w") as lock_file:
|
||||||
|
last_lock = utils.datetime_utcnow()
|
||||||
|
lock_file.write("%s" % utils.iso8601_format(last_lock))
|
||||||
|
self._next_lock = last_lock+datetime.timedelta(
|
||||||
|
seconds=EXPIRATION/2)
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
return max(0, (self._next_lock-utils.datetime_utcnow()).total_seconds())
|
||||||
|
def call(self):
|
||||||
|
if self.next() == 0:
|
||||||
|
self.lock()
|
||||||
|
|
||||||
|
def unlock(self):
|
||||||
|
if os.path.isfile(self._lock_location):
|
||||||
|
os.remove(self._lock_location)
|
|
@ -15,6 +15,9 @@ ISO8601_FORMAT_TZ = "%z"
|
||||||
|
|
||||||
DATETIME_HUMAN = "%Y/%m/%d %H:%M:%S"
|
DATETIME_HUMAN = "%Y/%m/%d %H:%M:%S"
|
||||||
|
|
||||||
|
def datetime_utcnow() -> datetime.datetime:
|
||||||
|
return datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
|
||||||
|
|
||||||
def iso8601_format(dt: datetime.datetime, milliseconds: bool=False) -> str:
|
def iso8601_format(dt: datetime.datetime, milliseconds: bool=False) -> str:
|
||||||
dt_format = dt.strftime(ISO8601_FORMAT_DT)
|
dt_format = dt.strftime(ISO8601_FORMAT_DT)
|
||||||
tz_format = dt.strftime(ISO8601_FORMAT_TZ)
|
tz_format = dt.strftime(ISO8601_FORMAT_TZ)
|
||||||
|
@ -25,8 +28,7 @@ def iso8601_format(dt: datetime.datetime, milliseconds: bool=False) -> str:
|
||||||
|
|
||||||
return "%s%s%s" % (dt_format, ms_format, tz_format)
|
return "%s%s%s" % (dt_format, ms_format, tz_format)
|
||||||
def iso8601_format_now(milliseconds: bool=False) -> str:
|
def iso8601_format_now(milliseconds: bool=False) -> str:
|
||||||
now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
|
return iso8601_format(datetime_utcnow(), milliseconds=milliseconds)
|
||||||
return iso8601_format(now, milliseconds=milliseconds)
|
|
||||||
def iso8601_parse(s: str, microseconds: bool=False) -> datetime.datetime:
|
def iso8601_parse(s: str, microseconds: bool=False) -> datetime.datetime:
|
||||||
fmt = ISO8601_PARSE_MICROSECONDS if microseconds else ISO8601_PARSE
|
fmt = ISO8601_PARSE_MICROSECONDS if microseconds else ISO8601_PARSE
|
||||||
return datetime.datetime.strptime(s, fmt)
|
return datetime.datetime.strptime(s, fmt)
|
||||||
|
|
14
start.py
14
start.py
|
@ -6,9 +6,9 @@ if sys.version_info < (3, 6):
|
||||||
sys.stderr.write("BitBot requires python 3.6.0 or later\n")
|
sys.stderr.write("BitBot requires python 3.6.0 or later\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
import argparse, faulthandler, os, platform, time
|
import atexit, argparse, faulthandler, os, platform, time
|
||||||
from src import Cache, Config, Database, EventManager, Exports, IRCBot
|
from src import Cache, Config, Database, EventManager, Exports, IRCBot
|
||||||
from src import Logging, ModuleManager, Timers, utils
|
from src import LockFile, Logging, ModuleManager, Timers, utils
|
||||||
|
|
||||||
faulthandler.enable()
|
faulthandler.enable()
|
||||||
|
|
||||||
|
@ -65,6 +65,14 @@ log = Logging.Log(not args.no_logging, log_level, args.log_dir)
|
||||||
log.info("Starting BitBot %s (Python v%s)",
|
log.info("Starting BitBot %s (Python v%s)",
|
||||||
[IRCBot.VERSION, platform.python_version()])
|
[IRCBot.VERSION, platform.python_version()])
|
||||||
|
|
||||||
|
lock_file = LockFile.LockFile(args.database)
|
||||||
|
if not lock_file.available():
|
||||||
|
log.critical("Database is locked. Is BitBot already running?")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
atexit.register(lock_file.unlock)
|
||||||
|
lock_file.lock()
|
||||||
|
|
||||||
database = Database.Database(log, args.database)
|
database = Database.Database(log, args.database)
|
||||||
|
|
||||||
if args.remove_server:
|
if args.remove_server:
|
||||||
|
@ -98,7 +106,7 @@ modules = ModuleManager.ModuleManager(events, exports, timers, config, log,
|
||||||
module_directories)
|
module_directories)
|
||||||
|
|
||||||
bot = IRCBot.Bot(directory, args, cache, config, database, events,
|
bot = IRCBot.Bot(directory, args, cache, config, database, events,
|
||||||
exports, log, modules, timers)
|
exports, log, modules, timers, lock_file)
|
||||||
|
|
||||||
if args.module:
|
if args.module:
|
||||||
definition = modules.find_module(args.module)
|
definition = modules.find_module(args.module)
|
||||||
|
|
Loading…
Reference in a new issue