diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index 07e83ccfa1fbcb98b07dbc698b03518ec385b825..e72de8be75eb29c5804502cf7d9eb0c7b4a1adc7 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -237,22 +237,46 @@ protected function init($theme_name = NULL) {
    */
   public function get() {
     $this->init($this->themeName);
-    if (isset($this->registry[$this->theme->getName()])) {
-      return $this->registry[$this->theme->getName()];
+    if ($cached = $this->cacheGet()) {
+      return $cached;
     }
-    if ($cache = $this->cache->get('theme_registry:' . $this->theme->getName())) {
-      $this->registry[$this->theme->getName()] = $cache->data;
-    }
-    else {
-      $this->build();
-      // Only persist it if all modules are loaded to ensure it is complete.
-      if ($this->moduleHandler->isLoaded()) {
-        $this->setCache();
+    // If called from inside a Fiber, suspend it, this may allow another code
+    // path to begin an asynchronous operation before we do the CPU-intensive
+    // task of building the theme registry.
+    if (\Fiber::getCurrent() !== NULL) {
+      \Fiber::suspend();
+      // When the Fiber is resumed, check the cache again since it may have been
+      // built in the meantime, either in this process or via a different
+      // request altogether.
+      if ($cached = $this->cacheGet()) {
+        return $cached;
       }
     }
+    $this->build();
+    // Only persist it if all modules are loaded to ensure it is complete.
+    if ($this->moduleHandler->isLoaded()) {
+      $this->setCache();
+    }
     return $this->registry[$this->theme->getName()];
   }
 
+  /**
+   * Gets the theme registry cache.
+   *
+   * @return array|null
+   */
+  protected function cacheGet(): ?array {
+    $theme_name = $this->theme->getName();
+    if (isset($this->registry[$theme_name])) {
+      return $this->registry[$theme_name];
+    }
+    elseif ($cache = $this->cache->get('theme_registry:' . $theme_name)) {
+      $this->registry[$theme_name] = $cache->data;
+      return $this->registry[$theme_name];
+    }
+    return NULL;
+  }
+
   /**
    * Returns the incomplete, runtime theme registry.
    *
diff --git a/core/modules/big_pipe/src/Render/BigPipe.php b/core/modules/big_pipe/src/Render/BigPipe.php
index 75adfebe3a876a89871e79dd08af84243184d314..eb4b731096788a9da5d08048bd67c8edc30fa19f 100644
--- a/core/modules/big_pipe/src/Render/BigPipe.php
+++ b/core/modules/big_pipe/src/Render/BigPipe.php
@@ -536,74 +536,104 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde
     $fake_request = $this->requestStack->getMainRequest()->duplicate();
     $fake_request->headers->set('Accept', 'application/vnd.drupal-ajax');
 
+    // Create a Fiber for each placeholder.
+    $fibers = [];
+    $message_placeholder_id = NULL;
     foreach ($placeholder_order as $placeholder_id) {
       if (!isset($placeholders[$placeholder_id])) {
         continue;
       }
-
-      // Render the placeholder.
       $placeholder_render_array = $placeholders[$placeholder_id];
-      try {
-        $elements = $this->renderPlaceholder($placeholder_id, $placeholder_render_array);
-      }
-      catch (\Exception $e) {
-        if ($this->configFactory->get('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
-          throw $e;
-        }
-        else {
-          trigger_error($e, E_USER_ERROR);
-          continue;
-        }
-      }
 
-      // Create a new AjaxResponse.
-      $ajax_response = new AjaxResponse();
-      // JavaScript's querySelector automatically decodes HTML entities in
-      // attributes, so we must decode the entities of the current BigPipe
-      // placeholder ID (which has HTML entities encoded since we use it to find
-      // the placeholders).
-      $big_pipe_js_placeholder_id = Html::decodeEntities($placeholder_id);
-      $ajax_response->addCommand(new ReplaceCommand(sprintf('[data-big-pipe-placeholder-id="%s"]', $big_pipe_js_placeholder_id), $elements['#markup']));
-      $ajax_response->setAttachments($elements['#attached']);
-
-      // Push a fake request with the asset libraries loaded so far and dispatch
-      // KernelEvents::RESPONSE event. This results in the attachments for the
-      // AJAX response being processed by AjaxResponseAttachmentsProcessor and
-      // hence:
-      // - the necessary AJAX commands to load the necessary missing asset
-      //   libraries and updated AJAX page state are added to the AJAX response
-      // - the attachments associated with the response are finalized, which
-      //   allows us to track the total set of asset libraries sent in the
-      //   initial HTML response plus all embedded AJAX responses sent so far.
-      $fake_request->query->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']);
-      try {
-        $ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response);
+      // Ensure the messages placeholder renders last, the render order of every
+      // other placeholder is safe to change.
+      // @see static::getPlaceholderOrder()
+      if (isset($placeholder_render_array['#lazy_builder']) && $placeholder_render_array['#lazy_builder'][0] === 'Drupal\Core\Render\Element\StatusMessages::renderMessages') {
+        $message_placeholder_id = $placeholder_id;
       }
-      catch (\Exception $e) {
-        if ($this->configFactory->get('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
-          throw $e;
-        }
-        else {
-          trigger_error($e, E_USER_ERROR);
+      $fibers[$placeholder_id] = new \Fiber(fn() => $this->renderPlaceholder($placeholder_id, $placeholder_render_array));
+    }
+    while (count($fibers) > 0) {
+      $iterations = 0;
+      foreach ($fibers as $placeholder_id => $fiber) {
+        // Keep skipping the messages placeholder until it's the only Fiber
+        // remaining. @todo https://www.drupal.org/project/drupal/issues/3379885
+        if (isset($message_placeholder_id) && $placeholder_id === $message_placeholder_id && count($fibers) > 1) {
           continue;
         }
-      }
-
-      // Send this embedded AJAX response.
-      $json = $ajax_response->getContent();
-      $output = <<<EOF
-    <script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="$placeholder_id">
-    $json
-    </script>
+        try {
+          if (!$fiber->isStarted()) {
+            $fiber->start();
+          }
+          elseif ($fiber->isSuspended()) {
+            $fiber->resume();
+          }
+          // If the Fiber hasn't terminated by this point, move onto the next
+          // placeholder, we'll resume this Fiber again when we get back here.
+          if (!$fiber->isTerminated()) {
+            // If we've gone through the placeholders once already, and they're
+            // still not finished, then start to allow code higher up the stack
+            // to get on with something else.
+            if ($iterations) {
+              $fiber = \Fiber::getCurrent();
+              if ($fiber !== NULL) {
+                $fiber->suspend();
+              }
+            }
+            continue;
+          }
+          $elements = $fiber->getReturn();
+          unset($fibers[$placeholder_id]);
+          // Create a new AjaxResponse.
+          $ajax_response = new AjaxResponse();
+          // JavaScript's querySelector automatically decodes HTML entities in
+          // attributes, so we must decode the entities of the current BigPipe
+          // placeholder ID (which has HTML entities encoded since we use it to
+          // find the placeholders).
+          $big_pipe_js_placeholder_id = Html::decodeEntities($placeholder_id);
+          $ajax_response->addCommand(new ReplaceCommand(sprintf('[data-big-pipe-placeholder-id="%s"]', $big_pipe_js_placeholder_id), $elements['#markup']));
+          $ajax_response->setAttachments($elements['#attached']);
+
+          // Push a fake request with the asset libraries loaded so far and
+          // dispatch KernelEvents::RESPONSE event. This results in the
+          // attachments for the AJAX response being processed by
+          // AjaxResponseAttachmentsProcessor and hence:
+          // - the necessary AJAX commands to load the necessary missing asset
+          //   libraries and updated AJAX page state are added to the AJAX
+          //   response
+          // - the attachments associated with the response are finalized,
+          // which allows us to track the total set of asset libraries sent in
+          // the initial HTML response plus all embedded AJAX responses sent so
+          // far.
+          $fake_request->query->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']);
+          $ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response);
+          // Send this embedded AJAX response.
+          $json = $ajax_response->getContent();
+          $output = <<<EOF
+<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="$placeholder_id">
+$json
+</script>
 EOF;
-      $this->sendChunk($output);
-
-      // Another placeholder was rendered and sent, track the set of asset
-      // libraries sent so far. Any new settings are already sent; we don't need
-      // to track those.
-      if (isset($ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])) {
-        $cumulative_assets->setAlreadyLoadedLibraries(explode(',', $ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries']));
+          $this->sendChunk($output);
+
+          // Another placeholder was rendered and sent, track the set of asset
+          // libraries sent so far. Any new settings are already sent; we
+          // don't need to track those.
+          if (isset($ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])) {
+            $cumulative_assets->setAlreadyLoadedLibraries(explode(',', $ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries']));
+          }
+        }
+        catch (\Exception $e) {
+          unset($fibers[$placeholder_id]);
+          if ($this->configFactory->get('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
+            throw $e;
+          }
+          else {
+            trigger_error($e, E_USER_ERROR);
+          }
+        }
       }
+      $iterations++;
     }
 
     // Send the stop signal.
@@ -696,7 +726,7 @@ protected function renderPlaceholder($placeholder, array $placeholder_render_arr
   /**
    * Gets the BigPipe placeholder order.
    *
-   * Determines the order in which BigPipe placeholders must be replaced.
+   * Determines the order in which BigPipe placeholders are executed.
    *
    * @param string $html
    *   HTML markup.
@@ -705,10 +735,14 @@ protected function renderPlaceholder($placeholder, array $placeholder_render_arr
    *   placeholder IDs.
    *
    * @return array
-   *   Indexed array; the order in which the BigPipe placeholders must be sent.
-   *   Values are the BigPipe placeholder IDs. Note that only unique
-   *   placeholders are kept: if the same placeholder occurs multiple times, we
-   *   only keep the first occurrence.
+   *   Indexed array; the order in which the BigPipe placeholders will start
+   *   execution. Placeholders begin execution in DOM order, except for the
+   *   messages placeholder which must always be executed last. Note that due to
+   *   the Fibers implementation of BigPipe, although placeholders will start
+   *   executing in DOM order, they may finish and render in any order. Values
+   *   are the BigPipe placeholder IDs. Note that only unique placeholders are
+   *   kept: if the same placeholder occurs multiple times, we only keep the
+   *   first occurrence.
    */
   protected function getPlaceholderOrder($html, $placeholders) {
     $placeholder_ids = [];
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php
index 602e1118ed810f7acb03f1347af817d0410e0ae0..44ffc8ccc3bda9ab9fbec8043d0b3ca297baaec6 100644
--- a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php
+++ b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php
@@ -4,6 +4,7 @@
  * @file
  */
 // cSpell:ignore Vxezb
+// cSpell:ignore divpiggydiv
 
 namespace Drupal\big_pipe_test;
 
@@ -188,7 +189,63 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
     ];
     $hello->embeddedHtmlResponse = '<marquee>Yarhar llamas forever!</marquee>';
 
-    // 5. Edge case: non-#lazy_builder placeholder.
+    // 5. Edge case: non-#lazy_builder placeholder that calls Fiber::suspend().
+    $piggy = new BigPipePlaceholderTestCase(
+      [
+        '#markup' => BigPipeMarkup::create('<div>piggy</div>'),
+        '#attached' => [
+          'placeholders' => [
+            '<div>piggy</div>' => [
+              '#pre_render' => [
+                '\Drupal\big_pipe_test\BigPipeTestController::piggy',
+              ],
+            ],
+          ],
+        ],
+      ],
+      '<div>piggy</div>',
+      []
+    );
+    $piggy->bigPipePlaceholderId = 'divpiggydiv';
+    $piggy->bigPipePlaceholderRenderArray = [
+      '#prefix' => '<span data-big-pipe-placeholder-id="divpiggydiv">',
+      'interface_preview' => [],
+      '#suffix' => '</span>',
+      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
+      '#attached' => [
+        'library' => ['big_pipe/big_pipe'],
+        'drupalSettings' => [
+          'bigPipePlaceholderIds' => [
+            'divpiggydiv' => TRUE,
+          ],
+        ],
+        'big_pipe_placeholders' => [
+          'divpiggydiv' => $piggy->placeholderRenderArray,
+        ],
+      ],
+    ];
+    $piggy->embeddedAjaxResponseCommands = [
+      [
+        'command' => 'insert',
+        'method' => 'replaceWith',
+        'selector' => '[data-big-pipe-placeholder-id="divpiggydiv"]',
+        'data' => '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>',
+        'settings' => NULL,
+      ],
+    ];
+    $piggy->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="divpiggydiv></span>';
+    $piggy->bigPipeNoJsPlaceholderRenderArray = [
+      '#markup' => '<span data-big-pipe-nojs-placeholder-id="divpiggydiv"></span>',
+      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
+      '#attached' => [
+        'big_pipe_nojs_placeholders' => [
+          '<span data-big-pipe-nojs-placeholder-id="divpiggydiv"></span>' => $piggy->placeholderRenderArray,
+        ],
+      ],
+    ];
+    $piggy->embeddedHtmlResponse = '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>';
+
+    // 6. Edge case: non-#lazy_builder placeholder.
     $current_time = new BigPipePlaceholderTestCase(
       [
         '#markup' => BigPipeMarkup::create('<time>CURRENT TIME</time>'),
@@ -249,7 +306,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
     ];
     $current_time->embeddedHtmlResponse = '<time datetime="1991-03-14"></time>';
 
-    // 6. Edge case: #lazy_builder that throws an exception.
+    // 7. Edge case: #lazy_builder that throws an exception.
     $exception = new BigPipePlaceholderTestCase(
       [
         '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
@@ -297,7 +354,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
 
     // cSpell:disable-next-line.
     $token = 'PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU';
-    // 7. Edge case: response filter throwing an exception for this placeholder.
+    // 8. Edge case: response filter throwing an exception for this placeholder.
     $embedded_response_exception = new BigPipePlaceholderTestCase(
       [
         '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
@@ -348,6 +405,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
       'html_attribute_value' => $form_action,
       'html_attribute_value_subset' => $csrf_token,
       'edge_case__invalid_html' => $hello,
+      'edge_case__html_non_lazy_builder_suspend' => $piggy,
       'edge_case__html_non_lazy_builder' => $current_time,
       'exception__lazy_builder' => $exception,
       'exception__embedded_response' => $embedded_response_exception,
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
index 6e6ea88f6a9b4da5523a092c552418e2adb0aa0f..8a0e9c9d9fa6a2f6e6fb7f9b536738c9b9cc4b6d 100644
--- a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
+++ b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
@@ -39,13 +39,16 @@ public function test() {
     // happens to not be valid HTML.
     $build['edge_case__invalid_html'] = $cases['edge_case__invalid_html']->renderArray;
 
-    // 5. Edge case: non-#lazy_builder placeholder.
+    // 5. Edge case: non-#lazy_builder placeholder that suspends.
+    $build['edge_case__html_non_lazy_builder_suspend'] = $cases['edge_case__html_non_lazy_builder_suspend']->renderArray;
+
+    // 6. Edge case: non-#lazy_builder placeholder.
     $build['edge_case__html_non_lazy_builder'] = $cases['edge_case__html_non_lazy_builder']->renderArray;
 
-    // 6. Exception: #lazy_builder that throws an exception.
+    // 7. Exception: #lazy_builder that throws an exception.
     $build['exception__lazy_builder'] = $cases['exception__lazy_builder']->renderArray;
 
-    // 7. Exception: placeholder that causes response filter to throw exception.
+    // 8. Exception: placeholder that causes response filter to throw exception.
     $build['exception__embedded_response'] = $cases['exception__embedded_response']->renderArray;
 
     return $build;
@@ -127,6 +130,24 @@ public static function currentTime() {
     ];
   }
 
+  /**
+   * #lazy_builder callback; suspends its own execution then returns markup.
+   *
+   * @return array
+   */
+  public static function piggy(): array {
+    // Immediately call Fiber::suspend(), so that other placeholders are
+    // executed next. When this is resumed, it will immediately return the
+    // render array.
+    if (\Fiber::getCurrent() !== NULL) {
+      \Fiber::suspend();
+    }
+    return [
+      '#markup' => '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>',
+      '#cache' => ['max-age' => 0],
+    ];
+  }
+
   /**
    * #lazy_builder callback; says "hello" or "yarhar".
    *
@@ -193,7 +214,7 @@ public static function counter() {
    * {@inheritdoc}
    */
   public static function trustedCallbacks() {
-    return ['currentTime', 'helloOrYarhar', 'exception', 'responseException', 'counter'];
+    return ['currentTime', 'piggy', 'helloOrYarhar', 'exception', 'responseException', 'counter'];
   }
 
 }
diff --git a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php b/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
index db7d610d41c33abf38d5f5b53cb46606373d713e..2a31e7bcedcf4d03e0414e90dcd6ac0aaa2a8fb4 100644
--- a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
+++ b/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
@@ -170,14 +170,19 @@ public function testBigPipe() {
     ]);
     $this->assertBigPipePlaceholders([
       $cases['html']->bigPipePlaceholderId                             => Json::encode($cases['html']->embeddedAjaxResponseCommands),
+      $cases['edge_case__html_non_lazy_builder_suspend']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder_suspend']->embeddedAjaxResponseCommands),
       $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder']->embeddedAjaxResponseCommands),
       $cases['exception__lazy_builder']->bigPipePlaceholderId          => NULL,
       $cases['exception__embedded_response']->bigPipePlaceholderId     => NULL,
     ], [
       0 => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId,
-      // The 'html' case contains the 'status messages' placeholder, which is
+      // The suspended placeholder is replaced after the non-suspended
+      // placeholder even though it appears first in the page.
+      // @see Drupal\big_pipe\Render\BigPipe\Render::sendPlaceholders()
+      1 => $cases['edge_case__html_non_lazy_builder_suspend']->bigPipePlaceholderId,
+       // The 'html' case contains the 'status messages' placeholder, which is
       // always rendered last.
-      1 => $cases['html']->bigPipePlaceholderId,
+      2 => $cases['html']->bigPipePlaceholderId,
     ]);
 
     $this->assertSession()->responseContains('</body>');
diff --git a/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php b/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
index d59ed4eddafe92b44ae68636331e82b3f50d1c49..be32aaa5f2aeebfbffc356305f310d1c59dce115 100644
--- a/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
+++ b/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
@@ -91,6 +91,13 @@ protected function build(array $placeholder_order) {
    *   A renderable array containing the message.
    */
   public static function setAndLogMessage($message) {
+    // Ensure that messages are rendered last even when earlier placeholders
+    // suspend the Fiber, this will cause BigPipe::renderPlaceholders() to loop
+    // around all of the fibers before resuming this one, then finally rendering
+    // the messages when there are no other placeholders left.
+    if (\Fiber::getCurrent() !== NULL) {
+      \Fiber::suspend();
+    }
     // Set message.
     \Drupal::messenger()->addStatus($message);