Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Race condition when performing parallel order actions #3398

Open
asonnleitner opened this issue Mar 6, 2025 · 1 comment
Open

Race condition when performing parallel order actions #3398

asonnleitner opened this issue Mar 6, 2025 · 1 comment
Labels
type: bug 🐛 Something isn't working

Comments

@asonnleitner
Copy link
Contributor

Description

When making parallel requests to update different parts of an order, the data gets inconsistently applied. This issue affects not only basic order details, but all order operations including customer information, addresses, shipping methods, order state transitions, and payment processing.

Steps to Reproduce

  1. Create an order by adding an item to the cart
  2. Make parallel requests to any combination of:
    • Set customer information with setCustomerForOrder
    • Set shipping address with setOrderShippingAddress
    • Set billing address with setOrderBillingAddress
    • Set shipping method with setOrderShippingMethod
    • Transition order state with transitionOrderToState
    • Add payment with addPaymentToOrder
  3. Retrieve the order data with activeOrder

Expected Behavior

All updates should be properly applied to the order in a consistent manner, with all information present in the final order regardless of which operations were performed in parallel.

Actual Behavior

The parallel requests result in inconsistent order state. Specifically:

  • The individual mutation responses show success for their operations
  • Each response shows only its own changes applied correctly
  • The final activeOrder query shows an inconsistent state where some updates are applied and others are lost
  • In our test case, customer information was completely lost despite a successful response from setCustomerForOrder

Technical Details

The issue appears to be a race condition in the backend when handling concurrent order modifications. Each mutation seems to operate on its own version of the order entity, and the last one to commit wins, overwriting some fields from earlier mutations.

When a client performs multiple parallel operations on the same order, the backend processes might be retrieving and updating different instances of the same order entity without proper synchronization, leading to lost updates.

In our test logs:

  1. setCustomerForOrder response shows customer data but empty addresses
  2. setOrderShippingAddress response shows shipping and billing addresses but null customer
  3. setOrderBillingAddress response shows only billing address
  4. Final activeOrder contains shipping and billing addresses but null customer

This issue likely affects all order mutation operations including setOrderShippingMethod, transitionOrderToState, and addPaymentToOrder, which would be particularly problematic during checkout flows where multiple operations might be performed in quick succession.

Environment

  • Vendure version: v2.2.0
  • NodeJS version: 22
  • Database: postgres

Suggested Fix

Consider implementing:

  1. Transaction isolation or locking mechanisms for order entities
  2. Atomic operations that can handle multiple order updates in a single request
  3. Optimistic concurrency control using version numbers
  4. A queue system for order operations to ensure they're processed sequentially

This issue requires attention as it could lead to incomplete orders, payment inconsistencies, and poor customer experience during checkout flows.

@asonnleitner asonnleitner added the type: bug 🐛 Something isn't working label Mar 6, 2025
@michaelbromley
Copy link
Member

Thanks for the report. One thing to note is that if you want to perform a number of mutations at once, you could include them in a single document like:

mutation {
  setCustomerForOrder () ...
  setOrderShippingAddress () ..
  etc.
}

and per the GraphQL spec, each mutation should be executed in series. This could satisfy your second suggestion, although it will not be "atomic" in that the whole series will not get rolled back if one of the later mutations fails.

But regarding the larger issue of concurrent (separate) requests, this will take some careful planning to remedy. Locking or versioning might work. Are you interested in investigating or putting together a proof of concept implementation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug 🐛 Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants