fix: make check-then-hold atomic against concurrent bookings (#4) #9
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/atomic-hold"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes the code portion of #4.
The bug
is_available()→create_booking()is a critical section. Two simultaneous requests for the same nights could both pass the availability read before either wrote itsheldrow → double-booking.The fix
A per-room
asyncio.Lock(_room_locks[room_id]) inservices.request_bookingwraps exactly theis_available→create_bookingpair. Because aheldrow itself blocks the dates (is_availablecountsheldas occupying), the first request to commit inside the lock wins; every later one re-checks, sees the hold, and getsUnavailable/409.views_api) and the RPC door (transport_rpcs) funnel throughservices.request_booking, so one lock covers every entry point — a payoff of the earlier services.py extraction.Scope / caveat (documented)
An
asyncio.Lockonly serializes within one event loop. LNbits runs a single worker, so this is sufficient today. If it ever runs multi-worker/multi-process, this must become a DB-level guard (Postgres exclusion constraint on the date range, orSELECT … FOR UPDATEon the room row). Noted inevent-flow.mdand thecrud.is_availabledocstring.Test
No automated test yet — chatelet has no test harness, and a real concurrency test needs the LNbits pytest fixtures (DB + FakeWallet). Manual: fire two
booking_requests for identical dates at once → exactly oneheld, one409. Worth standing up atests/harness as a follow-up so this gets a regression test.Why PR
Money-handling extension → PR category. Handing off merge to you via the Forgejo UI.
🤖 Generated with Claude Code