Daily updates from Odoo
Monday, March 30, 2026
1 change
Resolved issues and error corrections
This update resolves a critical issue where multiple users could simultaneously purchase the last event tickets, leading to overbooking. The fix uses database locking to ensure only one transaction can update ticket availability at a time, guaranteeing accurate seat counts and preventing overselling.
Original PR description
Steps to Reproduce: 1. Create an event with a limited number of tickets (e.g., Max Capacity = 1). 2. Try to buy the last tickets at the same time from the POS and the Website. 3. Observe that both…
Steps to Reproduce:
1. Create an event with a limited number of tickets (e.g., Max Capacity = 1).
2. Try to buy the last tickets at the same time from the POS and the Website.
3. Observe that both transactions succeed, resulting in overbooking.
Problem:
When multiple transactions attempt to purchase the last available ticket for an event simultaneously—whether through the Point of Sale, the website, or the backend—the system can conflict. In the Website vs. Website scenario, this conflict often manifests as an InFailedSqlTransaction. This occurs because both transactions attempt to update the same row in the "event_mail" table (the mail_count_done counter) to schedule confirmation emails. Under PostgreSQL's REPEATABLE READ isolation, this concurrent update triggers a "could not serialize access" error, aborting the transaction and causing subsequent database commands to be ignored.
While this "accidental" lock prevents overbooking on the website by crashing the loser of the race, it does not provide a robust solution and is entirely bypassed by the POS. The root cause lies in how PostgreSQL handles REPEATABLE READ isolation. In Odoo, when a transaction starts, it works from a snapshot of the database. If transactions run concurrently:
* Both read the registration count from their own isolated snapshot (which shows seats are still available).
* Both pass the validation check because they cannot see the other's uncommitted rows.
* Once both commit, the event ends up over-booked.
Previously, there was no locking mechanism to serialize these checks or force a fresh read of the data.
Fix:
A new method _lock_and_check_availability is added to event.event.ticket to ensure registrations are handled one at a time.
1. Row-Level Locking: The method issues a SELECT ... FOR UPDATE on the event_event and event_event_ticket rows. This prevents other transactions from modifying these records until the current one commits.
2. Fresh Data Read: After securing the lock, the method opens a new database cursor via self.pool.cursor(). Because this is a separate cursor, it ignores the stale snapshot of the original transaction and reads the true, committed state of the seats.
Implementation Details:
* Triggers: This check is called during payment post-processing (payment.transaction) and Point of Sale order finalization (pos.order).
* Error Handling:
* Retries: The payment controller now catches InFailedSqlTransaction. If a transaction loses the race for a lock and the cursor fails, Odoo will automatically schedule a retry.
* Blocking: If the fresh check determines no seats remain, it raises a ValidationError, rolls back the transaction, and informs the customer.
* User Feedback: A processing_msg field is added to payment.provider to update the portal UI. This ensures customers see a 'Processing' status while the availability check is running.
opw-5932567