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