update_manager: use a lock to prevent concurrent update and refresh requests

This should reduce the load on low perfomance devices.  This also makes it possible to queue update requests.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Arksine 2021-01-02 14:30:40 -05:00
parent 1e81297624
commit ea1ad0277d
1 changed files with 40 additions and 21 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, Condition from tornado.locks import Event, Condition, Lock
MOONRAKER_PATH = os.path.normpath(os.path.join( MOONRAKER_PATH = os.path.normpath(os.path.join(
os.path.dirname(__file__), "../..")) os.path.dirname(__file__), "../.."))
@ -58,6 +58,8 @@ class UpdateManager:
self.gh_limit_remaining = None self.gh_limit_remaining = None
self.gh_limit_reset_time = None self.gh_limit_reset_time = None
self.gh_init_evt = Event() self.gh_init_evt = Event()
self.cmd_request_lock = Lock()
self.is_refreshing = False
self.server.register_endpoint( self.server.register_endpoint(
"/machine/update/moonraker", ["POST"], "/machine/update/moonraker", ["POST"],
@ -98,33 +100,50 @@ class UpdateManager:
async def _handle_update_request(self, web_request): async def _handle_update_request(self, web_request):
app = web_request.get_endpoint().split("/")[-1] app = web_request.get_endpoint().split("/")[-1]
inc_deps = web_request.get_boolean('include_deps', False) inc_deps = web_request.get_boolean('include_deps', False)
if self.current_update: if self.current_update is not None and \
raise self.server.error("A current update is in progress") self.current_update[0] == app:
return f"Object {app} is currently being updated"
updater = self.updaters.get(app, None) updater = self.updaters.get(app, None)
if updater is None: if updater is None:
raise self.server.error(f"Updater {app} not available") raise self.server.error(f"Updater {app} not available")
self.current_update = (app, id(web_request)) async with self.cmd_request_lock:
try: self.current_update = (app, id(web_request))
await updater.update(inc_deps) try:
except Exception: await updater.update(inc_deps)
self.current_update = None except Exception:
raise raise
self.current_update = None finally:
self.current_update = None
return "ok" return "ok"
async def _handle_status_request(self, web_request): async def _handle_status_request(self, web_request):
refresh = web_request.get_boolean('refresh', False) check_refresh = web_request.get_boolean('refresh', False)
# Don't refresh if an update is currently in progress,
# just return current state
check_refresh &= self.current_update is None
need_refresh = False
if check_refresh:
# If there is an outstanding request processing a
# refresh, we don't need to do it again.
need_refresh = not self.is_refreshing
await self.cmd_request_lock.acquire()
self.is_refreshing = True
vinfo = {} vinfo = {}
for name, updater in list(self.updaters.items()): try:
await updater.check_initialized(120.) for name, updater in list(self.updaters.items()):
cur_update = self.current_update or ("", -1) await updater.check_initialized(120.)
# Do not refresh if the current update is in progress if need_refresh:
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 if hasattr(updater, "get_update_status"):
if hasattr(updater, "get_update_status"): vinfo[name] = updater.get_update_status()
vinfo[name] = updater.get_update_status() except Exception:
raise
finally:
if check_refresh:
self.is_refreshing = False
self.cmd_request_lock.release()
return { return {
'version_info': vinfo, 'version_info': vinfo,
'github_rate_limit': self.gh_rate_limit, 'github_rate_limit': self.gh_rate_limit,