Critical tasks now cause the app to exit, instead of letting it continue with broken functionality
This commit is contained in:
1 parent
f32c8f25d8
commit
016955c8ba
3 files changed
+25
-8
No files matched your search
@@ -849,7 +849,7 @@ class Twitch:
|
|||||||
with suppress(asyncio.TimeoutError):
|
with suppress(asyncio.TimeoutError):
|
||||||
await asyncio.wait_for(self._watching_restart.wait(), timeout=delay)
|
await asyncio.wait_for(self._watching_restart.wait(), timeout=delay)
|
||||||
|
|
||||||
@task_wrapper
|
@task_wrapper(critical=True)
|
||||||
async def _watch_loop(self) -> NoReturn:
|
async def _watch_loop(self) -> NoReturn:
|
||||||
interval: float = WATCH_INTERVAL.total_seconds()
|
interval: float = WATCH_INTERVAL.total_seconds()
|
||||||
while True:
|
while True:
|
||||||
@@ -898,7 +898,7 @@ class Twitch:
|
|||||||
logger.log(CALL, "No active drop could be determined")
|
logger.log(CALL, "No active drop could be determined")
|
||||||
await self._watch_sleep(interval)
|
await self._watch_sleep(interval)
|
||||||
|
|
||||||
@task_wrapper
|
@task_wrapper(critical=True)
|
||||||
async def _maintenance_task(self) -> None:
|
async def _maintenance_task(self) -> None:
|
||||||
claim_period = timedelta(minutes=30)
|
claim_period = timedelta(minutes=30)
|
||||||
max_period = timedelta(hours=1)
|
max_period = timedelta(hours=1)
|
||||||
@@ -911,10 +911,9 @@ class Twitch:
|
|||||||
break
|
break
|
||||||
next_trigger = min(now + claim_period, next_period)
|
next_trigger = min(now + claim_period, next_period)
|
||||||
trigger_cleanup = False
|
trigger_cleanup = False
|
||||||
while self._mnt_triggers and (switch_trigger := self._mnt_triggers[0]) <= next_trigger:
|
while self._mnt_triggers and self._mnt_triggers[0] <= next_trigger:
|
||||||
|
next_trigger = self._mnt_triggers.popleft()
|
||||||
trigger_cleanup = True
|
trigger_cleanup = True
|
||||||
self._mnt_triggers.popleft()
|
|
||||||
next_trigger = switch_trigger
|
|
||||||
if next_trigger == next_period:
|
if next_trigger == next_period:
|
||||||
trigger_type: str = "Reload"
|
trigger_type: str = "Reload"
|
||||||
elif trigger_cleanup:
|
elif trigger_cleanup:
|
||||||
@@ -943,7 +942,7 @@ class Twitch:
|
|||||||
await watching_channel.claim_bonus()
|
await watching_channel.claim_bonus()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # we intentionally silently skip anything else
|
pass # we intentionally silently skip anything else
|
||||||
# this triggers this task restart every (up to) 60 minutes
|
# this triggers a restart of this task every (up to) 60 minutes
|
||||||
logger.log(CALL, "Maintenance task requests a reload")
|
logger.log(CALL, "Maintenance task requests a reload")
|
||||||
self.change_state(State.INVENTORY_FETCH)
|
self.change_state(State.INVENTORY_FETCH)
|
||||||
|
|
||||||
|
|||||||
@@ -131,8 +131,11 @@ def deduplicate(iterable: abc.Iterable[_T]) -> list[_T]:
|
|||||||
|
|
||||||
|
|
||||||
def task_wrapper(
|
def task_wrapper(
|
||||||
|
afunc: abc.Callable[_P, abc.Coroutine[Any, Any, _T]] | None = None, *, critical: bool = False
|
||||||
|
):
|
||||||
|
def decorator(
|
||||||
afunc: abc.Callable[_P, abc.Coroutine[Any, Any, _T]]
|
afunc: abc.Callable[_P, abc.Coroutine[Any, Any, _T]]
|
||||||
) -> abc.Callable[_P, abc.Coroutine[Any, Any, _T]]:
|
) -> abc.Callable[_P, abc.Coroutine[Any, Any, _T]]:
|
||||||
@wraps(afunc)
|
@wraps(afunc)
|
||||||
async def wrapper(*args: _P.args, **kwargs: _P.kwargs):
|
async def wrapper(*args: _P.args, **kwargs: _P.kwargs):
|
||||||
try:
|
try:
|
||||||
@@ -141,8 +144,23 @@ def task_wrapper(
|
|||||||
pass
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception(f"Exception in {afunc.__name__} task")
|
logger.exception(f"Exception in {afunc.__name__} task")
|
||||||
|
if critical:
|
||||||
|
# critical task's death should trigger a termination.
|
||||||
|
# there isn't an easy and sure way to obtain the Twitch instance here,
|
||||||
|
# but we can improvise finding it
|
||||||
|
from twitch import Twitch # cyclic import
|
||||||
|
probe = args and args[0] or None # extract from 'self' arg
|
||||||
|
if isinstance(probe, Twitch):
|
||||||
|
probe.close()
|
||||||
|
elif probe is not None:
|
||||||
|
probe = getattr(probe, "_twitch", None) # extract from '_twitch' attr
|
||||||
|
if isinstance(probe, Twitch):
|
||||||
|
probe.close()
|
||||||
raise # raise up to the wrapping task
|
raise # raise up to the wrapping task
|
||||||
return wrapper
|
return wrapper
|
||||||
|
if afunc is None:
|
||||||
|
return decorator
|
||||||
|
return decorator(afunc)
|
||||||
|
|
||||||
|
|
||||||
def invalidate_cache(instance, *attrnames):
|
def invalidate_cache(instance, *attrnames):
|
||||||
|
|||||||
+1
-1
@@ -141,7 +141,7 @@ class Websocket:
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
@task_wrapper
|
@task_wrapper(critical=True)
|
||||||
async def _handle(self):
|
async def _handle(self):
|
||||||
# ensure we're logged in before connecting
|
# ensure we're logged in before connecting
|
||||||
self.set_status(_("gui", "websocket", "initializing"))
|
self.set_status(_("gui", "websocket", "initializing"))
|
||||||
|
|||||||
Reference in new issue
Block a user