update_manager: resolve potential rentry issues

Calls to "refresh" should not be allowed to occur while a client a current refresh is in progress.  Updates will wait for a pending refresh to complete before beginning the update procedure.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Arksine 2021-01-02 08:43:36 -05:00
parent ae3661b100
commit 1e81297624
1 changed files with 58 additions and 13 deletions

View File

@ -16,7 +16,7 @@ import time
import tornado.gen import tornado.gen
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
from tornado.httpclient import AsyncHTTPClient from tornado.httpclient import AsyncHTTPClient
from tornado.locks import Event from tornado.locks import Event, Condition
MOONRAKER_PATH = os.path.normpath(os.path.join( MOONRAKER_PATH = os.path.normpath(os.path.join(
os.path.dirname(__file__), "../..")) os.path.dirname(__file__), "../.."))
@ -115,9 +115,11 @@ class UpdateManager:
async def _handle_status_request(self, web_request): async def _handle_status_request(self, web_request):
refresh = web_request.get_boolean('refresh', False) refresh = web_request.get_boolean('refresh', False)
vinfo = {} vinfo = {}
for name, updater in self.updaters.items(): for name, updater in list(self.updaters.items()):
await updater.check_initialized(120.) await updater.check_initialized(120.)
if refresh: cur_update = self.current_update or ("", -1)
# Do not refresh if the current update is in progress
if refresh and name != cur_update[0]:
ret = updater.refresh() ret = updater.refresh()
if asyncio.iscoroutine(ret): if asyncio.iscoroutine(ret):
await ret await ret
@ -291,6 +293,7 @@ class GitUpdater:
self.version = self.cur_hash = "?" self.version = self.cur_hash = "?"
self.remote_version = self.remote_hash = "?" self.remote_version = self.remote_hash = "?"
self.init_evt = Event() self.init_evt = Event()
self.refresh_condition = None
self.debug = umgr.repo_debug self.debug = umgr.repo_debug
self.remote = "origin" self.remote = "origin"
self.branch = "master" self.branch = "master"
@ -341,8 +344,19 @@ class GitUpdater:
await self.init_evt.wait(timeout) await self.init_evt.wait(timeout)
async def refresh(self): async def refresh(self):
await self._check_version() if self.refresh_condition is None:
self.init_evt.set() self.refresh_condition = Condition()
else:
self.refresh_condition.wait()
return
try:
await self._check_version()
except Exception:
logging.exception("Error Refreshing git state")
else:
self.init_evt.set()
self.refresh_condition.notify_all()
self.refresh_condition = None
async def _check_version(self, need_fetch=True): async def _check_version(self, need_fetch=True):
self.is_valid = self.detached = False self.is_valid = self.detached = False
@ -434,6 +448,9 @@ class GitUpdater:
f"{self.remote}/{self.branch}") f"{self.remote}/{self.branch}")
async def update(self, update_deps=False): async def update(self, update_deps=False):
await self.check_initialized(20.)
if self.refresh_condition is not None:
self.refresh_condition.wait()
if not self.is_valid: if not self.is_valid:
raise self._log_exc("Update aborted, repo is not valid", False) raise self._log_exc("Update aborted, repo is not valid", False)
if self.is_dirty: if self.is_dirty:
@ -592,17 +609,20 @@ class PackageUpdater:
self.notify_update_response = umgr.notify_update_response self.notify_update_response = umgr.notify_update_response
self.available_packages = [] self.available_packages = []
self.init_evt = Event() self.init_evt = Event()
self.refresh_condition = None
IOLoop.current().spawn_callback(self.refresh) IOLoop.current().spawn_callback(self.refresh)
async def refresh(self): async def refresh(self):
# TODO: Use python-apt python lib rather than command line for updates # TODO: Use python-apt python lib rather than command line for updates
if self.refresh_condition is None:
self.refresh_condition = Condition()
else:
self.refresh_condition.wait()
return
try: try:
await self.execute_cmd(f"{APT_CMD} update", timeout=300.) await self.execute_cmd(f"{APT_CMD} update", timeout=300.)
res = await self.execute_cmd_with_response( res = await self.execute_cmd_with_response(
"apt list --upgradable") "apt list --upgradable")
except Exception:
logging.exception("Error Refreshing System Packages")
else:
pkg_list = [p.strip() for p in res.split("\n") if p.strip()] pkg_list = [p.strip() for p in res.split("\n") if p.strip()]
if pkg_list: if pkg_list:
pkg_list = pkg_list[2:] pkg_list = pkg_list[2:]
@ -612,7 +632,12 @@ class PackageUpdater:
logging.info( logging.info(
f"Detected {len(self.available_packages)} package updates:" f"Detected {len(self.available_packages)} package updates:"
f"\n{pkg_list}") f"\n{pkg_list}")
self.init_evt.set() except Exception:
logging.exception("Error Refreshing System Packages")
else:
self.init_evt.set()
self.refresh_condition.notify_all()
self.refresh_condition = None
async def check_initialized(self, timeout=None): async def check_initialized(self, timeout=None):
if self.init_evt.is_set(): if self.init_evt.is_set():
@ -622,6 +647,9 @@ class PackageUpdater:
await self.init_evt.wait(timeout) await self.init_evt.wait(timeout)
async def update(self, *args): async def update(self, *args):
await self.check_initialized(20.)
if self.refresh_condition is not None:
self.refresh_condition.wait()
self.notify_update_response("Updating packages...") self.notify_update_response("Updating packages...")
try: try:
await self.execute_cmd( await self.execute_cmd(
@ -651,6 +679,7 @@ class ClientUpdater:
self.version = self.remote_version = self.dl_url = "?" self.version = self.remote_version = self.dl_url = "?"
self.etag = None self.etag = None
self.init_evt = Event() self.init_evt = Event()
self.refresh_condition = None
self._get_local_version() self._get_local_version()
logging.info(f"\nInitializing Client Updater: '{self.name}'," logging.info(f"\nInitializing Client Updater: '{self.name}',"
f"\nversion: {self.version}" f"\nversion: {self.version}"
@ -672,9 +701,22 @@ class ClientUpdater:
await self.init_evt.wait(timeout) await self.init_evt.wait(timeout)
async def refresh(self): async def refresh(self):
# Local state if self.refresh_condition is None:
self._get_local_version() self.refresh_condition = Condition()
else:
self.refresh_condition.wait()
return
try:
self._get_local_version()
await self._get_remote_version()
except Exception:
logging.exception("Error Refreshing Client")
else:
self.init_evt.set()
self.refresh_condition.notify_all()
self.refresh_condition = None
async def _get_remote_version(self):
# Remote state # Remote state
url = f"https://api.github.com/repos/{self.repo}/releases/latest" url = f"https://api.github.com/repos/{self.repo}/releases/latest"
try: try:
@ -684,7 +726,7 @@ class ClientUpdater:
result = {} result = {}
if result is None: if result is None:
# No change, update not necessary # No change, update not necessary
return None return
self.etag = result.get('etag', None) self.etag = result.get('etag', None)
self.remote_version = result.get('name', "?") self.remote_version = result.get('name', "?")
release_assets = result.get('assets', [{}])[0] release_assets = result.get('assets', [{}])[0]
@ -694,9 +736,12 @@ class ClientUpdater:
f"Local Version: {self.version}\n" f"Local Version: {self.version}\n"
f"Remote Version: {self.remote_version}\n" f"Remote Version: {self.remote_version}\n"
f"url: {self.dl_url}") f"url: {self.dl_url}")
self.init_evt.set()
async def update(self, *args): async def update(self, *args):
await self.check_initialized(20.)
if self.refresh_condition is not None:
# wait for refresh if in progess
self.refresh_condition.wait()
if self.remote_version == "?": if self.remote_version == "?":
await self.refresh() await self.refresh()
if self.remote_version == "?": if self.remote_version == "?":