implement lazy multisave saving using a daemon thread
This commit is contained in:
parent
01d793bc00
commit
7e3ee8101f
|
@ -25,3 +25,4 @@ weights/
|
||||||
/logs/
|
/logs/
|
||||||
_persistent_storage.yaml
|
_persistent_storage.yaml
|
||||||
mystery_result_*.yaml
|
mystery_result_*.yaml
|
||||||
|
/db.db3
|
||||||
|
|
|
@ -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.ctx.save(True)
|
||||||
self.output("Game saved")
|
self.output("Game saved")
|
||||||
return True
|
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)
|
||||||
|
|
Loading…
Reference in New Issue