11import asyncio
2+ import random
3+
24import hikari
35from loguru import logger
46
1719from luckypot .discord .commands import register_commands
1820from luckypot .discord .scheduler import run_daily_draw_loop
1921
22+ # Startup retry parameters for reaching StackCoin. Tuned so that luckypot waits
23+ # roughly long enough for stackcoin to come up after a simultaneous restart
24+ # (~16s in practice), but gives up well inside Docker's automatic-restart window
25+ # so the whole container gets recycled by `restart: unless-stopped`.
26+ STACKCOIN_CONNECT_MAX_ATTEMPTS = 12
27+ STACKCOIN_CONNECT_BASE_DELAY = 1.0
28+ STACKCOIN_CONNECT_MAX_DELAY = 30.0
29+
2030
2131logger .add ("lucky_pot.log" , rotation = "1 day" , retention = "7 days" , level = "INFO" )
2232
@@ -43,6 +53,44 @@ def _task_done_callback(task: asyncio.Task) -> None:
4353 logger .error (f"Background task { task .get_name ()} failed: { exc !r} " )
4454
4555
56+ async def _fetch_stackcoin_discord_id_or_die () -> str :
57+ """Retry ``fetch_stackcoin_discord_id`` against a not-yet-ready StackCoin.
58+
59+ stackcoin-python normalises transport faults into ``StackCoinError`` (since
60+ v0.1.6), so we only catch that. If we exhaust all attempts we raise — the
61+ caller in :func:`on_started` shuts the whole bot down by closing the hikari
62+ bot, which exits the process non-zero and lets Docker's
63+ ``restart: unless-stopped`` policy recycle the container. By the next boot
64+ StackCoin has been up for ages.
65+ """
66+ last_exc : BaseException | None = None
67+ for attempt in range (1 , STACKCOIN_CONNECT_MAX_ATTEMPTS + 1 ):
68+ try :
69+ discord_id = await stk .fetch_stackcoin_discord_id ()
70+ if discord_id is not None :
71+ return discord_id
72+ # Defence in depth: SDK returned None without raising — treat as a
73+ # soft miss and retry, since the only None path in stk.py is the
74+ # StackCoinError swallow at stk.py:40-42.
75+ last_exc = RuntimeError ("fetch_stackcoin_discord_id returned None" )
76+ except stackcoin .StackCoinError as e :
77+ last_exc = e
78+ delay = min (
79+ STACKCOIN_CONNECT_MAX_DELAY ,
80+ STACKCOIN_CONNECT_BASE_DELAY * (2 ** (attempt - 1 )),
81+ ) + random .random ()
82+ logger .warning (
83+ f"StackCoin not ready (attempt { attempt } /"
84+ f"{ STACKCOIN_CONNECT_MAX_ATTEMPTS } ), retrying in { delay :.1f} s: "
85+ f"{ last_exc !r} "
86+ )
87+ await asyncio .sleep (delay )
88+ raise RuntimeError (
89+ f"Could not reach StackCoin after { STACKCOIN_CONNECT_MAX_ATTEMPTS } "
90+ f"attempts; refusing to start half-functional. Last error: { last_exc !r} "
91+ )
92+
93+
4694@bot .listen ()
4795async def on_started (_event : hikari .StartedEvent ) -> None :
4896 global _gateway
@@ -64,7 +112,12 @@ def persist_event_id(event_id: int) -> None:
64112 finally :
65113 c .close ()
66114
67- await stk .fetch_stackcoin_discord_id ()
115+ try :
116+ await _fetch_stackcoin_discord_id_or_die ()
117+ except RuntimeError as e :
118+ logger .error (str (e ))
119+ await bot .close ()
120+ return
68121
69122 _gateway = stackcoin .Gateway (
70123 ws_url = settings .stackcoin_ws_url ,
0 commit comments