diff --git a/services.py b/services.py index 5da63ef..444a9d5 100644 --- a/services.py +++ b/services.py @@ -288,18 +288,57 @@ async def provision_merchant( # Publish the kind 30017 stall event so customers' clients can resolve # the stall name when they fetch products. Non-fatal on failure: a # later product publish (or webapp self-heal) will retry. + # + # Fire-and-forget: `nostr_client.publish_nostr_event` has no per-relay + # deadline and will block indefinitely if every configured relay is + # unreachable (cf. aiolabs/nostrmarket#7). When `provision_merchant` + # is called from the eager signup hook (lnbits/core/services/users.py + # ::_create_default_merchant, aiolabs/lnbits#46), inline-awaiting that + # publish hangs the uvicorn worker on `POST /auth/register` forever. + # The DB rows we just wrote are sufficient to serve the wallet UI; + # the stall event_id gets backfilled when the publish completes (or + # stays NULL until a later resubscribe-driven republish lands it). + asyncio.create_task( + _publish_default_stall_background(merchant.id, merchant, default_stall) + ) + + return merchant + + +# Generous bound: signing through the bunker can take 1–2 s on a cold +# session, plus the relay publish itself. 30 s is well over both, and +# the cap matters only when the relay set is unreachable. +STALL_PUBLISH_TIMEOUT_S = 30.0 + + +async def _publish_default_stall_background( + merchant_id: str, merchant: Merchant, default_stall: Stall +) -> None: + """Background helper for `provision_merchant`'s default-stall publish. + + Bounded by `STALL_PUBLISH_TIMEOUT_S` so even a permanently-unreachable + relay set doesn't pin an asyncio task forever. Errors and timeouts are + logged at warning — never raised, since the caller scheduled-and-forgot. + """ try: - stall_event = await sign_and_send_to_nostr(merchant, default_stall) + stall_event = await asyncio.wait_for( + sign_and_send_to_nostr(merchant, default_stall), + timeout=STALL_PUBLISH_TIMEOUT_S, + ) default_stall.event_id = stall_event.id - await update_stall(merchant.id, default_stall) + await update_stall(merchant_id, default_stall) + except asyncio.TimeoutError: + logger.warning( + f"[NOSTRMARKET] Default stall publish for merchant " + f"{merchant_id} timed out after {STALL_PUBLISH_TIMEOUT_S}s; " + f"event_id stays NULL until a later republish lands it" + ) except Exception as ex: logger.warning( f"[NOSTRMARKET] Failed to publish default stall for " - f"merchant {merchant.id}: {ex}" + f"merchant {merchant_id}: {ex}" ) - return merchant - async def handle_order_paid(order_id: str, merchant_pubkey: str): try: