implement lazy multisave saving using a daemon thread

This commit is contained in:
Fabian Dill 2020-06-20 15:46:33 +02:00
parent 01d793bc00
commit 7e3ee8101f
2 changed files with 59 additions and 21 deletions

1
.gitignore vendored
View File

@ -25,3 +25,4 @@ weights/
/logs/ /logs/
_persistent_storage.yaml _persistent_storage.yaml
mystery_result_*.yaml mystery_result_*.yaml
/db.db3

View File

@ -11,6 +11,7 @@ import typing
import inspect import inspect
import weakref import weakref
import datetime import datetime
import threading
import ModuleUpdate import ModuleUpdate
@ -94,14 +95,17 @@ class Context(Node):
self.commandprocessor = ServerCommandProcessor(self) self.commandprocessor = ServerCommandProcessor(self)
self.embedded_blacklist = {"host", "port"} self.embedded_blacklist = {"host", "port"}
self.client_ids: typing.Dict[typing.Tuple[int, int], datetime.datetime] = {} self.client_ids: typing.Dict[typing.Tuple[int, int], datetime.datetime] = {}
self.auto_save_interval = 60 # in seconds
self.auto_saver_thread = None
self.save_dirty = False
def load(self, multidatapath: str, use_embedded_server_options: bool = False): def load(self, multidatapath: str, use_embedded_server_options: bool = False):
with open(multidatapath, 'rb') as f: with open(multidatapath, 'rb') as f:
self._load(f, use_embedded_server_options) self._load(json.loads(zlib.decompress(f.read()).decode("utf-8-sig")),
use_embedded_server_options)
self.data_filename = multidatapath self.data_filename = multidatapath
def _load(self, fileobj, use_embedded_server_options: bool): def _load(self, jsonobj: dict, use_embedded_server_options: bool):
jsonobj = json.loads(zlib.decompress(fileobj.read()).decode("utf-8-sig"))
for team, names in enumerate(jsonobj['names']): for team, names in enumerate(jsonobj['names']):
for player, name in enumerate(names, 1): for player, name in enumerate(names, 1):
self.player_names[(team, player)] = name self.player_names[(team, player)] = name
@ -126,6 +130,28 @@ class Context(Node):
setattr(self, key, value) setattr(self, key, value)
self.item_cheat = not server_options.get("disable_item_cheat", True) self.item_cheat = not server_options.get("disable_item_cheat", True)
def save(self, now=False) -> bool:
if self.saving:
if now:
self.save_dirty = False
return self._save()
self.save_dirty = True
return True
return False
def _save(self) -> bool:
try:
jsonstr = json.dumps(self.get_save())
with open(self.save_filename, "wb") as f:
f.write(zlib.compress(jsonstr.encode("utf-8")))
except Exception as e:
logging.exception(e)
return False
else:
return True
def init_save(self, enabled: bool = True): def init_save(self, enabled: bool = True):
self.saving = enabled self.saving = enabled
if self.saving: if self.saving:
@ -141,6 +167,22 @@ class Context(Node):
except Exception as e: except Exception as e:
logging.exception(e) logging.exception(e)
if not self.auto_saver_thread:
def save_regularly():
import time
while self.running:
time.sleep(self.auto_save_interval)
if self.save_dirty:
logging.debug("Saving multisave via thread.")
self.save_dirty = False
self._save()
self.auto_saver_thread = threading.Thread(target=save_regularly, daemon=True)
self.auto_saver_thread.start()
import atexit
atexit.register(self._save) # make sure we save on exit too
def get_save(self) -> dict: def get_save(self) -> dict:
d = { d = {
"rom_names": list(self.rom_names.items()), "rom_names": list(self.rom_names.items()),
@ -407,7 +449,7 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations):
for client in ctx.endpoints: for client in ctx.endpoints:
if client.team == team and client.slot == slot: if client.team == team and client.slot == slot:
asyncio.create_task(ctx.send_msgs(client, [["HintPointUpdate", (get_client_points(ctx, client),)]])) asyncio.create_task(ctx.send_msgs(client, [["HintPointUpdate", (get_client_points(ctx, client),)]]))
save(ctx) ctx.save()
def notify_team(ctx: Context, team: int, text: str): def notify_team(ctx: Context, team: int, text: str):
@ -415,15 +457,6 @@ def notify_team(ctx: Context, team: int, text: str):
ctx.broadcast_team(team, [['Print', text]]) ctx.broadcast_team(team, [['Print', text]])
def save(ctx: Context):
if ctx.saving:
try:
jsonstr = json.dumps(ctx.get_save())
with open(ctx.save_filename, "wb") as f:
f.write(zlib.compress(jsonstr.encode("utf-8")))
except Exception as e:
logging.exception(e)
def collect_hints(ctx: Context, team: int, slot: int, item: str) -> typing.List[Utils.Hint]: def collect_hints(ctx: Context, team: int, slot: int, item: str) -> typing.List[Utils.Hint]:
hints = [] hints = []
@ -683,13 +716,13 @@ class ClientMessageProcessor(CommandProcessor):
self.ctx.name_aliases[self.client.team, self.client.slot] = alias_name self.ctx.name_aliases[self.client.team, self.client.slot] = alias_name
self.output(f"Hello, {alias_name}") self.output(f"Hello, {alias_name}")
update_aliases(self.ctx, self.client.team) update_aliases(self.ctx, self.client.team)
save(self.ctx) self.ctx.save()
return True return True
elif (self.client.team, self.client.slot) in self.ctx.name_aliases: elif (self.client.team, self.client.slot) in self.ctx.name_aliases:
del (self.ctx.name_aliases[self.client.team, self.client.slot]) del (self.ctx.name_aliases[self.client.team, self.client.slot])
self.output("Removed Alias") self.output("Removed Alias")
update_aliases(self.ctx, self.client.team) update_aliases(self.ctx, self.client.team)
save(self.ctx) self.ctx.save()
return True return True
return False return False
@ -776,7 +809,7 @@ class ClientMessageProcessor(CommandProcessor):
self.output( self.output(
"Could not pay for everything. Rerun the hint later with more points to get the remaining hints.") "Could not pay for everything. Rerun the hint later with more points to get the remaining hints.")
notify_hints(self.ctx, self.client.team, found_hints + hints) notify_hints(self.ctx, self.client.team, found_hints + hints)
save(self.ctx) self.ctx.save()
return True return True
else: else:
@ -938,9 +971,13 @@ class ServerCommandProcessor(CommandProcessor):
def _cmd_save(self) -> bool: def _cmd_save(self) -> bool:
"""Save current state to multidata""" """Save current state to multidata"""
save(self.ctx) if self.ctx.saving:
self.output("Game saved") self.ctx.save(True)
return True self.output("Game saved")
return True
else:
self.output("Saving is disabled.")
return False
def _cmd_players(self) -> bool: def _cmd_players(self) -> bool:
"""Get information about connected players""" """Get information about connected players"""
@ -968,13 +1005,13 @@ class ServerCommandProcessor(CommandProcessor):
self.ctx.name_aliases[team, slot] = alias_name self.ctx.name_aliases[team, slot] = alias_name
self.output(f"Named {player_name} as {alias_name}") self.output(f"Named {player_name} as {alias_name}")
update_aliases(self.ctx, team) update_aliases(self.ctx, team)
save(self.ctx) self.ctx.save()
return True return True
else: else:
del (self.ctx.name_aliases[team, slot]) del (self.ctx.name_aliases[team, slot])
self.output(f"Removed Alias for {player_name}") self.output(f"Removed Alias for {player_name}")
update_aliases(self.ctx, team) update_aliases(self.ctx, team)
save(self.ctx) self.ctx.save()
return True return True
else: else:
self.output(response) self.output(response)