659 lines
21 KiB
Python
659 lines
21 KiB
Python
"""
|
|
tasks.py
|
|
|
|
Scheduler definitions using discord.ext.tasks loops.
|
|
Thin wrappers that invoke the corresponding execute_* functions from task_executors.py
|
|
on configured intervals.
|
|
"""
|
|
|
|
# Standard Library Imports
|
|
import asyncio
|
|
import os
|
|
import json
|
|
import logging
|
|
import shutil
|
|
import time
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
|
|
# Third-Party Library Imports
|
|
import aiohttp
|
|
from discord.ext import tasks
|
|
|
|
# Local Module Imports
|
|
from . import lux_apis
|
|
from .autologging import handle_ws_replays, handle_gob_message
|
|
from .health import get_recent_ttl_stats, record_task_run, write_heartbeat
|
|
from .meta_manager import process_all_players, sync_all_guild_metas
|
|
from .task_executors import (
|
|
execute_ldb_alarm_task,
|
|
execute_points_alarm_task,
|
|
execute_leave_alarm_task,
|
|
execute_squadron_stats_tracker,
|
|
execute_update_squadrons_db_task,
|
|
execute_sync_squadron_members_bulk,
|
|
execute_sync_squadron_members_points_loop,
|
|
execute_cleanup_stale_squadrons,
|
|
execute_weekly_br_report_task,
|
|
init_squadrons_points_table,
|
|
cleanup_replays,
|
|
)
|
|
from .utils import (
|
|
get_bot,
|
|
STACKS_DIR,
|
|
refresh_entitled_guilds,
|
|
SQB_STATS_TRACKER_WINDOWS,
|
|
SQB_BOUNDARY_TIMES,
|
|
)
|
|
|
|
|
|
async def _record(task_name: str, success: bool, error: str = ""):
|
|
"""Record task execution for health dashboard."""
|
|
await record_task_run(task_name, success, error)
|
|
|
|
|
|
def _format_duration(seconds: int) -> str:
|
|
minutes, rem = divmod(int(seconds), 60)
|
|
return f"{minutes}m {rem:02d}s"
|
|
|
|
|
|
async def _send_ttl_alert_webhook(content: str) -> bool:
|
|
webhook_url = os.environ.get("SREBOT_TTL_ALERT_WEBHOOK_URL", "").strip()
|
|
if not webhook_url:
|
|
logging.warning("[TTL-ALERT] SREBOT_TTL_ALERT_WEBHOOK_URL is not configured.")
|
|
return False
|
|
|
|
timeout = aiohttp.ClientTimeout(total=15)
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
async with session.post(webhook_url, json={"content": content}) as response:
|
|
if 200 <= response.status < 300:
|
|
return True
|
|
body = await response.text()
|
|
logging.error("[TTL-ALERT] Webhook failed with HTTP %s: %s", response.status, body[:500])
|
|
return False
|
|
|
|
|
|
_TTL_ALERT_HIGH_THRESHOLD_SECONDS = 20 * 60
|
|
_TTL_ALERT_RECOVERY_THRESHOLD_SECONDS = 10 * 60
|
|
_TTL_ALERT_HOLD_SECONDS = 10 * 60
|
|
_ttl_alert_high_since: Optional[float] = None
|
|
_ttl_alert_active = False
|
|
|
|
|
|
async def _execute_ttl_alert_check() -> None:
|
|
global _ttl_alert_high_since, _ttl_alert_active
|
|
|
|
stats = await get_recent_ttl_stats(limit=30)
|
|
avg_delay = stats["avg_delay"]
|
|
sample_size = stats["sample_size"]
|
|
last_received_ts = stats["last_received_ts"]
|
|
|
|
if avg_delay is None:
|
|
_ttl_alert_high_since = None
|
|
logging.info("[TTL-ALERT] No TTL data available.")
|
|
return
|
|
|
|
now = time.time()
|
|
if not _ttl_alert_active:
|
|
if avg_delay > _TTL_ALERT_HIGH_THRESHOLD_SECONDS:
|
|
if _ttl_alert_high_since is None:
|
|
_ttl_alert_high_since = now
|
|
logging.warning(
|
|
"[TTL-ALERT] Avg TTL above threshold: %s across %s games.",
|
|
_format_duration(avg_delay),
|
|
sample_size,
|
|
)
|
|
return
|
|
|
|
if now - _ttl_alert_high_since >= _TTL_ALERT_HOLD_SECONDS:
|
|
content = (
|
|
":warning: **SRE Bot TTL alert**\n"
|
|
f"Average TTL across the last {sample_size} games is "
|
|
f"**{_format_duration(avg_delay)}**, and has stayed above "
|
|
f"{_format_duration(_TTL_ALERT_HIGH_THRESHOLD_SECONDS)} for at least "
|
|
f"{_format_duration(_TTL_ALERT_HOLD_SECONDS)}.\n"
|
|
"Historical game messages may be delayed or out of date."
|
|
)
|
|
if last_received_ts:
|
|
content += f"\nLast game received: <t:{int(last_received_ts)}:R>."
|
|
sent = await _send_ttl_alert_webhook(content)
|
|
if sent:
|
|
_ttl_alert_active = True
|
|
logging.warning("[TTL-ALERT] High TTL webhook sent.")
|
|
return
|
|
|
|
_ttl_alert_high_since = None
|
|
return
|
|
|
|
if avg_delay < _TTL_ALERT_RECOVERY_THRESHOLD_SECONDS:
|
|
content = (
|
|
":white_check_mark: **SRE Bot TTL recovered**\n"
|
|
f"Average TTL across the last {sample_size} games is now "
|
|
f"**{_format_duration(avg_delay)}**, below "
|
|
f"{_format_duration(_TTL_ALERT_RECOVERY_THRESHOLD_SECONDS)}. "
|
|
"Back at normal operations."
|
|
)
|
|
if last_received_ts:
|
|
content += f"\nLast game received: <t:{int(last_received_ts)}:R>."
|
|
sent = await _send_ttl_alert_webhook(content)
|
|
if sent:
|
|
_ttl_alert_active = False
|
|
_ttl_alert_high_since = None
|
|
logging.info("[TTL-ALERT] Recovery webhook sent.")
|
|
|
|
|
|
# ============================================================================
|
|
# TTL ALERT TASK
|
|
# ============================================================================
|
|
|
|
@tasks.loop(minutes=10)
|
|
async def ttl_alert_task():
|
|
"""Alert when recent game receive TTL is elevated for a sustained period."""
|
|
try:
|
|
await _execute_ttl_alert_check()
|
|
await _record("ttl_alert", True)
|
|
except Exception as e:
|
|
await _record("ttl_alert", False, str(e))
|
|
logging.error("[TTL-ALERT] Failed to check TTL alert state: %s", e)
|
|
|
|
|
|
@ttl_alert_task.before_loop
|
|
async def before_ttl_alert_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# LEADERBOARD ALARM TASK
|
|
# ============================================================================
|
|
|
|
@tasks.loop(seconds=60)
|
|
async def ldb_alarm_task():
|
|
"""Checks time and triggers leaderboard alarm at 22:35 and 7:35 UTC."""
|
|
now_utc = datetime.now(timezone.utc).time()
|
|
if (now_utc.hour, now_utc.minute) in [(22, 35), (7, 35)]:
|
|
try:
|
|
await execute_ldb_alarm_task()
|
|
await _record("ldb_alarm", True)
|
|
except Exception as e:
|
|
await _record("ldb_alarm", False, str(e))
|
|
raise
|
|
|
|
|
|
@ldb_alarm_task.before_loop
|
|
async def before_ldb_alarm_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# SQUADRON STATS TRACKER TASKS
|
|
# ============================================================================
|
|
|
|
@tasks.loop(hours=1)
|
|
async def squadron_stats_tracker_task():
|
|
"""Track squadron points every hour during SQB timeslot hours."""
|
|
now_utc = datetime.now(timezone.utc).time()
|
|
if any(start <= now_utc <= end for _, start, end in SQB_STATS_TRACKER_WINDOWS):
|
|
try:
|
|
await execute_squadron_stats_tracker()
|
|
await _record("squadron_stats_tracker", True)
|
|
except Exception as e:
|
|
await _record("squadron_stats_tracker", False, str(e))
|
|
raise
|
|
|
|
|
|
@tasks.loop(seconds=60)
|
|
async def squadron_stats_boundary_task():
|
|
"""Track squadron points at timeslot boundaries (see SQB_BOUNDARY_TIMES in utils)."""
|
|
now_utc = datetime.now(timezone.utc)
|
|
if now_utc.time().replace(second=0, microsecond=0) in SQB_BOUNDARY_TIMES:
|
|
try:
|
|
await execute_squadron_stats_tracker()
|
|
await _record("squadron_stats_boundary", True)
|
|
except Exception as e:
|
|
await _record("squadron_stats_boundary", False, str(e))
|
|
raise
|
|
|
|
|
|
@squadron_stats_tracker_task.before_loop
|
|
async def before_squadron_stats_tracker_task():
|
|
await get_bot().wait_until_ready()
|
|
await init_squadrons_points_table()
|
|
|
|
|
|
@squadron_stats_boundary_task.before_loop
|
|
async def before_squadron_stats_boundary_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# ENTITLEMENT CACHE REFRESH (hourly fallback)
|
|
# ============================================================================
|
|
|
|
@tasks.loop(hours=1)
|
|
async def entitlement_cache_task():
|
|
"""Refresh the entitlement cache hourly so new subscriptions are picked up
|
|
even when no autolog batches or points alarms are running."""
|
|
try:
|
|
await refresh_entitled_guilds(force=True)
|
|
await _record("entitlement_cache", True)
|
|
except Exception as e:
|
|
await _record("entitlement_cache", False, str(e))
|
|
raise
|
|
|
|
|
|
@entitlement_cache_task.before_loop
|
|
async def before_entitlement_cache():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# POINTS ALARM TASK
|
|
# ============================================================================
|
|
|
|
@tasks.loop(seconds=60)
|
|
async def points_alarm_task():
|
|
"""Checks time and triggers points alarm at 22:25 and 7:25 UTC."""
|
|
now_utc = datetime.now(timezone.utc).time()
|
|
region = None
|
|
if now_utc.hour == 22 and now_utc.minute == 25:
|
|
region = "EU"
|
|
elif now_utc.hour == 7 and now_utc.minute == 25:
|
|
region = "NA"
|
|
if region:
|
|
try:
|
|
await execute_points_alarm_task(region)
|
|
await _record("points_alarm", True)
|
|
except Exception as e:
|
|
await _record("points_alarm", False, str(e))
|
|
raise
|
|
|
|
|
|
@points_alarm_task.before_loop
|
|
async def before_points_alarm_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# REPLAY CLEANUP TASK
|
|
# ============================================================================
|
|
|
|
@tasks.loop(hours=4)
|
|
async def replay_cleanup_task():
|
|
"""Cleans up replay directories every 4 hours."""
|
|
try:
|
|
await cleanup_replays()
|
|
await _record("replay_cleanup", True)
|
|
except Exception as e:
|
|
await _record("replay_cleanup", False, str(e))
|
|
raise
|
|
|
|
|
|
@replay_cleanup_task.before_loop
|
|
async def before_replay_cleanup_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# LEAVE ALARM TASK
|
|
# ============================================================================
|
|
|
|
@tasks.loop(minutes=30)
|
|
async def leave_alarm_task():
|
|
"""Runs leave alarm every 30 minutes."""
|
|
try:
|
|
await execute_leave_alarm_task()
|
|
await _record("leave_alarm", True)
|
|
except Exception as e:
|
|
await _record("leave_alarm", False, str(e))
|
|
raise
|
|
|
|
|
|
@leave_alarm_task.before_loop
|
|
async def before_leave_alarm_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# UPDATE SQUADRONS DB TASK
|
|
# ============================================================================
|
|
|
|
_startup_db_done = False
|
|
|
|
|
|
@tasks.loop(hours=1)
|
|
async def update_squadrons_db_task():
|
|
"""
|
|
Every 1 hour:
|
|
1. Refresh the main squadrons.db via Game API.
|
|
2. Sync squadron member UIDs for top 1000 squadrons (bulk API).
|
|
3. Update the Bot's status
|
|
"""
|
|
global _startup_db_done
|
|
if not _startup_db_done:
|
|
_startup_db_done = True
|
|
return # Skip — already ran in _startup_heavy_init
|
|
try:
|
|
await execute_update_squadrons_db_task(count=1000)
|
|
await execute_sync_squadron_members_bulk(count=1000)
|
|
await execute_cleanup_stale_squadrons()
|
|
await _record("update_squadrons_db", True)
|
|
except Exception as e:
|
|
await _record("update_squadrons_db", False, str(e))
|
|
raise
|
|
|
|
|
|
|
|
@update_squadrons_db_task.before_loop
|
|
async def before_update_squadrons_db_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# UPDATE META DATA TASK
|
|
# ============================================================================
|
|
|
|
@tasks.loop(hours=12)
|
|
async def update_meta_data_task():
|
|
"""
|
|
Every 12 hours:
|
|
Update all player vehicle data in Meta.db from the game API.
|
|
Skips players that were updated within 2 days.
|
|
"""
|
|
try:
|
|
await process_all_players(
|
|
limit=None,
|
|
skip_existing=True,
|
|
batch_size=50,
|
|
max_concurrent=5
|
|
)
|
|
await _record("update_meta_data", True)
|
|
except Exception as e:
|
|
await _record("update_meta_data", False, str(e))
|
|
logging.error(f"[META-DB] Error during meta data update: {e}")
|
|
|
|
|
|
@update_meta_data_task.before_loop
|
|
async def before_update_meta_data_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# WEBSOCKET AUTOLOG TASK
|
|
# ============================================================================
|
|
|
|
@tasks.loop(count=1)
|
|
async def ws_autolog_task():
|
|
"""
|
|
Single-run task that maintains persistent WebSocket connection.
|
|
Replaces the polling-based auto_logging_task.
|
|
"""
|
|
await lux_apis.ws_replay_listener(handle_ws_replays)
|
|
|
|
|
|
@ws_autolog_task.before_loop
|
|
async def before_ws_autolog():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
@ws_autolog_task.after_loop
|
|
async def after_ws_autolog():
|
|
if ws_autolog_task.failed():
|
|
logging.error("[WS] ws_autolog_task died, restarting in 10s...")
|
|
await asyncio.sleep(10)
|
|
ws_autolog_task.start()
|
|
|
|
|
|
# ============================================================================
|
|
# WEBSOCKET GOB LISTENER TASK
|
|
# ============================================================================
|
|
|
|
@tasks.loop(count=1)
|
|
async def ws_gob_task():
|
|
"""
|
|
Single-run task that maintains persistent WebSocket connection to the GOB endpoint.
|
|
Saves incoming compressed GOB replays to disk for on-demand video generation.
|
|
"""
|
|
await lux_apis.ws_gob_listener(handle_gob_message)
|
|
|
|
|
|
@ws_gob_task.before_loop
|
|
async def before_ws_gob():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
@ws_gob_task.after_loop
|
|
async def after_ws_gob():
|
|
if ws_gob_task.failed():
|
|
logging.error("[GOB] ws_gob_task died, restarting in 10s...")
|
|
await asyncio.sleep(10)
|
|
ws_gob_task.start()
|
|
|
|
|
|
# ============================================================================
|
|
# SQUADRON POINTS CONTINUOUS UPDATER
|
|
# ============================================================================
|
|
|
|
@tasks.loop(count=1)
|
|
async def squadron_points_loop_task():
|
|
"""
|
|
Continuously update squadron member points forever.
|
|
Very slow (3s per clan), runs indefinitely in the background.
|
|
"""
|
|
await execute_sync_squadron_members_points_loop(count=1000, delay=3.0)
|
|
|
|
|
|
@squadron_points_loop_task.before_loop
|
|
async def before_squadron_points_loop():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
@squadron_points_loop_task.after_loop
|
|
async def after_squadron_points_loop():
|
|
if squadron_points_loop_task.failed():
|
|
logging.error("[SQ-POINTS] squadron_points_loop_task died, restarting in 10s...")
|
|
await asyncio.sleep(10)
|
|
squadron_points_loop_task.start()
|
|
|
|
|
|
# ============================================================================
|
|
# STACKS CLEANUP TASK
|
|
# ============================================================================
|
|
|
|
# Fires at 07:30 and 22:30 UTC — 20 minutes after each SQB timeslot ends.
|
|
_STACKS_CLEANUP_TIMES = [(7, 30), (22, 30)]
|
|
|
|
|
|
@tasks.loop(seconds=60)
|
|
async def stacks_cleanup_task():
|
|
"""Wipe all active stacks at the end of each SQB timeslot."""
|
|
now_utc = datetime.now(timezone.utc)
|
|
if (now_utc.hour, now_utc.minute) in _STACKS_CLEANUP_TIMES:
|
|
try:
|
|
if STACKS_DIR.exists():
|
|
shutil.rmtree(STACKS_DIR)
|
|
STACKS_DIR.mkdir(parents=True, exist_ok=True)
|
|
logging.info("[STACKS] Wiped STACKS_DIR at timeslot end.")
|
|
await _record("stacks_cleanup", True)
|
|
except Exception as e:
|
|
await _record("stacks_cleanup", False, str(e))
|
|
logging.error(f"[STACKS] Failed to wipe STACKS_DIR: {e}")
|
|
|
|
|
|
@stacks_cleanup_task.before_loop
|
|
async def before_stacks_cleanup_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# HEALTH HEARTBEAT TASK
|
|
# ============================================================================
|
|
|
|
@tasks.loop(seconds=30)
|
|
async def health_heartbeat_task():
|
|
"""Write bot health snapshot every 30 seconds."""
|
|
await write_heartbeat()
|
|
|
|
|
|
@health_heartbeat_task.before_loop
|
|
async def before_health_heartbeat_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# GUILD META MEMBER SYNC TASK
|
|
# ============================================================================
|
|
|
|
@tasks.loop(hours=24)
|
|
async def sync_guild_metas_task():
|
|
"""
|
|
Every 24 hours: for each configured guild, fetch the current squadron roster
|
|
from the Game API and reconcile Guild_Metas — adding new members and removing
|
|
players who have left the squadron.
|
|
"""
|
|
try:
|
|
await sync_all_guild_metas()
|
|
await _record("sync_guild_metas", True)
|
|
except Exception as e:
|
|
await _record("sync_guild_metas", False, str(e))
|
|
logging.error(f"[META-SYNC] Error during daily guild meta sync: {e}")
|
|
|
|
|
|
@sync_guild_metas_task.before_loop
|
|
async def before_sync_guild_metas_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# WEEKLY BR REPORT TASK
|
|
# ============================================================================
|
|
|
|
# Fires ~10 min after each BR window ends (10 * 60 = 600s).
|
|
_WEEKLY_BR_FIRE_OFFSET_S = 600
|
|
_WEEKLY_BR_FIRE_TOLERANCE_S = 60 # tolerance window for the 60s loop tick
|
|
_SCHEDULE_PATH = Path(__file__).parent / "SCHEDULE.json"
|
|
|
|
|
|
def _just_ended_br_window(now_ts: int) -> Optional[Dict[str, Any]]:
|
|
"""Return the SCHEDULE.json entry whose end is `_WEEKLY_BR_FIRE_OFFSET_S`
|
|
seconds in the past (within `_WEEKLY_BR_FIRE_TOLERANCE_S`), or None.
|
|
"""
|
|
try:
|
|
with open(_SCHEDULE_PATH, "r", encoding="utf-8") as fp:
|
|
schedule = json.load(fp)
|
|
except Exception as e:
|
|
logging.error("[WBR] Failed to read SCHEDULE.json: %s", e)
|
|
return None
|
|
|
|
target = now_ts - _WEEKLY_BR_FIRE_OFFSET_S
|
|
for entry in schedule:
|
|
try:
|
|
end_ts = int(entry["end"])
|
|
except (KeyError, TypeError, ValueError):
|
|
continue
|
|
if target - _WEEKLY_BR_FIRE_TOLERANCE_S <= end_ts <= target:
|
|
return entry
|
|
return None
|
|
|
|
|
|
@tasks.loop(seconds=60)
|
|
async def weekly_br_report_task():
|
|
"""Fire the Weekly BR Report ~10 min after each BR window ends."""
|
|
now_ts = int(datetime.now(timezone.utc).timestamp())
|
|
window = _just_ended_br_window(now_ts)
|
|
if window is None:
|
|
return
|
|
try:
|
|
await execute_weekly_br_report_task(window)
|
|
await _record("weekly_br_report", True)
|
|
except Exception as e:
|
|
await _record("weekly_br_report", False, str(e))
|
|
raise
|
|
|
|
|
|
@weekly_br_report_task.before_loop
|
|
async def before_weekly_br_report_task():
|
|
await get_bot().wait_until_ready()
|
|
|
|
|
|
# ============================================================================
|
|
# TASK STARTER FUNCTION
|
|
# ============================================================================
|
|
|
|
async def _startup_heavy_init():
|
|
"""Run heavy DB/API tasks at startup, then start recurring tasks."""
|
|
try:
|
|
await execute_update_squadrons_db_task(count=1000)
|
|
except Exception as e:
|
|
logging.error(f"[STARTUP] squadrons.db update failed: {e}")
|
|
|
|
try:
|
|
await execute_sync_squadron_members_bulk(count=1000)
|
|
except Exception as e:
|
|
logging.error(f"[STARTUP] squadron members sync failed: {e}")
|
|
|
|
try:
|
|
await execute_cleanup_stale_squadrons()
|
|
except Exception as e:
|
|
logging.error(f"[STARTUP] stale squadron cleanup failed: {e}")
|
|
|
|
await asyncio.sleep(5)
|
|
|
|
# Recurring tasks (started after heavy init completes)
|
|
update_squadrons_db_task.start()
|
|
squadron_stats_tracker_task.start()
|
|
leave_alarm_task.start()
|
|
|
|
# Continuous background tasks
|
|
squadron_points_loop_task.start()
|
|
update_meta_data_task.start()
|
|
sync_guild_metas_task.start()
|
|
|
|
|
|
async def start_all_tasks():
|
|
"""
|
|
Start all background tasks. Heavy DB init runs in a background task
|
|
so on_ready returns quickly.
|
|
|
|
Order:
|
|
1. Lightweight time-check tasks (just check clock, no heavy work)
|
|
2. WebSocket listeners (persistent connections)
|
|
3. Heavy DB/API tasks (background — does not block on_ready)
|
|
"""
|
|
# Phase 1: Lightweight time-check tasks
|
|
entitlement_cache_task.start()
|
|
ldb_alarm_task.start()
|
|
squadron_stats_boundary_task.start()
|
|
points_alarm_task.start()
|
|
stacks_cleanup_task.start()
|
|
replay_cleanup_task.start()
|
|
health_heartbeat_task.start()
|
|
weekly_br_report_task.start()
|
|
ttl_alert_task.start()
|
|
# Phase 2: WebSocket listeners
|
|
ws_autolog_task.start()
|
|
ws_gob_task.start()
|
|
# Phase 3: Heavy DB tasks (background — doesn't block on_ready)
|
|
asyncio.create_task(_startup_heavy_init())
|
|
|
|
|
|
def stop_all_tasks():
|
|
"""Stop all background tasks. Call this on shutdown."""
|
|
entitlement_cache_task.cancel()
|
|
ldb_alarm_task.cancel()
|
|
squadron_stats_tracker_task.cancel()
|
|
squadron_stats_boundary_task.cancel()
|
|
points_alarm_task.cancel()
|
|
stacks_cleanup_task.cancel()
|
|
replay_cleanup_task.cancel()
|
|
leave_alarm_task.cancel()
|
|
update_squadrons_db_task.cancel()
|
|
update_meta_data_task.cancel()
|
|
ws_autolog_task.cancel()
|
|
ws_gob_task.cancel()
|
|
squadron_points_loop_task.cancel()
|
|
sync_guild_metas_task.cancel()
|
|
health_heartbeat_task.cancel()
|
|
weekly_br_report_task.cancel()
|
|
ttl_alert_task.cancel()
|