fix(provision): publish default stall in background to avoid blocking signup (#7) #8

Merged
padreug merged 1 commit from fix-7-publish-stall-async into main 2026-06-03 16:40:04 +00:00
Owner

Summary

Closes #7. provision_merchant was inline-awaiting sign_and_send_to_nostr for the default stall publish; the underlying nostr_client.publish_nostr_event has no per-relay timeout, so an unreachable relay set pinned the uvicorn worker on POST /auth/register forever (since this code runs inside lnbits's eager default-merchant hook from aiolabs/lnbits#46).

Schedule the publish via asyncio.create_task so signup returns immediately. The background task wraps the publish in asyncio.wait_for with a 30 s cap so a permanently-unreachable relay set doesn't pin an asyncio task for the lifetime of the process; on timeout the stall's event_id simply stays NULL until a later republish lands it (existing webapp-self-heal / republish behavior already handles that).

The publish was already documented as "Non-fatal on failure" in the existing comment, so fire-and-forget matches the original intent — the inline await was a structural mistake, not a deliberate hard-dependency.

Test plan

Verified locally on the regtest dev stack:

  • LNBITS_USER_DEFAULT_EXTENSIONS includes nostrmarket so _create_default_merchant fires on signup.
  • Bunker disabled (LocalSigner path), external relays unreachable from the docker network — i.e. the exact conditions that hung uvicorn pre-patch.
  • POST /api/v1/auth/register returns HTTP 200 with a valid JWT in 2.77 s (was: indefinite).
  • Background publish lands the kind-30017 stall event on the lnbits-internal nostrrelay ~12 s after signup:
    nostr event: [30017, <merchant_pubkey>, '{"id":"…","name":"Bg_601\\'s Store",…}']
    
  • merchants + stalls rows persist with expected names; no orphaned/duplicated rows.
  • Subsequent signups / logins are not blocked (no worker-pool starvation from prior hang).
  • To verify on bunker-mode path once aiolabs/nsecbunkerd#20 and #21 are fixed.

Notes

  • The sign_and_send_to_nostr helper is also called from product/stall update/delete paths. Those flows want the inline-await behavior (caller wants the event.id round-tripped). This patch only touches the provision_merchant call-site since that's the one in the signup-critical inline-await path.
  • 30 s timeout is generous (signing through the bunker costs 1–2 s, plus relay publish itself). The cap matters only when the relay set is unreachable.

🤖 Generated with Claude Code

## Summary Closes #7. `provision_merchant` was inline-awaiting `sign_and_send_to_nostr` for the default stall publish; the underlying `nostr_client.publish_nostr_event` has no per-relay timeout, so an unreachable relay set pinned the uvicorn worker on `POST /auth/register` forever (since this code runs inside lnbits's eager default-merchant hook from aiolabs/lnbits#46). Schedule the publish via `asyncio.create_task` so signup returns immediately. The background task wraps the publish in `asyncio.wait_for` with a 30 s cap so a permanently-unreachable relay set doesn't pin an asyncio task for the lifetime of the process; on timeout the stall's `event_id` simply stays NULL until a later republish lands it (existing webapp-self-heal / republish behavior already handles that). The publish was already documented as "Non-fatal on failure" in the existing comment, so fire-and-forget matches the original intent — the inline await was a structural mistake, not a deliberate hard-dependency. ## Test plan Verified locally on the regtest dev stack: - [x] `LNBITS_USER_DEFAULT_EXTENSIONS` includes `nostrmarket` so `_create_default_merchant` fires on signup. - [x] Bunker disabled (LocalSigner path), external relays unreachable from the docker network — i.e. the exact conditions that hung uvicorn pre-patch. - [x] `POST /api/v1/auth/register` returns `HTTP 200` with a valid JWT in **2.77 s** (was: indefinite). - [x] Background publish lands the kind-30017 stall event on the lnbits-internal nostrrelay ~12 s after signup: ``` nostr event: [30017, <merchant_pubkey>, '{"id":"…","name":"Bg_601\\'s Store",…}'] ``` - [x] `merchants` + `stalls` rows persist with expected names; no orphaned/duplicated rows. - [x] Subsequent signups / logins are not blocked (no worker-pool starvation from prior hang). - [ ] To verify on bunker-mode path once aiolabs/nsecbunkerd#20 and #21 are fixed. ## Notes - The `sign_and_send_to_nostr` helper is also called from product/stall update/delete paths. Those flows want the inline-await behavior (caller wants the `event.id` round-tripped). This patch only touches the `provision_merchant` call-site since that's the one in the signup-critical inline-await path. - 30 s timeout is generous (signing through the bunker costs 1–2 s, plus relay publish itself). The cap matters only when the relay set is unreachable. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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
774c3586a1
`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>
padreug deleted branch fix-7-publish-stall-async 2026-06-03 16:40:05 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/nostrmarket!8
No description provided.