fix(provision): publish default stall in background to avoid blocking signup (#7)
Some checks failed
ci.yml / fix(provision): publish default stall in background to avoid blocking signup (#7) (pull_request) Failing after 0s
Some checks failed
ci.yml / fix(provision): publish default stall in background to avoid blocking signup (#7) (pull_request) Failing after 0s
`provision_merchant` is awaited inline by lnbits's eager default-merchant hook (lnbits/core/services/users.py::_create_default_merchant, aiolabs/lnbits#46). The pre-fix code inline-awaited `sign_and_send_to_nostr(merchant, default_stall)`, whose terminal `nostr_client.publish_nostr_event` has no per-relay deadline — every configured external relay being unreachable from the lnbits process pinned the uvicorn worker on `POST /auth/register` forever, with no exception ever raised. Subsequent signup / login attempts then queued behind that worker, locking out the instance until restart. This was filed as aiolabs/nostrmarket#7 and reproduces deterministically on the regtest dev stack whenever external relays aren't reachable from the docker network. The same hang reproduces whether or not the NIP-46 bunker is in the loop — the publish is the culprit, not the signer. Fix: - Schedule the publish via `asyncio.create_task(...)`. The signup response returns immediately after the DB rows we control are committed; the publish completes (or fails, or times out) in the background. Matches the existing comment "Non-fatal on failure: a later product publish (or webapp self-heal) will retry." - Wrap the background publish in `asyncio.wait_for` with a 30 s cap so a permanently-unreachable relay set doesn't leave an asyncio task pinned for the lifetime of the uvicorn process. Timeout logs at warning; `event_id` simply stays NULL on the stall row until a later republish lands it. Verified locally (regtest, bunker disabled, LocalSigner path): - signup `POST /auth/register` returns in <3 s with a valid JWT - background publish lands the kind-30017 stall event on the relay ~12 s later - merchant / stall rows persist with the expected names Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
14e7ea63eb
commit
774c3586a1
1 changed files with 44 additions and 5 deletions
55
services.py
55
services.py
|
|
@ -288,19 +288,58 @@ async def provision_merchant(
|
||||||
# Publish the kind 30017 stall event so customers' clients can resolve
|
# Publish the kind 30017 stall event so customers' clients can resolve
|
||||||
# the stall name when they fetch products. Non-fatal on failure: a
|
# the stall name when they fetch products. Non-fatal on failure: a
|
||||||
# later product publish (or webapp self-heal) will retry.
|
# later product publish (or webapp self-heal) will retry.
|
||||||
try:
|
#
|
||||||
stall_event = await sign_and_send_to_nostr(merchant, default_stall)
|
# Fire-and-forget: `nostr_client.publish_nostr_event` has no per-relay
|
||||||
default_stall.event_id = stall_event.id
|
# deadline and will block indefinitely if every configured relay is
|
||||||
await update_stall(merchant.id, default_stall)
|
# unreachable (cf. aiolabs/nostrmarket#7). When `provision_merchant`
|
||||||
except Exception as ex:
|
# is called from the eager signup hook (lnbits/core/services/users.py
|
||||||
logger.warning(
|
# ::_create_default_merchant, aiolabs/lnbits#46), inline-awaiting that
|
||||||
f"[NOSTRMARKET] Failed to publish default stall for "
|
# publish hangs the uvicorn worker on `POST /auth/register` forever.
|
||||||
f"merchant {merchant.id}: {ex}"
|
# 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
|
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 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)
|
||||||
|
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}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def handle_order_paid(order_id: str, merchant_pubkey: str):
|
async def handle_order_paid(order_id: str, merchant_pubkey: str):
|
||||||
try:
|
try:
|
||||||
order = await update_order_paid_status(order_id, True)
|
order = await update_order_paid_status(order_id, True)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue