Skip to content

Content Delivery API deadlocks when simultaneously requesting items referencing each other #15827

@diger74

Description

@diger74

Which Umbraco version are you using? (Please write the exact version, example: 10.1.0)

12.3.7

Bug summary

When there are multiple items in the CMS tree which reference each other, e.g. assume Author content type with SimilarAuthors multinode treepicker property, and the following content:

  1. Author_1, with SimilarAuthors: Author_2, Author_3
  2. Author_2, with SimilarAuthors: Author_1, Author_3
  3. Author_3, with SimilarAuthors: Author_1, Author_2

When 2 or more of these items are requested through Content Delivery API simultaneously, it causes deadlocks in Umbraco. These requests (and all further requests to these items) hang infinitely until Memory Cache Reload or application restart. Requesting other items works just fine, only these colliding items hang.

This is only reproducible when these requested items are not yet in the Content Delivery API cache. They can be in nuCache though.

Specifics

We are implementing a website using Umbraco 12 Content Delivery API with NextJS 14.

This website contains a few thousand of products, many of which have Related Products (multinode treepicker) populated. Very often these related products form clusters of 3-5 products which are specified as related to each other.

By the description of this bug, you may think this issue occurs extremely rarely because it is hard to imagine that 2 Content Delivery API requests (with an average execution time of about 50ms) will collide at almost the same time. In our case this however happens very often during the build of a static site in NextJS, when the framework requests many pages to be built at once. With the number of pages we got in Umbraco CMS, and the fact the related products are present there very often, our NextJS builds are crushing 80% of the times with requests to Umbraco hanging in deadlocks.

Steps to reproduce

Please find a vanilla test solution prepared on Umbraco 12.3.7:
TestApiLanguages.zip

Within the CMS content tree it contains:

  1. Author 1 (en), with SimilarAuthors: Author 2 (en), Author 3 (en)
  2. Author 2 (en), with SimilarAuthors: Author 1 (en), Author 3 (en)
  3. Author 3 (en), with SimilarAuthors: Author 1 (en), Author 2 (en)

API requests to query this content:

  1. https://localhost:44354/umbraco/delivery/api/v1/content/item/en/author-1-en/?expand=all ['Accept-Language': 'en-US']
  2. https://localhost:44354/umbraco/delivery/api/v1/content/item/en/author-2-en/?expand=all ['Accept-Language': 'en-US']
  3. https://localhost:44354/umbraco/delivery/api/v1/content/item/en/author-3-en/?expand=all ['Accept-Language': 'en-US']

To reproduce this issue, you need to be able to send the requests above at almost exactly the same time. Manually through Postman or some other tool, it is impossible to reproduce. Please find below the script implemented in k6 framework that tries to send these requests randomly in 100 concurrent threads. This approach has a pretty high probability of reproducing this issue. You need to install k6 to run this script, or implement something similar with any other tool.

import http from 'k6/http';
import { check } from 'k6';

export let options = {
  vus: 100, // 100 virtual users
  duration: '10s', // Duration to run the test, adjust as needed
};

export default function () {
  const requests = [
    {
      url: 'https://localhost:44354/umbraco/delivery/api/v1/content/item/en/author-1-en/?expand=all',
      headers: { 'Accept-Language': 'en-US' },
    },
    {
      url: 'https://localhost:44354/umbraco/delivery/api/v1/content/item/en/author-2-en/?expand=all',
      headers: { 'Accept-Language': 'en-US' },
    },
    {
      url: 'https://localhost:44354/umbraco/delivery/api/v1/content/item/en/author-3-en/?expand=all',
      headers: { 'Accept-Language': 'en-US' },
    },
  ];

  // Select a random request from the array
  const randomRequest = requests[Math.floor(Math.random() * requests.length)];
  
  // Perform the request
  let response = http.get(randomRequest.url, { headers: randomRequest.headers });
  
  // Check the response
  check(response, {
    [`status was 200 (${randomRequest.headers.Accept})`]: (r) => r.status === 200,
  });
}

Test case 1 (no deadlocks on warmed-up cache):

  1. Start Umbraco
  2. Request API URLs 1-3, manually, one at a time to warmup the Content Delivery API cache
  3. Run k6 script above to generate concurrent workload
  4. Request API URLs 1-3 again, manually, one at a time to make sure these still return results

Restart Umbraco (works 100%) OR reload Memory Cache (works 70% of time)

Test case 2 (deadlocks):

  1. Start Umbraco
  2. DO NOT request API URLs 1-3 manually, one at a time to avoid warmup of the Content Delivery API cache
  3. Run k6 script above to generate concurrent workload
  4. Request API URLs 1-3 again, manually, one at a time to make sure these requests are deadlocked now

Expected result / actual result

Test case 1:
You should NOT see any deadlocks with warmed up Content Delivery API cache.
k6 script run should show you some solid execution stats, on my machine there were ~40k successful calls to Content Delivery API in 10 seconds, returning the content back:
image

Test case 2:
You should experience deadlocks now, k6 script run should show something like this, with no executions, because all the requests were hanging waiting for the response from Umbraco:
image
Then, after running the script, if you try to request any of these 3 URLs via Postman or some other tool you should see infinite loading:
image

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions