From de4547f37fbf5825d7f2fc8c54838fb75aecf885 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Thu, 10 Apr 2025 00:54:59 +0100
Subject: [PATCH 01/35] Trap render context inside a fiber.

---
 core/lib/Drupal/Core/Render/Renderer.php | 36 ++++++++++++++++++++++--
 1 file changed, 33 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 1f26381bb181..641101be29a4 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -615,12 +615,42 @@ public function hasRenderContext() {
    * {@inheritdoc}
    */
   public function executeInRenderContext(RenderContext $context, callable $callable) {
-    // Store the current render context.
+    // When executing in a render context, we need to isolate any bubbled
+    // context within this method. To allow for async rendering, it's necessary
+    // to detect if a fiber suspends within a render context. When this happens,
+    // we swap the previous render context in before suspending upwards, then
+    // back out again before resuming.
     $previous_context = $this->getCurrentRenderContext();
-
     // Set the provided context and call the callable, it will use that context.
     $this->setCurrentRenderContext($context);
-    $result = $callable();
+
+    $fiber = new \Fiber(function () use ($callable) {
+      return $callable();
+    });
+    $running = TRUE;
+    $fiber->start();
+    $result = NULL;
+    while ($running) {
+      if ($fiber->isSuspended()) {
+        $parent_fiber = \Fiber::getCurrent();
+        if ($parent_fiber !== NULL) {
+          $this->setCurrentRenderContext($previous_context);
+          $parent_fiber->suspend();
+          $this->setCurrentRenderContext($context);
+        }
+        $fiber->resume();
+        if ($parent_fiber === NULL) {
+          // Prevent a spin-lock if a fiber keeps suspending repeatedly.
+          usleep(10);
+        }
+      }
+      // 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()) {
+        $running = FALSE;
+        $result = $fiber->getReturn();
+      }
+    }
     assert($context->count() <= 1, 'Bubbling failed.');
 
     // Restore the original render context.
-- 
GitLab


From bb7c3ddbc17a5ab61b8ebcb59ef137eed9aac6c4 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 20 Jun 2025 12:50:26 +0100
Subject: [PATCH 02/35] Initial attempt at test coverage.

---
 .../Drupal/Tests/Core/Render/RendererTest.php | 62 +++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 9c68273365b3..8ec2cf12846f 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -1126,6 +1126,68 @@ public function testHasRenderContext(): void {
     $this->assertFalse($this->renderer->hasRenderContext());
   }
 
+
+  /**
+   * @covers ::executeInRenderContext().
+   */
+  public function testExecuteInRenderContext(): void {
+
+    $this->renderer->executeInRenderContext(new RenderContext(), function() {
+      $callback = function() {
+        $build = [
+          'foo' => [
+            '#cache' => [
+              'tags' => ['foo'],
+            ],
+            '#access' => AccessResult::forbidden(),
+          ],
+          'bar' => [
+            '#cache' => [
+              'contexts' => ['foo'],
+            ],
+          ],
+        ];
+        \Fiber::suspend();
+        $this->renderer->render($build);
+        \Fiber::suspend();
+        $build = [
+          'foo' => [
+            '#cache' => [
+              'tags' => ['foo'],
+            ],
+          ],
+          'bar' => [
+            '#cache' => [
+              'contexts' => ['foo'],
+            ],
+          ],
+        ];
+        $this->renderer->render($build);
+        \Fiber::suspend();
+      };
+
+      $fibers = [];
+      foreach ([0, 1, 2] as $key) {
+        $fibers[$key] = new \Fiber(function () use ($callback) {
+          return $callback();
+        });
+      }
+      while ($fibers) {
+        foreach ($fibers as $key => $fiber) {
+          if ($fiber->isTerminated()) {
+            unset($fibers[$key]);
+            continue;
+          }
+          if ($fiber->isSuspended()) {
+            $fiber->resume();
+            continue;
+          }
+          $fiber->start();
+        }
+      }
+    });
+  }
+
 }
 
 /**
-- 
GitLab


From b2b72d6e1b07319b3e9d88781a8d1fbf381ee7be Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 20 Jun 2025 14:05:04 +0100
Subject: [PATCH 03/35] More tests

---
 .../Drupal/Tests/Core/Render/RendererTest.php | 46 ++++++++++++-------
 1 file changed, 29 insertions(+), 17 deletions(-)

diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 8ec2cf12846f..1d8c9ac2e168 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -1132,14 +1132,24 @@ public function testHasRenderContext(): void {
    */
   public function testExecuteInRenderContext(): void {
 
+
     $this->renderer->executeInRenderContext(new RenderContext(), function() {
-      $callback = function() {
+      $suspend_callback = function() {
+        $fiber_suspend_pre_render = function($elements) {
+          $elements = [
+            'foo' => [
+              '#markup' => 'hello',
+            ],
+          ];
+          \Fiber::suspend();
+          return $elements;
+        };
         $build = [
           'foo' => [
+            '#pre_render' => [$fiber_suspend_pre_render],
             '#cache' => [
               'tags' => ['foo'],
             ],
-            '#access' => AccessResult::forbidden(),
           ],
           'bar' => [
             '#cache' => [
@@ -1147,47 +1157,49 @@ public function testExecuteInRenderContext(): void {
             ],
           ],
         ];
-        \Fiber::suspend();
         $this->renderer->render($build);
-        \Fiber::suspend();
+      };
+      $callback = function() {
         $build = [
           'foo' => [
             '#cache' => [
               'tags' => ['foo'],
             ],
-          ],
-          'bar' => [
-            '#cache' => [
-              'contexts' => ['foo'],
-            ],
+            '#access' => AccessResult::forbidden(),
           ],
         ];
         $this->renderer->render($build);
-        \Fiber::suspend();
       };
 
       $fibers = [];
-      foreach ([0, 1, 2] as $key) {
-        $fibers[$key] = new \Fiber(function () use ($callback) {
-          return $callback();
+      $fibers[] = new \Fiber(function ()  use ($callback) {
+        return $callback();
+      });
+      foreach ([0, 1] as $key) {
+        $fibers[] = new \Fiber(function () use ($suspend_callback) {
+          return $suspend_callback();
         });
       }
       while ($fibers) {
         foreach ($fibers as $key => $fiber) {
-          if ($fiber->isTerminated()) {
-            unset($fibers[$key]);
-            continue;
-          }
           if ($fiber->isSuspended()) {
+            dump('suspended');
             $fiber->resume();
             continue;
           }
+          if ($fiber->isTerminated()) {
+            unset($fibers[$key]);
+            continue;
+          }
           $fiber->start();
         }
       }
+      $this->assertSame([], $fibers);
     });
+
   }
 
+
 }
 
 /**
-- 
GitLab


From 3a963a91762d431f51dba3a431727a163eb3575b Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 20 Jun 2025 14:12:05 +0100
Subject: [PATCH 04/35] Moar tests.

---
 core/lib/Drupal/Core/Render/Renderer.php      | 36 ++-----------------
 .../Drupal/Tests/Core/Render/RendererTest.php | 15 +++++---
 2 files changed, 14 insertions(+), 37 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 641101be29a4..1f26381bb181 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -615,42 +615,12 @@ public function hasRenderContext() {
    * {@inheritdoc}
    */
   public function executeInRenderContext(RenderContext $context, callable $callable) {
-    // When executing in a render context, we need to isolate any bubbled
-    // context within this method. To allow for async rendering, it's necessary
-    // to detect if a fiber suspends within a render context. When this happens,
-    // we swap the previous render context in before suspending upwards, then
-    // back out again before resuming.
+    // Store the current render context.
     $previous_context = $this->getCurrentRenderContext();
+
     // Set the provided context and call the callable, it will use that context.
     $this->setCurrentRenderContext($context);
-
-    $fiber = new \Fiber(function () use ($callable) {
-      return $callable();
-    });
-    $running = TRUE;
-    $fiber->start();
-    $result = NULL;
-    while ($running) {
-      if ($fiber->isSuspended()) {
-        $parent_fiber = \Fiber::getCurrent();
-        if ($parent_fiber !== NULL) {
-          $this->setCurrentRenderContext($previous_context);
-          $parent_fiber->suspend();
-          $this->setCurrentRenderContext($context);
-        }
-        $fiber->resume();
-        if ($parent_fiber === NULL) {
-          // Prevent a spin-lock if a fiber keeps suspending repeatedly.
-          usleep(10);
-        }
-      }
-      // 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()) {
-        $running = FALSE;
-        $result = $fiber->getReturn();
-      }
-    }
+    $result = $callable();
     assert($context->count() <= 1, 'Bubbling failed.');
 
     // Restore the original render context.
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 1d8c9ac2e168..5206cbb9f2fa 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -1144,6 +1144,14 @@ public function testExecuteInRenderContext(): void {
           \Fiber::suspend();
           return $elements;
         };
+
+        $build = [
+          'foo' => [
+            '#markup' => 'hello',
+          ],
+        ];
+        $this->renderer->render($build);
+        \Fiber::suspend();
         $build = [
           'foo' => [
             '#pre_render' => [$fiber_suspend_pre_render],
@@ -1172,18 +1180,17 @@ public function testExecuteInRenderContext(): void {
       };
 
       $fibers = [];
-      $fibers[] = new \Fiber(function ()  use ($callback) {
-        return $callback();
-      });
       foreach ([0, 1] as $key) {
         $fibers[] = new \Fiber(function () use ($suspend_callback) {
           return $suspend_callback();
         });
       }
+      $fibers[] = new \Fiber(function ()  use ($callback) {
+        return $callback();
+      });
       while ($fibers) {
         foreach ($fibers as $key => $fiber) {
           if ($fiber->isSuspended()) {
-            dump('suspended');
             $fiber->resume();
             continue;
           }
-- 
GitLab


From 817f4295b62e522f83539dec9c890f70ca6c147c Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 20 Jun 2025 14:53:35 +0100
Subject: [PATCH 05/35] Improve test.

---
 .../Drupal/Tests/Core/Render/RendererTest.php | 33 +++++--------------
 1 file changed, 8 insertions(+), 25 deletions(-)

diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 5206cbb9f2fa..e09c7009d8d6 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -1136,22 +1136,20 @@ public function testExecuteInRenderContext(): void {
     $this->renderer->executeInRenderContext(new RenderContext(), function() {
       $suspend_callback = function() {
         $fiber_suspend_pre_render = function($elements) {
-          $elements = [
+          $fiber_suspend = function ($elements) {
+            \Fiber::suspend();
+            return $elements;
+          };
+          $build = [
             'foo' => [
-              '#markup' => 'hello',
+              '#markup' => 'foo',
+              '#pre_render'=> [$fiber_suspend],
             ],
           ];
+          $this->renderer->renderInIsolation($build);
           \Fiber::suspend();
           return $elements;
         };
-
-        $build = [
-          'foo' => [
-            '#markup' => 'hello',
-          ],
-        ];
-        $this->renderer->render($build);
-        \Fiber::suspend();
         $build = [
           'foo' => [
             '#pre_render' => [$fiber_suspend_pre_render],
@@ -1167,27 +1165,12 @@ public function testExecuteInRenderContext(): void {
         ];
         $this->renderer->render($build);
       };
-      $callback = function() {
-        $build = [
-          'foo' => [
-            '#cache' => [
-              'tags' => ['foo'],
-            ],
-            '#access' => AccessResult::forbidden(),
-          ],
-        ];
-        $this->renderer->render($build);
-      };
-
       $fibers = [];
       foreach ([0, 1] as $key) {
         $fibers[] = new \Fiber(function () use ($suspend_callback) {
           return $suspend_callback();
         });
       }
-      $fibers[] = new \Fiber(function ()  use ($callback) {
-        return $callback();
-      });
       while ($fibers) {
         foreach ($fibers as $key => $fiber) {
           if ($fiber->isSuspended()) {
-- 
GitLab


From fafb2ddedd8543dd7268710831cbd24eefedfc34 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 20 Jun 2025 15:25:33 +0100
Subject: [PATCH 06/35] Back to bubbling failed.

---
 .../Drupal/Tests/Core/Render/RendererTest.php | 38 ++++++++-----------
 1 file changed, 16 insertions(+), 22 deletions(-)

diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index e09c7009d8d6..4c4fb2babc53 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -1131,10 +1131,8 @@ public function testHasRenderContext(): void {
    * @covers ::executeInRenderContext().
    */
   public function testExecuteInRenderContext(): void {
-
-
-    $this->renderer->executeInRenderContext(new RenderContext(), function() {
-      $suspend_callback = function() {
+    $return = $this->renderer->executeInRenderContext(new RenderContext(), function() {
+      $fiber_callback = function() {
         $fiber_suspend_pre_render = function($elements) {
           $fiber_suspend = function ($elements) {
             \Fiber::suspend();
@@ -1147,46 +1145,42 @@ public function testExecuteInRenderContext(): void {
             ],
           ];
           $this->renderer->renderInIsolation($build);
-          \Fiber::suspend();
+          //$this->renderer->executeInRenderContext(new RenderContext(), function() {
+            //\Fiber::suspend();
+          //});
           return $elements;
         };
         $build = [
           'foo' => [
             '#pre_render' => [$fiber_suspend_pre_render],
-            '#cache' => [
-              'tags' => ['foo'],
-            ],
-          ],
-          'bar' => [
-            '#cache' => [
-              'contexts' => ['foo'],
-            ],
           ],
         ];
         $this->renderer->render($build);
       };
       $fibers = [];
       foreach ([0, 1] as $key) {
-        $fibers[] = new \Fiber(function () use ($suspend_callback) {
-          return $suspend_callback();
+        $fibers[] = new \Fiber(function () use ($fiber_callback) {
+          $fiber_callback();
+          return 'hello';
         });
       }
       while ($fibers) {
         foreach ($fibers as $key => $fiber) {
-          if ($fiber->isSuspended()) {
-            $fiber->resume();
-            continue;
-          }
           if ($fiber->isTerminated()) {
             unset($fibers[$key]);
             continue;
           }
-          $fiber->start();
+          if ($fiber->isSuspended()) {
+            $fiber->resume();
+          }
+          else {
+            $fiber->start();
+          }
         }
       }
-      $this->assertSame([], $fibers);
+      return TRUE;
     });
-
+    $this->assertTrue($return);
   }
 
 
-- 
GitLab


From f0b7e8a7f2723e9370e48a4b4b706ae65c8e3a2e Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 20 Jun 2025 15:27:11 +0100
Subject: [PATCH 07/35] Trap render context within a Fiber.

---
 core/lib/Drupal/Core/Render/Renderer.php | 36 ++++++++++++++++++++++--
 1 file changed, 33 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 1f26381bb181..641101be29a4 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -615,12 +615,42 @@ public function hasRenderContext() {
    * {@inheritdoc}
    */
   public function executeInRenderContext(RenderContext $context, callable $callable) {
-    // Store the current render context.
+    // When executing in a render context, we need to isolate any bubbled
+    // context within this method. To allow for async rendering, it's necessary
+    // to detect if a fiber suspends within a render context. When this happens,
+    // we swap the previous render context in before suspending upwards, then
+    // back out again before resuming.
     $previous_context = $this->getCurrentRenderContext();
-
     // Set the provided context and call the callable, it will use that context.
     $this->setCurrentRenderContext($context);
-    $result = $callable();
+
+    $fiber = new \Fiber(function () use ($callable) {
+      return $callable();
+    });
+    $running = TRUE;
+    $fiber->start();
+    $result = NULL;
+    while ($running) {
+      if ($fiber->isSuspended()) {
+        $parent_fiber = \Fiber::getCurrent();
+        if ($parent_fiber !== NULL) {
+          $this->setCurrentRenderContext($previous_context);
+          $parent_fiber->suspend();
+          $this->setCurrentRenderContext($context);
+        }
+        $fiber->resume();
+        if ($parent_fiber === NULL) {
+          // Prevent a spin-lock if a fiber keeps suspending repeatedly.
+          usleep(10);
+        }
+      }
+      // 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()) {
+        $running = FALSE;
+        $result = $fiber->getReturn();
+      }
+    }
     assert($context->count() <= 1, 'Bubbling failed.');
 
     // Restore the original render context.
-- 
GitLab


From 52826efc771d5ab74e040ff5ec7c696afa200237 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 20 Jun 2025 15:37:02 +0100
Subject: [PATCH 08/35] tidy up and add comments.

---
 core/tests/Drupal/Tests/Core/Render/RendererTest.php | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 4c4fb2babc53..bf4d2c3fde55 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -1133,6 +1133,13 @@ public function testHasRenderContext(): void {
   public function testExecuteInRenderContext(): void {
     $return = $this->renderer->executeInRenderContext(new RenderContext(), function() {
       $fiber_callback = function() {
+
+        // Create a #pre_render callback that renders a render array in
+        // isolation. This has its own #pre_render callback that calls
+        // Fiber::suspend(). This ensures that suspending a Fiber within
+        // multiple nested calls to ::executeInRenderContext() doesn't
+        // allow render context to get out of sync. This simulates similar
+        // conditions to BigPipe placeholder rendering.
         $fiber_suspend_pre_render = function($elements) {
           $fiber_suspend = function ($elements) {
             \Fiber::suspend();
@@ -1145,9 +1152,6 @@ public function testExecuteInRenderContext(): void {
             ],
           ];
           $this->renderer->renderInIsolation($build);
-          //$this->renderer->executeInRenderContext(new RenderContext(), function() {
-            //\Fiber::suspend();
-          //});
           return $elements;
         };
         $build = [
-- 
GitLab


From 07eb80ac08ff896f39d7c4630945b8c3fe029bbe Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 20 Jun 2025 15:59:15 +0100
Subject: [PATCH 09/35] More test cleanup.

---
 .../Drupal/Tests/Core/Render/RendererTest.php | 24 +++++++++----------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index bf4d2c3fde55..5fae8d53501d 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -1126,13 +1126,12 @@ public function testHasRenderContext(): void {
     $this->assertFalse($this->renderer->hasRenderContext());
   }
 
-
   /**
-   * @covers ::executeInRenderContext().
+   * @covers ::executeInRenderContext
    */
   public function testExecuteInRenderContext(): void {
-    $return = $this->renderer->executeInRenderContext(new RenderContext(), function() {
-      $fiber_callback = function() {
+    $return = $this->renderer->executeInRenderContext(new RenderContext(), function () {
+      $fiber_callback = function () {
 
         // Create a #pre_render callback that renders a render array in
         // isolation. This has its own #pre_render callback that calls
@@ -1140,7 +1139,7 @@ public function testExecuteInRenderContext(): void {
         // multiple nested calls to ::executeInRenderContext() doesn't
         // allow render context to get out of sync. This simulates similar
         // conditions to BigPipe placeholder rendering.
-        $fiber_suspend_pre_render = function($elements) {
+        $fiber_suspend_pre_render = function ($elements) {
           $fiber_suspend = function ($elements) {
             \Fiber::suspend();
             return $elements;
@@ -1148,10 +1147,11 @@ public function testExecuteInRenderContext(): void {
           $build = [
             'foo' => [
               '#markup' => 'foo',
-              '#pre_render'=> [$fiber_suspend],
+              '#pre_render' => [$fiber_suspend],
             ],
           ];
-          $this->renderer->renderInIsolation($build);
+          $markup = $this->renderer->renderInIsolation($build);
+          $elements['#markup'] = $markup;
           return $elements;
         };
         $build = [
@@ -1159,13 +1159,12 @@ public function testExecuteInRenderContext(): void {
             '#pre_render' => [$fiber_suspend_pre_render],
           ],
         ];
-        $this->renderer->render($build);
+        return $this->renderer->render($build);
       };
       $fibers = [];
       foreach ([0, 1] as $key) {
         $fibers[] = new \Fiber(function () use ($fiber_callback) {
-          $fiber_callback();
-          return 'hello';
+          return $fiber_callback();
         });
       }
       while ($fibers) {
@@ -1182,12 +1181,11 @@ public function testExecuteInRenderContext(): void {
           }
         }
       }
-      return TRUE;
+      return $fiber->getReturn();
     });
-    $this->assertTrue($return);
+    $this->assertEquals(Markup::create('foo'), $return);
   }
 
-
 }
 
 /**
-- 
GitLab


From 8b63c28e8e28e6cb130dd403074c1c1b466031ec Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 20 Jun 2025 16:02:41 +0100
Subject: [PATCH 10/35] More comments.

---
 core/tests/Drupal/Tests/Core/Render/RendererTest.php | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 5fae8d53501d..62595933f7c4 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -1161,6 +1161,10 @@ public function testExecuteInRenderContext(): void {
         ];
         return $this->renderer->render($build);
       };
+
+      // Build an array of two fibers that executes the code defined above. This
+      // ensures that Fiber::suspend() is called from within two
+      // ::renderInIsolation() calls without either having been completed.
       $fibers = [];
       foreach ([0, 1] as $key) {
         $fibers[] = new \Fiber(function () use ($fiber_callback) {
-- 
GitLab


From 48e70405c8cd8bc186ccd19c27dacaf679394baa Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 20 Jun 2025 16:27:42 +0100
Subject: [PATCH 11/35] One more cache get.

---
 .../OpenTelemetryFrontPagePerformanceTest.php                   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
index 071202256fbc..027d2933a995 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
@@ -50,7 +50,7 @@ protected function testFrontPageColdCache(): void {
 
     $expected = [
       'QueryCount' => 381,
-      'CacheGetCount' => 471,
+      'CacheGetCount' => 472,
       'CacheSetCount' => 467,
       'CacheDeleteCount' => 0,
       'CacheTagLookupQueryCount' => 49,
-- 
GitLab


From 6f761c557e79b760950a4f45c8e43ca02bb37096 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Sat, 21 Jun 2025 08:09:02 +0000
Subject: [PATCH 12/35] Apply 2 suggestion(s) to 2 file(s)

Co-authored-by: godotislate <15754-godotislate@users.noreply.drupalcode.org>
Co-authored-by: Lee Rowlands <23667-larowlan@users.noreply.drupalcode.org>
---
 core/lib/Drupal/Core/Render/Renderer.php             | 4 +---
 core/tests/Drupal/Tests/Core/Render/RendererTest.php | 4 +---
 2 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 641101be29a4..aeaff203227f 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -624,9 +624,7 @@ public function executeInRenderContext(RenderContext $context, callable $callabl
     // Set the provided context and call the callable, it will use that context.
     $this->setCurrentRenderContext($context);
 
-    $fiber = new \Fiber(function () use ($callable) {
-      return $callable();
-    });
+    $fiber = new \Fiber(static fn () => $callable());
     $running = TRUE;
     $fiber->start();
     $result = NULL;
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index 62595933f7c4..e4fbfa60caf7 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -1167,9 +1167,7 @@ public function testExecuteInRenderContext(): void {
       // ::renderInIsolation() calls without either having been completed.
       $fibers = [];
       foreach ([0, 1] as $key) {
-        $fibers[] = new \Fiber(function () use ($fiber_callback) {
-          return $fiber_callback();
-        });
+        $fibers[] = new \Fiber(static fn () => $fiber_callback());
       }
       while ($fibers) {
         foreach ($fibers as $key => $fiber) {
-- 
GitLab


From cad41262d2fa0924af1120307674fe44f8d7dd70 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Sat, 21 Jun 2025 09:20:04 +0100
Subject: [PATCH 13/35] Improve comments.

---
 core/lib/Drupal/Core/Render/Renderer.php | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index aeaff203227f..7efecbea9fa1 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -630,6 +630,11 @@ public function executeInRenderContext(RenderContext $context, callable $callabl
     $result = NULL;
     while ($running) {
       if ($fiber->isSuspended()) {
+        // When ::executeInRenderContext() is executed within a Fiber, which is
+        // always the case when rendering placeholders, if the callback results
+        // in this fiber being suspended, we need to suspend again up to the
+        // parent Fiber. Doing so allows other placeholders to be rendered
+        // before returning here.
         $parent_fiber = \Fiber::getCurrent();
         if ($parent_fiber !== NULL) {
           $this->setCurrentRenderContext($previous_context);
@@ -638,12 +643,12 @@ public function executeInRenderContext(RenderContext $context, callable $callabl
         }
         $fiber->resume();
         if ($parent_fiber === NULL) {
-          // Prevent a spin-lock if a fiber keeps suspending repeatedly.
-          usleep(10);
+          // Prevent a spin-lock if a fiber keeps suspending repeatedly and
+          // there is no parent fiber to suspend upwards to.
+          usleep(100);
         }
       }
-      // 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.
+      // Once the fiber is terminated, exit the while loop.
       if ($fiber->isTerminated()) {
         $running = FALSE;
         $result = $fiber->getReturn();
-- 
GitLab


From 2934ef617808743fe8f7d0ff25be7d3af4f5eaf5 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Sun, 22 Jun 2025 11:10:49 +0100
Subject: [PATCH 14/35] Make OpenTelemetryNodePagePerformanceTest more robust
 for timing issues.

---
 .../OpenTelemetryNodePagePerformanceTest.php                   | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
index dd26c2f12638..3045b218bc41 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
@@ -136,9 +136,12 @@ protected function testNodePageCoolCache(): void {
   protected function testNodePageWarmCache(): void {
     // First of all visit the node page to ensure the image style exists.
     $this->drupalGet('node/1');
+    // Allow time for the image style and asset aggregate requests to finish.
+    sleep(1);
     $this->clearCaches();
     // Now visit a different node page to warm non-path-specific caches.
     $this->drupalGet('node/2');
+    sleep(1);
     $performance_data = $this->collectPerformanceData(function () {
       $this->drupalGet('node/1');
     }, 'umamiNodePageWarmCache');
-- 
GitLab


From b5cd7e1f22c4623498d8f7df33a90d7e6b36c8f1 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Sun, 22 Jun 2025 14:04:06 +0100
Subject: [PATCH 15/35] Move the spin-lock protection to the very end of the
 while loop.

---
 core/lib/Drupal/Core/Render/Renderer.php | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 7efecbea9fa1..0c769ff80f1a 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -642,17 +642,18 @@ public function executeInRenderContext(RenderContext $context, callable $callabl
           $this->setCurrentRenderContext($context);
         }
         $fiber->resume();
-        if ($parent_fiber === NULL) {
-          // Prevent a spin-lock if a fiber keeps suspending repeatedly and
-          // there is no parent fiber to suspend upwards to.
-          usleep(100);
-        }
       }
       // Once the fiber is terminated, exit the while loop.
       if ($fiber->isTerminated()) {
         $running = FALSE;
         $result = $fiber->getReturn();
       }
+      else {
+        // If we've reached this point, then the fiber has already been started
+        // and resumed at least once, so may be suspending repeatedly. Avoid
+        // a spin-lock by waiting for 0.5ms prior to continuing the while loop.
+        usleep(500);
+      }
     }
     assert($context->count() <= 1, 'Bubbling failed.');
 
-- 
GitLab


From 7c7987348911a2156d381075a59480024efd9515 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Mon, 30 Dec 2024 09:16:04 +0000
Subject: [PATCH 16/35] Initial proof of concept for delayed multiple loading
 of entities.

---
 .../Drupal/Core/Entity/EntityStorageBase.php  | 20 +++++++++++++++++++
 .../EntityReferenceFormatterBase.php          |  1 +
 2 files changed, 21 insertions(+)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 3d9a90f76200..5be438224237 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -89,6 +89,11 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
    */
   protected $memoryCacheTag;
 
+  /**
+   * Entity IDs awaiting loading.
+   */
+  protected array $entityIdsToLoad = [];
+
   /**
    * Constructs an EntityStorageBase instance.
    *
@@ -285,6 +290,21 @@ public function loadMultiple(?array $ids = NULL) {
       $ids = array_keys(array_diff_key($flipped_ids, $entities));
     }
 
+    if ($ids) {
+      $fiber = \Fiber::getCurrent();
+      if ($fiber !== NULL) {
+        $this->entityIdsToLoad += $ids;
+
+        $fiber->suspend();
+        // In the meantime, code executed outside the bi
+        $entities_from_cache = $this->getFromStaticCache($ids);
+        // Replace the IDs to load with the full list of entity IDs to load
+        // collected from previous calls to this method up to this point.
+        $ids = array_keys(array_diff_key(array_flip($this->entityIdsToLoad), $entities_from_cache));
+        $this->entityIdsToLoad = [];
+      }
+    }
+
     // Try to gather any remaining entities from a 'preload' method. This method
     // can invoke a hook to be used by modules that need, for example, to swap
     // the default revision of an entity with a different one. Even though the
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php
index 5e91daf6e494..0f08fee5aa72 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php
@@ -145,6 +145,7 @@ public function prepareView(array $entities_items) {
       $target_type = $this->getFieldSetting('target_type');
       $target_entities = \Drupal::entityTypeManager()->getStorage($target_type)->loadMultiple($ids);
     }
+    dump($target_type);
 
     // For each item, pre-populate the loaded entity in $item->entity, and set
     // the 'loaded' flag.
-- 
GitLab


From 4b3f1a5411586d41307a245d185f9ac0f2f6c53f Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 3 Jan 2025 20:04:20 +0000
Subject: [PATCH 17/35] Fix bugs.

---
 .../lib/Drupal/Core/Entity/EntityStorageBase.php | 16 ++++++++--------
 .../EntityReferenceFormatterBase.php             |  2 --
 2 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 5be438224237..6d9d2b3256e7 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -291,18 +291,18 @@ public function loadMultiple(?array $ids = NULL) {
     }
 
     if ($ids) {
+      $this->entityIdsToLoad += $ids;
       $fiber = \Fiber::getCurrent();
       if ($fiber !== NULL) {
-        $this->entityIdsToLoad += $ids;
-
         $fiber->suspend();
-        // In the meantime, code executed outside the bi
-        $entities_from_cache = $this->getFromStaticCache($ids);
-        // Replace the IDs to load with the full list of entity IDs to load
-        // collected from previous calls to this method up to this point.
-        $ids = array_keys(array_diff_key(array_flip($this->entityIdsToLoad), $entities_from_cache));
-        $this->entityIdsToLoad = [];
       }
+      $entities += $this->getFromStaticCache($this->entityIdsToLoad);
+      // Replace the IDs to load with the full list of entity IDs to load
+      // collected from previous calls to this method up to this point.
+      if ($entities)  {
+        $ids = array_keys(array_diff_key(array_flip($this->entityIdsToLoad), $entities));
+      }
+      $this->entityIdsToLoad = [];
     }
 
     // Try to gather any remaining entities from a 'preload' method. This method
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php
index 0f08fee5aa72..70505d0a6505 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php
@@ -145,8 +145,6 @@ public function prepareView(array $entities_items) {
       $target_type = $this->getFieldSetting('target_type');
       $target_entities = \Drupal::entityTypeManager()->getStorage($target_type)->loadMultiple($ids);
     }
-    dump($target_type);
-
     // For each item, pre-populate the loaded entity in $item->entity, and set
     // the 'loaded' flag.
     foreach ($entities_items as $items) {
-- 
GitLab


From 0e1b2311ea784821664131f8c7aad7ef7cdbd8bb Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Fri, 11 Apr 2025 08:01:10 +0100
Subject: [PATCH 18/35] phpcs.

---
 core/lib/Drupal/Core/Entity/EntityStorageBase.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 6d9d2b3256e7..217ef9b6ac08 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -299,7 +299,7 @@ public function loadMultiple(?array $ids = NULL) {
       $entities += $this->getFromStaticCache($this->entityIdsToLoad);
       // Replace the IDs to load with the full list of entity IDs to load
       // collected from previous calls to this method up to this point.
-      if ($entities)  {
+      if ($entities) {
         $ids = array_keys(array_diff_key(array_flip($this->entityIdsToLoad), $entities));
       }
       $this->entityIdsToLoad = [];
-- 
GitLab


From 7eb764a2640a7b05e27472a406e68dfb4995b05a Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Sun, 13 Apr 2025 14:33:06 +0100
Subject: [PATCH 19/35] Only preload when the entity type uses static caching.

---
 .../Drupal/Core/Entity/EntityStorageBase.php   | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 217ef9b6ac08..327ffd8d84a3 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -290,19 +290,19 @@ public function loadMultiple(?array $ids = NULL) {
       $ids = array_keys(array_diff_key($flipped_ids, $entities));
     }
 
-    if ($ids) {
-      $this->entityIdsToLoad += $ids;
+    if ($ids && $this->entityType->isStaticallyCacheable()) {
       $fiber = \Fiber::getCurrent();
       if ($fiber !== NULL) {
+        $this->entityIdsToLoad += $ids;
         $fiber->suspend();
+        $entities += $this->getFromStaticCache($this->entityIdsToLoad);
+        // Replace the IDs to load with the full list of entity IDs to load
+        // collected from previous calls to this method up to this point.
+        if ($entities) {
+          $ids = array_keys(array_diff_key(array_flip($this->entityIdsToLoad), $entities));
+        }
+        $this->entityIdsToLoad = [];
       }
-      $entities += $this->getFromStaticCache($this->entityIdsToLoad);
-      // Replace the IDs to load with the full list of entity IDs to load
-      // collected from previous calls to this method up to this point.
-      if ($entities) {
-        $ids = array_keys(array_diff_key(array_flip($this->entityIdsToLoad), $entities));
-      }
-      $this->entityIdsToLoad = [];
     }
 
     // Try to gather any remaining entities from a 'preload' method. This method
-- 
GitLab


From d25a398468a013b9459887473d7b500e61c230cc Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Tue, 15 Apr 2025 05:24:26 +0100
Subject: [PATCH 20/35] Add test coverage and fix bugs found via test coverage.

---
 .../Drupal/Core/Entity/EntityStorageBase.php  |  5 ++-
 .../KernelTests/Core/Entity/EntityApiTest.php | 45 +++++++++++++++++++
 2 files changed, 49 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 327ffd8d84a3..ea1b13ce2423 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -293,7 +293,7 @@ public function loadMultiple(?array $ids = NULL) {
     if ($ids && $this->entityType->isStaticallyCacheable()) {
       $fiber = \Fiber::getCurrent();
       if ($fiber !== NULL) {
-        $this->entityIdsToLoad += $ids;
+        $this->entityIdsToLoad = array_merge($this->entityIdsToLoad, $ids);
         $fiber->suspend();
         $entities += $this->getFromStaticCache($this->entityIdsToLoad);
         // Replace the IDs to load with the full list of entity IDs to load
@@ -301,6 +301,9 @@ public function loadMultiple(?array $ids = NULL) {
         if ($entities) {
           $ids = array_keys(array_diff_key(array_flip($this->entityIdsToLoad), $entities));
         }
+        else {
+          $ids = $this->entityIdsToLoad;
+        }
         $this->entityIdsToLoad = [];
       }
     }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php
index f4f1b70eac52..aa132b57b3a6 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php
@@ -143,6 +143,51 @@ protected function assertCRUD(string $entity_type, UserInterface $user1): void {
     }
   }
 
+  /**
+   * Test lazy preloading.
+   */
+  public function testLazyPreLoading(): void {
+    $storage = $this->container->get('entity_type.manager')->getStorage('entity_test');
+    $ids = [];
+    $entity = $storage->create(['name' => 'test']);
+    $entity->save();
+    $ids[] = $entity->id();
+
+    $entity = $storage->create(['name' => 'test2']);
+    $entity->save();
+    $ids[] = $entity->id();
+
+    $fiber1 = new \Fiber(function() use ($ids, $storage) {
+      $storage->load($ids[0]);
+    });
+    $fiber2 = new \Fiber(function() use ($ids, $storage) {
+      $storage->load($ids[1]);
+    });
+
+    // Make sure the entity cache is empty.
+    $this->container->get('entity.memory_cache')->reset();
+
+    // Start Fiber 1, this should set the first entity to be loaded, without
+    // actually loading it, and then suspend.
+    $fiber1->start();
+    $this->assertTrue($fiber1->isSuspended());
+    $this->assertFalse($this->container->get('entity.memory_cache')->get('values:entity_test:' . $ids[0]));
+
+    // Start Fiber 2, this should set the first entity to be loaded, without
+    // actually loading it, and then suspend.
+    $fiber2->start();
+    $this->assertTrue($fiber2->isSuspended());
+    $this->assertFalse($this->container->get('entity.memory_cache')->get('values:entity_test:' . $ids[1]));
+
+    $fiber2->resume();
+
+    $this->assertTrue($fiber2->isTerminated());
+
+    // Now both entities should be loaded.
+    $this->assertNotFalse($this->container->get('entity.memory_cache')->get('values:entity_test:' . $ids[0]));
+    $this->assertNotFalse($this->container->get('entity.memory_cache')->get('values:entity_test:' . $ids[1]));
+  }
+
   /**
    * Tests that the Entity storage loads the entities in the correct order.
    *
-- 
GitLab


From 47103dfd10f37cbbccfe4b51e4d2aa113f7b05c3 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Sun, 22 Jun 2025 15:18:31 +0100
Subject: [PATCH 21/35] Arrow functions.

---
 .../Drupal/KernelTests/Core/Entity/EntityApiTest.php      | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php
index aa132b57b3a6..8878e1cc0443 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php
@@ -157,12 +157,8 @@ public function testLazyPreLoading(): void {
     $entity->save();
     $ids[] = $entity->id();
 
-    $fiber1 = new \Fiber(function() use ($ids, $storage) {
-      $storage->load($ids[0]);
-    });
-    $fiber2 = new \Fiber(function() use ($ids, $storage) {
-      $storage->load($ids[1]);
-    });
+    $fiber1 = new \Fiber(fn () => $storage->load($ids[0]));
+    $fiber2 = new \Fiber(fn () => $storage->load($ids[1]));
 
     // Make sure the entity cache is empty.
     $this->container->get('entity.memory_cache')->reset();
-- 
GitLab


From f99aee897ff740c3003cf881b222de3c97d68435 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Sun, 22 Jun 2025 21:37:55 +0100
Subject: [PATCH 22/35] Update OpenTelemetryFrontPagePerformanceTest.

---
 .../OpenTelemetryFrontPagePerformanceTest.php             | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
index 027d2933a995..028fd8e2ed39 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
@@ -49,11 +49,11 @@ protected function testFrontPageColdCache(): void {
     $this->assertSession()->pageTextContains('Umami');
 
     $expected = [
-      'QueryCount' => 381,
-      'CacheGetCount' => 472,
-      'CacheSetCount' => 467,
+      'QueryCount' => 359,
+      'CacheGetCount' => 458,
+      'CacheSetCount' => 452,
       'CacheDeleteCount' => 0,
-      'CacheTagLookupQueryCount' => 49,
+      'CacheTagLookupQueryCount' => 45,
       'CacheTagInvalidationCount' => 0,
       'ScriptCount' => 1,
       'ScriptBytes' => 12000,
-- 
GitLab


From 23f6cf337fd25f6e523ae82892031992d345bd56 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Mon, 23 Jun 2025 10:03:41 +0100
Subject: [PATCH 23/35] Fix issue when returning to the method after entities
 have been loaded elsewhere + test coverage.

---
 core/lib/Drupal/Core/Entity/EntityStorageBase.php | 15 +++++++++++++--
 .../KernelTests/Core/Entity/EntityApiTest.php     |  5 +++++
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index ea1b13ce2423..aef39f77750c 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -293,17 +293,28 @@ public function loadMultiple(?array $ids = NULL) {
     if ($ids && $this->entityType->isStaticallyCacheable()) {
       $fiber = \Fiber::getCurrent();
       if ($fiber !== NULL) {
+        // Before suspending the fiber, add the IDs passed in to the full list
+        // of entities to load, so that another call can load everything at
+        // once.
         $this->entityIdsToLoad = array_merge($this->entityIdsToLoad, $ids);
         $fiber->suspend();
+        // Another call to this method may have reset the entityIdsToLoad
+        // property after loading entities. Combine it with the passed in IDs
+        // again in case this has happened.
+        $this->entityIdsToLoad = array_merge($this->entityIdsToLoad, $ids);
         $entities += $this->getFromStaticCache($this->entityIdsToLoad);
-        // Replace the IDs to load with the full list of entity IDs to load
-        // collected from previous calls to this method up to this point.
         if ($entities) {
+          // Remove any entities found in the static cache from the IDs to load.
           $ids = array_keys(array_diff_key(array_flip($this->entityIdsToLoad), $entities));
         }
         else {
+          // If nothing was found in the static cache, load every entity ID
+          // requested so far.
           $ids = $this->entityIdsToLoad;
         }
+        // Now that we've reached this point, unset the list of entity IDs to
+        // load so that further calls start with a blank slate (apart from the
+        // entity static cache itself).
         $this->entityIdsToLoad = [];
       }
     }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php
index 8878e1cc0443..35eaf6b4c151 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityApiTest.php
@@ -179,9 +179,14 @@ public function testLazyPreLoading(): void {
 
     $this->assertTrue($fiber2->isTerminated());
 
+    $this->assertSame($fiber2->getReturn()->id(), $ids[1]);
+
     // Now both entities should be loaded.
     $this->assertNotFalse($this->container->get('entity.memory_cache')->get('values:entity_test:' . $ids[0]));
     $this->assertNotFalse($this->container->get('entity.memory_cache')->get('values:entity_test:' . $ids[1]));
+    $fiber1->resume();
+    $this->assertTrue($fiber1->isTerminated());
+    $this->assertSame($fiber1->getReturn()->id(), $ids[0]);
   }
 
   /**
-- 
GitLab


From 98ce3f83688354e5c7b913fb77b79bbe8e4bfb0b Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Mon, 23 Jun 2025 10:19:28 +0100
Subject: [PATCH 24/35] += instead of array_merge

---
 core/lib/Drupal/Core/Entity/EntityStorageBase.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index aef39f77750c..27e3b9c6bf9e 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -296,12 +296,12 @@ public function loadMultiple(?array $ids = NULL) {
         // Before suspending the fiber, add the IDs passed in to the full list
         // of entities to load, so that another call can load everything at
         // once.
-        $this->entityIdsToLoad = array_merge($this->entityIdsToLoad, $ids);
+        $this->entityIdsToLoad += $ids;
         $fiber->suspend();
         // Another call to this method may have reset the entityIdsToLoad
         // property after loading entities. Combine it with the passed in IDs
         // again in case this has happened.
-        $this->entityIdsToLoad = array_merge($this->entityIdsToLoad, $ids);
+        $this->entityIdsToLoad += $ids;
         $entities += $this->getFromStaticCache($this->entityIdsToLoad);
         if ($entities) {
           // Remove any entities found in the static cache from the IDs to load.
-- 
GitLab


From b497d49fa4ef174b40aa06267c149c7938b7cc99 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Mon, 23 Jun 2025 10:33:24 +0100
Subject: [PATCH 25/35] Use array_unique(array_merge())

---
 .../Drupal/Core/Entity/EntityStorageBase.php  | 50 +++++++++----------
 1 file changed, 24 insertions(+), 26 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 27e3b9c6bf9e..3c9f13bfa16a 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -290,33 +290,31 @@ public function loadMultiple(?array $ids = NULL) {
       $ids = array_keys(array_diff_key($flipped_ids, $entities));
     }
 
-    if ($ids && $this->entityType->isStaticallyCacheable()) {
-      $fiber = \Fiber::getCurrent();
-      if ($fiber !== NULL) {
-        // Before suspending the fiber, add the IDs passed in to the full list
-        // of entities to load, so that another call can load everything at
-        // once.
-        $this->entityIdsToLoad += $ids;
-        $fiber->suspend();
-        // Another call to this method may have reset the entityIdsToLoad
-        // property after loading entities. Combine it with the passed in IDs
-        // again in case this has happened.
-        $this->entityIdsToLoad += $ids;
-        $entities += $this->getFromStaticCache($this->entityIdsToLoad);
-        if ($entities) {
-          // Remove any entities found in the static cache from the IDs to load.
-          $ids = array_keys(array_diff_key(array_flip($this->entityIdsToLoad), $entities));
-        }
-        else {
-          // If nothing was found in the static cache, load every entity ID
-          // requested so far.
-          $ids = $this->entityIdsToLoad;
-        }
-        // Now that we've reached this point, unset the list of entity IDs to
-        // load so that further calls start with a blank slate (apart from the
-        // entity static cache itself).
-        $this->entityIdsToLoad = [];
+    $fiber = \Fiber::getCurrent();
+    if ($ids && $fiber !== NULL && $this->entityType->isStaticallyCacheable()) {
+      // Before suspending the fiber, add the IDs passed in to the full list
+      // of entities to load, so that another call can load everything at
+      // once.
+      $this->entityIdsToLoad = array_unique(array_merge($this->entityIdsToLoad, $ids));
+      $fiber->suspend();
+      // Another call to this method may have reset the entityIdsToLoad
+      // property after loading entities. Combine it with the passed in IDs
+      // again in case this has happened.
+      $this->entityIdsToLoad = array_unique(array_merge($this->entityIdsToLoad, $ids));
+      $entities += $this->getFromStaticCache($this->entityIdsToLoad);
+      if ($entities) {
+        // Remove any entities found in the static cache from the IDs to load.
+        $ids = array_keys(array_diff_key(array_flip($this->entityIdsToLoad), $entities));
+      }
+      else {
+        // If nothing was found in the static cache, load every entity ID
+        // requested so far.
+        $ids = $this->entityIdsToLoad;
       }
+      // Now that we've reached this point, unset the list of entity IDs to
+      // load so that further calls start with a blank slate (apart from the
+      // entity static cache itself).
+      $this->entityIdsToLoad = [];
     }
 
     // Try to gather any remaining entities from a 'preload' method. This method
-- 
GitLab


From 5a0cc88523230e6edc903a1aa3a7c528053b1dfa Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Mon, 23 Jun 2025 10:46:41 +0100
Subject: [PATCH 26/35] Update assertions now the correct number of operations
 actually happen.

---
 .../OpenTelemetryFrontPagePerformanceTest.php      | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
index 028fd8e2ed39..f86c136630e8 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
@@ -49,11 +49,11 @@ protected function testFrontPageColdCache(): void {
     $this->assertSession()->pageTextContains('Umami');
 
     $expected = [
-      'QueryCount' => 359,
-      'CacheGetCount' => 458,
-      'CacheSetCount' => 452,
+      'QueryCount' => 364,
+      'CacheGetCount' => 466,
+      'CacheSetCount' => 461,
       'CacheDeleteCount' => 0,
-      'CacheTagLookupQueryCount' => 45,
+      'CacheTagLookupQueryCount' => 49,
       'CacheTagInvalidationCount' => 0,
       'ScriptCount' => 1,
       'ScriptBytes' => 12000,
@@ -119,9 +119,9 @@ protected function testFrontPageCoolCache(): void {
     }, 'umamiFrontPageCoolCache');
 
     $expected = [
-      'QueryCount' => 112,
-      'CacheGetCount' => 239,
-      'CacheSetCount' => 93,
+      'QueryCount' => 103,
+      'CacheGetCount' => 236,
+      'CacheSetCount' => 90,
       'CacheDeleteCount' => 0,
       'CacheTagInvalidationCount' => 0,
       'CacheTagLookupQueryCount' => 31,
-- 
GitLab


From 9c06eb87c1048a3b9e19da7a7b15700f9c55d2fa Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Mon, 23 Jun 2025 23:29:56 +0100
Subject: [PATCH 27/35] Tidy up.

---
 core/lib/Drupal/Core/Entity/EntityStorageBase.php |  5 ++---
 core/lib/Drupal/Core/Render/Renderer.php          | 10 ++++------
 core/modules/big_pipe/src/Render/BigPipe.php      |  5 ++---
 3 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 3c9f13bfa16a..180e9c38d342 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -290,13 +290,12 @@ public function loadMultiple(?array $ids = NULL) {
       $ids = array_keys(array_diff_key($flipped_ids, $entities));
     }
 
-    $fiber = \Fiber::getCurrent();
-    if ($ids && $fiber !== NULL && $this->entityType->isStaticallyCacheable()) {
+    if ($ids && \Fiber::getCurrent() !== NULL && $this->entityType->isStaticallyCacheable()) {
       // Before suspending the fiber, add the IDs passed in to the full list
       // of entities to load, so that another call can load everything at
       // once.
       $this->entityIdsToLoad = array_unique(array_merge($this->entityIdsToLoad, $ids));
-      $fiber->suspend();
+      \Fiber::suspend();
       // Another call to this method may have reset the entityIdsToLoad
       // property after loading entities. Combine it with the passed in IDs
       // again in case this has happened.
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 0c769ff80f1a..e5bcbdf20033 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -635,10 +635,9 @@ public function executeInRenderContext(RenderContext $context, callable $callabl
         // in this fiber being suspended, we need to suspend again up to the
         // parent Fiber. Doing so allows other placeholders to be rendered
         // before returning here.
-        $parent_fiber = \Fiber::getCurrent();
-        if ($parent_fiber !== NULL) {
+        if (\Fiber::getCurrent() !== NULL) {
           $this->setCurrentRenderContext($previous_context);
-          $parent_fiber->suspend();
+          \Fiber::suspend();
           $this->setCurrentRenderContext($context);
         }
         $fiber->resume();
@@ -776,9 +775,8 @@ protected function replacePlaceholders(array &$elements) {
           // 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();
+            if (\Fiber::getCurrent() !== NULL) {
+              \Fiber::suspend();
             }
           }
           continue;
diff --git a/core/modules/big_pipe/src/Render/BigPipe.php b/core/modules/big_pipe/src/Render/BigPipe.php
index eaedcda43835..dabf552d9083 100644
--- a/core/modules/big_pipe/src/Render/BigPipe.php
+++ b/core/modules/big_pipe/src/Render/BigPipe.php
@@ -516,9 +516,8 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde
             // 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();
+              if (\Fiber::getCurrent() !== NULL) {
+                \Fiber::suspend();
               }
             }
             continue;
-- 
GitLab


From 2f5077a699c00edc6769b9490aed76ea15c075c5 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Tue, 24 Jun 2025 09:57:26 +0100
Subject: [PATCH 28/35] Removing $running variable.

---
 core/lib/Drupal/Core/Render/Renderer.php | 19 ++++++-------------
 1 file changed, 6 insertions(+), 13 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index e5bcbdf20033..e470b355f288 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -625,10 +625,9 @@ public function executeInRenderContext(RenderContext $context, callable $callabl
     $this->setCurrentRenderContext($context);
 
     $fiber = new \Fiber(static fn () => $callable());
-    $running = TRUE;
     $fiber->start();
     $result = NULL;
-    while ($running) {
+    while (!$fiber->isTerminated()) {
       if ($fiber->isSuspended()) {
         // When ::executeInRenderContext() is executed within a Fiber, which is
         // always the case when rendering placeholders, if the callback results
@@ -642,18 +641,12 @@ public function executeInRenderContext(RenderContext $context, callable $callabl
         }
         $fiber->resume();
       }
-      // Once the fiber is terminated, exit the while loop.
-      if ($fiber->isTerminated()) {
-        $running = FALSE;
-        $result = $fiber->getReturn();
-      }
-      else {
-        // If we've reached this point, then the fiber has already been started
-        // and resumed at least once, so may be suspending repeatedly. Avoid
-        // a spin-lock by waiting for 0.5ms prior to continuing the while loop.
-        usleep(500);
-      }
+      // If we've reached this point, then the fiber has already been started
+      // and resumed at least once, so may be suspending repeatedly. Avoid
+      // a spin-lock by waiting for 0.5ms prior to continuing the while loop.
+      usleep(500);
     }
+    $result = $fiber->getReturn();
     assert($context->count() <= 1, 'Bubbling failed.');
 
     // Restore the original render context.
-- 
GitLab


From cf18c0c74a057f57b8cce7578aae809bbe59ccc4 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Tue, 24 Jun 2025 14:40:30 +0100
Subject: [PATCH 29/35] Convert to a while loop.

---
 core/lib/Drupal/Core/Render/Renderer.php | 53 +++++++++++++-----------
 1 file changed, 28 insertions(+), 25 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index e470b355f288..0336e5359bac 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -626,7 +626,6 @@ public function executeInRenderContext(RenderContext $context, callable $callabl
 
     $fiber = new \Fiber(static fn () => $callable());
     $fiber->start();
-    $result = NULL;
     while (!$fiber->isTerminated()) {
       if ($fiber->isSuspended()) {
         // When ::executeInRenderContext() is executed within a Fiber, which is
@@ -748,40 +747,44 @@ protected function replacePlaceholders(array &$elements) {
       else {
         // Get the render array for the given placeholder
         $fibers[$placeholder] = new \Fiber(function () use ($placeholder_element) {
-          return [$this->doRenderPlaceholder($placeholder_element), $placeholder_element];
+          $return = $this->doRenderPlaceholder($placeholder_element);
+          return [$return, $placeholder_element];
         });
       }
     }
     $iterations = 0;
-    while (count($fibers) > 0) {
-      foreach ($fibers as $placeholder => $fiber) {
-        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) {
-            if (\Fiber::getCurrent() !== NULL) {
-              \Fiber::suspend();
-            }
+    $replaced = [];
+    while ($fibers) {
+      $placeholder = array_rand($fibers);
+      $fiber = $fibers[$placeholder];
+      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) {
+          if (\Fiber::getCurrent() !== NULL) {
+            \Fiber::suspend();
           }
-          continue;
         }
-        [$markup, $placeholder_element] = $fiber->getReturn();
-
-        $elements = $this->doReplacePlaceholder($placeholder, $markup, $elements, $placeholder_element);
+      }
+      if ($fiber->isTerminated()) {
+        $replaced[$placeholder] = $fiber->getReturn();
         unset($fibers[$placeholder]);
       }
-      $iterations++;
     }
+    foreach ($replaced as $placeholder => $replacement) {
+      [$markup, $placeholder_element] = $replacement;
 
+      $elements = $this->doReplacePlaceholder($placeholder, $markup, $elements, $placeholder_element);
+    }
     // Then render 'status messages' placeholders.
     foreach ($message_placeholders as $message_placeholder) {
       $elements = $this->renderPlaceholder($message_placeholder, $elements);
-- 
GitLab


From ff680015f7418dfa1994918d736ad4a3157c6c23 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Tue, 24 Jun 2025 14:09:35 +0000
Subject: [PATCH 30/35] Apply 1 suggestion(s) to 1 file(s)

Co-authored-by: godotislate <15754-godotislate@users.noreply.drupalcode.org>
---
 core/lib/Drupal/Core/Entity/EntityStorageBase.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 180e9c38d342..43562888c60d 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -359,6 +359,7 @@ public function loadMultiple(?array $ids = NULL) {
     // $ids array if this was passed in and remove any invalid IDs.
     if ($flipped_ids) {
       // Remove any invalid IDs from the array and preserve the order passed in.
+      $entities = array_intersect_key($entities, $flipped_ids);
       $flipped_ids = array_intersect_key($flipped_ids, $entities);
       $entities = array_replace($flipped_ids, $entities);
     }
-- 
GitLab


From 8f40a8a90ed16eed22c4f2ef38c3a5404436b330 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Tue, 24 Jun 2025 14:11:25 +0000
Subject: [PATCH 31/35] Apply 2 suggestion(s) to 1 file(s)

Co-authored-by: godotislate <15754-godotislate@users.noreply.drupalcode.org>
---
 core/lib/Drupal/Core/Entity/EntityStorageBase.php | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 43562888c60d..d11091e57e7e 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -91,6 +91,8 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
 
   /**
    * Entity IDs awaiting loading.
+   *
+   * @var list<int|string>
    */
   protected array $entityIdsToLoad = [];
 
@@ -331,7 +333,7 @@ public function loadMultiple(?array $ids = NULL) {
 
       // If any entities were pre-loaded, remove them from the IDs still to
       // load.
-      $ids = array_keys(array_diff_key($flipped_ids, $entities));
+      $ids = array_keys(array_diff_key(array_flip($ids), $entities));
 
       // Add pre-loaded entities to the cache.
       $this->setStaticCache($preloaded_entities);
-- 
GitLab


From 6dd4efb38982228427a24449dc5cb0df4805dcb4 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Tue, 24 Jun 2025 22:13:19 +0100
Subject: [PATCH 32/35] Exclude file entities from preloading.

---
 core/lib/Drupal/Core/Entity/EntityStorageBase.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index d11091e57e7e..e90c0e4c91b8 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -292,7 +292,8 @@ public function loadMultiple(?array $ids = NULL) {
       $ids = array_keys(array_diff_key($flipped_ids, $entities));
     }
 
-    if ($ids && \Fiber::getCurrent() !== NULL && $this->entityType->isStaticallyCacheable()) {
+    // @todo Find out why file entities cause time and space to collapse.
+    if ($ids && \Fiber::getCurrent() !== NULL && $this->entityType->isStaticallyCacheable() && $this->entityTypeId !== 'file') {
       // Before suspending the fiber, add the IDs passed in to the full list
       // of entities to load, so that another call can load everything at
       // once.
-- 
GitLab


From b77901a6ce5f2bcaaf699fc32fc25ded8dc5ff4d Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Tue, 24 Jun 2025 22:35:43 +0100
Subject: [PATCH 33/35] Revert "Convert to a while loop."

This reverts commit cf18c0c74a057f57b8cce7578aae809bbe59ccc4.
---
 core/lib/Drupal/Core/Render/Renderer.php | 53 +++++++++++-------------
 1 file changed, 25 insertions(+), 28 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 0336e5359bac..e470b355f288 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -626,6 +626,7 @@ public function executeInRenderContext(RenderContext $context, callable $callabl
 
     $fiber = new \Fiber(static fn () => $callable());
     $fiber->start();
+    $result = NULL;
     while (!$fiber->isTerminated()) {
       if ($fiber->isSuspended()) {
         // When ::executeInRenderContext() is executed within a Fiber, which is
@@ -747,44 +748,40 @@ protected function replacePlaceholders(array &$elements) {
       else {
         // Get the render array for the given placeholder
         $fibers[$placeholder] = new \Fiber(function () use ($placeholder_element) {
-          $return = $this->doRenderPlaceholder($placeholder_element);
-          return [$return, $placeholder_element];
+          return [$this->doRenderPlaceholder($placeholder_element), $placeholder_element];
         });
       }
     }
     $iterations = 0;
-    $replaced = [];
-    while ($fibers) {
-      $placeholder = array_rand($fibers);
-      $fiber = $fibers[$placeholder];
-      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) {
-          if (\Fiber::getCurrent() !== NULL) {
-            \Fiber::suspend();
+    while (count($fibers) > 0) {
+      foreach ($fibers as $placeholder => $fiber) {
+        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) {
+            if (\Fiber::getCurrent() !== NULL) {
+              \Fiber::suspend();
+            }
           }
+          continue;
         }
-      }
-      if ($fiber->isTerminated()) {
-        $replaced[$placeholder] = $fiber->getReturn();
+        [$markup, $placeholder_element] = $fiber->getReturn();
+
+        $elements = $this->doReplacePlaceholder($placeholder, $markup, $elements, $placeholder_element);
         unset($fibers[$placeholder]);
       }
+      $iterations++;
     }
-    foreach ($replaced as $placeholder => $replacement) {
-      [$markup, $placeholder_element] = $replacement;
 
-      $elements = $this->doReplacePlaceholder($placeholder, $markup, $elements, $placeholder_element);
-    }
     // Then render 'status messages' placeholders.
     foreach ($message_placeholders as $message_placeholder) {
       $elements = $this->renderPlaceholder($message_placeholder, $elements);
-- 
GitLab


From 8d7311c73dc86e6a2399ed2402c6fac4cde7c0ae Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Tue, 24 Jun 2025 22:40:44 +0100
Subject: [PATCH 34/35] Revert "Apply 1 suggestion(s) to 1 file(s)"

This reverts commit ff680015f7418dfa1994918d736ad4a3157c6c23.
---
 core/lib/Drupal/Core/Entity/EntityStorageBase.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index e90c0e4c91b8..5eef28f45c43 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -362,7 +362,6 @@ public function loadMultiple(?array $ids = NULL) {
     // $ids array if this was passed in and remove any invalid IDs.
     if ($flipped_ids) {
       // Remove any invalid IDs from the array and preserve the order passed in.
-      $entities = array_intersect_key($entities, $flipped_ids);
       $flipped_ids = array_intersect_key($flipped_ids, $entities);
       $entities = array_replace($flipped_ids, $entities);
     }
-- 
GitLab


From de7af2a115cab0cbe3ea10e9037db862b5f8f4b7 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Tue, 24 Jun 2025 23:12:14 +0100
Subject: [PATCH 35/35] Update assertions.

---
 .../OpenTelemetryFrontPagePerformanceTest.php     | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
index f86c136630e8..3c59e4ee6c02 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
@@ -40,8 +40,9 @@ protected function testFrontPageColdCache(): void {
     // a non-deterministic test since they happen in parallel and therefore post
     // response tasks run in different orders each time.
     $this->drupalGet('<front>');
+    sleep(1);
     $this->drupalGet('<front>');
-    sleep(2);
+    sleep(1);
     $this->clearCaches();
     $performance_data = $this->collectPerformanceData(function () {
       $this->drupalGet('<front>');
@@ -49,9 +50,9 @@ protected function testFrontPageColdCache(): void {
     $this->assertSession()->pageTextContains('Umami');
 
     $expected = [
-      'QueryCount' => 364,
-      'CacheGetCount' => 466,
-      'CacheSetCount' => 461,
+      'QueryCount' => 366,
+      'CacheGetCount' => 468,
+      'CacheSetCount' => 463,
       'CacheDeleteCount' => 0,
       'CacheTagLookupQueryCount' => 49,
       'CacheTagInvalidationCount' => 0,
@@ -119,9 +120,9 @@ protected function testFrontPageCoolCache(): void {
     }, 'umamiFrontPageCoolCache');
 
     $expected = [
-      'QueryCount' => 103,
-      'CacheGetCount' => 236,
-      'CacheSetCount' => 90,
+      'QueryCount' => 104,
+      'CacheGetCount' => 237,
+      'CacheSetCount' => 91,
       'CacheDeleteCount' => 0,
       'CacheTagInvalidationCount' => 0,
       'CacheTagLookupQueryCount' => 31,
-- 
GitLab