Potential race conditions with cart update ajax call to just update cart attributes?

Hi, I have been noticing that there are times when I use the cart update ajax api to update attribute A, while there are another similar call from another app to update attribute B at the same time, my update might not go through even if the attribute I am updating is A rather than B and there are no overlapping on both updates:

If there is indeed a race condition on this, this could be problematic because I have lot of apps installed that makes this cart update ajax call to cart attribute and if race conditions happen, it will cause them not work reliably.

to be specific, here is the ajax call I am talking about.

POST: /cart/update.js

{
  attributes: {
    'A': 'something1'
  }
}

Hello Andrew,

I don’t know any solution to this except limiting the update sources (apps, scripts …). If attribute B starts being updated before attribute A is finished, you will indeed lose the attribute A value in the process.

Seeing this as well. Also with overlapping /add and /update calls. Both calls come back with ok status, but one of them “wins”. This is actually quite common, because analytics / tracking apps call /update while theme or other apps call /add.

I ran into the same issue. From what I was able to determine, the race condition has to do with creating carts — not updating them. If two updates go out at the same time, but a cart did not previously exist, then both updates will cause a cart to be created. Subsequent updates will apply to only one of those carts, essentially orphaning the other created cart. This has the appearance of one update clobbering another.

The fix was to trigger an update as early as possible in the theme code in order to ensure a cart exists before other updates are performed. The code looks something like this:

<script>
  (function () {
    if (typeof window === 'undefined' || typeof fetch !== 'function') return;
    if (window.__cartBootstrapPromise) return;

    if (document.cookie.split(';').some((cookie) => cookie.trim().startsWith('cart='))) {
      window.__cartBootstrapPromise = Promise.resolve();
      return;
    }

    const formData = new URLSearchParams();
    formData.append('attributes[_cart_init]', `${Date.now()}`);

    window.__cartBootstrapPromise = fetch('/cart/update.js', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
      body: formData,
    })
      .then((response) => {
        if (!response.ok) {
          throw new Error(`Cart bootstrap failed (${response.status})`);
        }
      })
      .catch((error) => {
        console.warn('[CartBootstrap] Unable to pre-initialize cart', error);
      });
  })();
</script>