 core/modules/big_pipe/src/Render/BigPipe.php       | 66 +++++++++++++++--
 .../src/Tests/BigPipePlaceholderTestCases.php      | 82 ++++++++++++++++++++++
 core/modules/big_pipe/src/Tests/BigPipeTest.php    | 72 ++++++++++++++++---
 .../big_pipe_test/big_pipe_test.routing.yml        |  6 +-
 .../big_pipe_test/src/BigPipeTestController.php    | 34 +++++++++
 .../src/EventSubscriber/BigPipeTestSubscriber.php  | 42 ++++++++++-
 .../Render/Placeholder/BigPipeStrategyTest.php     |  6 ++
 7 files changed, 290 insertions(+), 18 deletions(-)

diff --git a/core/modules/big_pipe/src/Render/BigPipe.php b/core/modules/big_pipe/src/Render/BigPipe.php
index 95fe785..967a8ce 100644
--- a/core/modules/big_pipe/src/Render/BigPipe.php
+++ b/core/modules/big_pipe/src/Render/BigPipe.php
@@ -205,6 +205,12 @@ protected function sendPreBody($pre_body, array $no_js_placeholders, AttachedAss
    * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets
    *   The cumulative assets sent so far; to be updated while rendering no-JS
    *   BigPipe placeholders.
+   *
+   * @throws \Exception
+   *   If an exception is thrown during the rendering of a placeholder, it is
+   *   caught to allow the other placeholders to still be replaced. But when
+   *   error logging is configured to be verbose, the exception is rethrown to
+   *   simplify debugging.
    */
   protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) {
     // Split the HTML on every no-JS placeholder string.
@@ -238,7 +244,19 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse
           ],
         ],
       ];
-      $elements = $this->renderPlaceholder($placeholder, $placeholder_plus_cumulative_settings);
+      try {
+        $elements = $this->renderPlaceholder($placeholder, $placeholder_plus_cumulative_settings);
+      }
+      catch (\Exception $e) {
+        if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
+          throw $e;
+        }
+        else {
+          trigger_error($e, E_USER_ERROR);
+          continue;
+        }
+      }
+
 
       // Create a new HtmlResponse. Ensure the CSS and (non-bottom) JS is sent
       // before the HTML they're associated with. In other words: ensure the
@@ -263,7 +281,19 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse
       // - the HTML to load the JS (at the top) can be rendered.
       $fake_request = $this->requestStack->getMasterRequest()->duplicate();
       $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())]);
-      $html_response = $this->filterEmbeddedResponse($fake_request, $html_response);
+      try {
+        $html_response = $this->filterEmbeddedResponse($fake_request, $html_response);
+      }
+      catch (\Exception $e) {
+        if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
+          throw $e;
+        }
+        else {
+          trigger_error($e, E_USER_ERROR);
+          continue;
+        }
+      }
+
 
       // Send this embedded HTML response.
       print $html_response->getContent();
@@ -290,6 +320,12 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse
    * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets
    *   The cumulative assets sent so far; to be updated while rendering BigPipe
    *   placeholders.
+   *
+   * @throws \Exception
+   *   If an exception is thrown during the rendering of a placeholder, it is
+   *   caught to allow the other placeholders to still be replaced. But when
+   *   error logging is configured to be verbose, the exception is rethrown to
+   *   simplify debugging.
    */
   protected function sendPlaceholders(array $placeholders, array $placeholder_order, AttachedAssetsInterface $cumulative_assets) {
     // Return early if there are no BigPipe placeholders to send.
@@ -320,7 +356,18 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde
 
       // Render the placeholder.
       $placeholder_render_array = $placeholders[$placeholder_id];
-      $elements = $this->renderPlaceholder($placeholder_id, $placeholder_render_array);
+      try {
+        $elements = $this->renderPlaceholder($placeholder_id, $placeholder_render_array);
+      }
+      catch (\Exception $e) {
+        if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
+          throw $e;
+        }
+        else {
+          trigger_error($e, E_USER_ERROR);
+          continue;
+        }
+      }
 
       // Create a new AjaxResponse.
       $ajax_response = new AjaxResponse();
@@ -342,7 +389,18 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde
       //   allows us to track the total set of asset libraries sent in the
       //   initial HTML response plus all embedded AJAX responses sent so far.
       $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']);
-      $ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response);
+      try {
+        $ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response);
+      }
+      catch (\Exception $e) {
+        if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
+          throw $e;
+        }
+        else {
+          trigger_error($e, E_USER_ERROR);
+          continue;
+        }
+      }
 
       // Send this embedded AJAX response.
       $json = $ajax_response->getContent();
diff --git a/core/modules/big_pipe/src/Tests/BigPipePlaceholderTestCases.php b/core/modules/big_pipe/src/Tests/BigPipePlaceholderTestCases.php
index 5514eb7..4595844 100644
--- a/core/modules/big_pipe/src/Tests/BigPipePlaceholderTestCases.php
+++ b/core/modules/big_pipe/src/Tests/BigPipePlaceholderTestCases.php
@@ -260,12 +260,94 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
     $current_time->embeddedHtmlResponse = '<time datetime=1991-03-14"></time>';
 
 
+    // 6. Edge case: #lazy_builder that throws an exception.
+    $exception = new BigPipePlaceholderTestCase(
+      [
+        '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
+        '#create_placeholder' => TRUE,
+      ],
+      '<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::exception" arguments="0=llamas&amp;1=suck" token="68a75f1a"></drupal-render-placeholder>',
+      [
+        '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
+      ]
+    );
+    $exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a';
+    $exception->bigPipePlaceholderRenderArray = [
+      '#markup' => '<div data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a"></div>',
+      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
+      '#attached' => [
+        'library' => ['big_pipe/big_pipe'],
+        'drupalSettings' => [
+          'bigPipePlaceholderIds' => [
+            'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=68a75f1a' => TRUE,
+          ],
+        ],
+        'big_pipe_placeholders' => [
+          'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a' => $exception->placeholderRenderArray,
+        ],
+      ],
+    ];
+    $exception->embeddedAjaxResponseCommands = NULL;
+    $exception->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a"></div>';
+    $exception->bigPipeNoJsPlaceholderRenderArray = [
+      '#markup' => $exception->bigPipeNoJsPlaceholder,
+      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
+      '#attached' => [
+        'big_pipe_nojs_placeholders' => [
+          $exception->bigPipeNoJsPlaceholder => $exception->placeholderRenderArray,
+        ],
+      ],
+    ];
+    $exception->embeddedHtmlResponse = NULL;
+
+    // 7. Edge case: response filter throwing an exception for this placeholder.
+    $embedded_response_exception = new BigPipePlaceholderTestCase(
+      [
+        '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
+        '#create_placeholder' => TRUE,
+      ],
+      '<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::responseException" arguments="" token="2a9bd022"></drupal-render-placeholder>',
+      [
+        '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
+      ]
+    );
+    $embedded_response_exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=2a9bd022';
+    $embedded_response_exception->bigPipePlaceholderRenderArray = [
+      '#markup' => '<div data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=2a9bd022"></div>',
+      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
+      '#attached' => [
+        'library' => ['big_pipe/big_pipe'],
+        'drupalSettings' => [
+          'bigPipePlaceholderIds' => [
+            'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=2a9bd022' => TRUE,
+          ],
+        ],
+        'big_pipe_placeholders' => [
+          'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=2a9bd022' => $embedded_response_exception->placeholderRenderArray,
+        ],
+      ],
+    ];
+    $embedded_response_exception->embeddedAjaxResponseCommands = NULL;
+    $embedded_response_exception->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=2a9bd022"></div>';
+    $embedded_response_exception->bigPipeNoJsPlaceholderRenderArray = [
+      '#markup' => $embedded_response_exception->bigPipeNoJsPlaceholder,
+      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
+      '#attached' => [
+        'big_pipe_nojs_placeholders' => [
+          $embedded_response_exception->bigPipeNoJsPlaceholder => $embedded_response_exception->placeholderRenderArray,
+        ],
+      ],
+    ];
+    $exception->embeddedHtmlResponse = NULL;
+
     return [
       'html' => $status_messages,
       'html_attribute_value' => $form_action,
       'html_attribute_value_subset' => $csrf_token,
       'edge_case__invalid_html' => $hello,
       'edge_case__html_non_lazy_builder' => $current_time,
+      'exception__lazy_builder' => $exception,
+      'exception__embedded_response' => $embedded_response_exception,
     ];
   }
 
diff --git a/core/modules/big_pipe/src/Tests/BigPipeTest.php b/core/modules/big_pipe/src/Tests/BigPipeTest.php
index b3f4912..b61e5ef 100644
--- a/core/modules/big_pipe/src/Tests/BigPipeTest.php
+++ b/core/modules/big_pipe/src/Tests/BigPipeTest.php
@@ -11,6 +11,7 @@
 use Drupal\big_pipe\Render\BigPipe;
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Html;
+use Drupal\Core\Logger\RfcLogLevel;
 use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
@@ -32,7 +33,7 @@ class BigPipeTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = ['big_pipe', 'big_pipe_test'];
+  public static $modules = ['big_pipe', 'big_pipe_test', 'dblog'];
 
   /**
    * {@inheritdoc}
@@ -121,13 +122,13 @@ public function testNoJsDetection() {
     $this->cookies = [];
 
     // Edge case: route with '_no_big_pipe' option.
-    $this->drupalGet(Url::fromRoute('big_pipe_test.no_big_pipe'));
+    $this->drupalGet(Url::fromRoute('no_big_pipe'));
     $this->assertSessionCookieExists(FALSE);
     $this->assertBigPipeNoJsCookieExists(FALSE);
     $this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
     $this->assertNoRaw($no_js_to_js_markup);
     $this->drupalLogin($this->rootUser);
-    $this->drupalGet(Url::fromRoute('big_pipe_test.no_big_pipe'));
+    $this->drupalGet(Url::fromRoute('no_big_pipe'));
     $this->assertSessionCookieExists(TRUE);
     $this->assertBigPipeNoJsCookieExists(FALSE);
     $this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
@@ -145,10 +146,15 @@ public function testNoJsDetection() {
    * @see \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases
    */
   public function testBigPipe() {
+    // Simulate production.
+    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
+
     $this->drupalLogin($this->rootUser);
     $this->assertSessionCookieExists(TRUE);
     $this->assertBigPipeNoJsCookieExists(FALSE);
 
+    $log_count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField();
+
     // By not calling performMetaRefresh() here, we simulate JavaScript being
     // enabled, because as far as the BigPipe module is concerned, JavaScript is
     // enabled in the browser as long as the BigPipe no-JS cookie is *not* set.
@@ -167,15 +173,39 @@ public function testBigPipe() {
     $this->assertBigPipePlaceholders([
       $cases['html']->bigPipePlaceholderId                             => Json::encode($cases['html']->embeddedAjaxResponseCommands),
       $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder']->embeddedAjaxResponseCommands),
+      $cases['exception__lazy_builder']->bigPipePlaceholderId          => NULL,
+      $cases['exception__embedded_response']->bigPipePlaceholderId     => NULL,
     ]);
 
+    $this->assertRaw('</body>', 'Closing body tag present.');
+
     $this->pass('Verifying BigPipe assets are present…', 'Debug');
     $this->assertFalse(empty($this->getDrupalSettings()), 'drupalSettings present.');
     $this->assertTrue(in_array('big_pipe/big_pipe', explode(',', $this->getDrupalSettings()['ajaxPageState']['libraries'])), 'BigPipe asset library is present.');
 
+    // Verify that the two expected exceptions are logged as errors.
+    $this->assertEqual($log_count + 2, db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(), 'Two new watchdog entries.');
+    $records = db_query('SELECT * FROM {watchdog} ORDER BY wid DESC LIMIT 2')->fetchAll();
+    $this->assertEqual(RfcLogLevel::ERROR, $records[0]->severity);
+    $this->assertTrue(FALSE !== strpos((string) unserialize($records[0]->variables)['@message'], 'Oh noes!'));
+    $this->assertEqual(RfcLogLevel::ERROR, $records[0]->severity);
+    $this->assertTrue(FALSE !== strpos((string) unserialize($records[1]->variables)['@message'], 'You are not allowed to say llamas are not cool!'));
+
     // Verify that 4xx responses work fine. (4xx responses are handled by
     // subrequests to a route pointing to a controller with the desired output.)
     $this->drupalGet(Url::fromUri('base:non-existing-path'));
+
+    // Simulate development.
+    $this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug');
+    $this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
+    $this->drupalGet(Url::fromRoute('big_pipe_test'));
+    // The 'edge_case__html_exception' case throws an exception.
+    $this->assertRaw('The website encountered an unexpected error. Please try again later');
+    $this->assertRaw('You are not allowed to say llamas are not cool!');
+    $this->assertNoRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal absent: error occurred before then.');
+    $this->assertNoRaw('</body>', 'Closing body tag absent: error occurred before then.');
+    // The exception is expected. Do not interpret it as a test failure.
+    unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
   }
 
   /**
@@ -189,6 +219,9 @@ public function testBigPipe() {
    * @see \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases
    */
   public function testBigPipeNoJs() {
+    // Simulate production.
+    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
+
     $this->drupalLogin($this->rootUser);
     $this->assertSessionCookieExists(TRUE);
     $this->assertBigPipeNoJsCookieExists(FALSE);
@@ -211,6 +244,8 @@ public function testBigPipeNoJs() {
       $cases['html_attribute_value_subset']->bigPipeNoJsPlaceholder       => $cases['html_attribute_value_subset']->embeddedHtmlResponse,
       $cases['html']->bigPipeNoJsPlaceholder                              => $cases['html']->embeddedHtmlResponse,
       $cases['edge_case__html_non_lazy_builder']->bigPipeNoJsPlaceholder  => $cases['edge_case__html_non_lazy_builder']->embeddedHtmlResponse,
+      $cases['exception__lazy_builder']->bigPipePlaceholderId             => NULL,
+      $cases['exception__embedded_response']->bigPipePlaceholderId        => NULL,
     ]);
 
     $this->pass('Verifying there are no BigPipe placeholders & replacements…', 'Debug');
@@ -221,10 +256,22 @@ public function testBigPipeNoJs() {
 
     $this->pass('Verifying BigPipe assets are absent…', 'Debug');
     $this->assertFalse(empty($this->getDrupalSettings()), 'drupalSettings and BigPipe asset library absent.');
+    $this->assertRaw('</body>', 'Closing body tag present.');
 
     // Verify that 4xx responses work fine. (4xx responses are handled by
     // subrequests to a route pointing to a controller with the desired output.)
     $this->drupalGet(Url::fromUri('base:non-existing-path'));
+
+    // Simulate development.
+    $this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug');
+    $this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
+    $this->drupalGet(Url::fromRoute('big_pipe_test'));
+    // The 'edge_case__html_exception' case throws an exception.
+    $this->assertRaw('The website encountered an unexpected error. Please try again later');
+    $this->assertRaw('You are not allowed to say llamas are not cool!');
+    $this->assertNoRaw('</body>', 'Closing body tag absent: error occurred before then.');
+    // The exception is expected. Do not interpret it as a test failure.
+    unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
   }
 
   protected function assertBigPipeResponseHeadersPresent() {
@@ -246,7 +293,10 @@ protected function assertBigPipeNoJsPlaceholders(array $expected_big_pipe_nojs_p
     $this->assertSetsEqual(array_keys($expected_big_pipe_nojs_placeholders), array_map('rawurldecode', explode(' ', $this->drupalGetHeader('BigPipe-Test-No-Js-Placeholders'))));
     foreach ($expected_big_pipe_nojs_placeholders as $big_pipe_nojs_placeholder => $expected_replacement) {
       $this->pass('Checking whether the replacement for the BigPipe no-JS placeholder "' . $big_pipe_nojs_placeholder . '" is present:');
-      $this->assertRaw($expected_replacement);
+      $this->assertNoRaw($big_pipe_nojs_placeholder);
+      if ($expected_replacement !== NULL) {
+        $this->assertRaw($expected_replacement);
+      }
     }
   }
 
@@ -269,9 +319,14 @@ protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholde
       $pos = strpos($this->getRawContent(), $expected_placeholder_html);
       $placeholder_positions[$pos] = $big_pipe_placeholder_id;
       // Verify expected placeholder replacement.
+      $expected_placeholder_replacement = '<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
       $result = $this->xpath('//script[@data-big-pipe-replacement-for-placeholder-with-id=:id]', [':id' => Html::decodeEntities($big_pipe_placeholder_id)]);
+      if ($expected_ajax_response === NULL) {
+        $this->assertEqual(0, count($result));
+        $this->assertNoRaw($expected_placeholder_replacement);
+        continue;
+      }
       $this->assertEqual($expected_ajax_response, trim((string) $result[0]));
-      $expected_placeholder_replacement = '<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
       $this->assertRaw($expected_placeholder_replacement);
       $pos = strpos($this->getRawContent(), $expected_placeholder_replacement);
       $placeholder_replacement_positions[$pos] = $big_pipe_placeholder_id;
@@ -279,8 +334,9 @@ protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholde
     ksort($placeholder_positions, SORT_NUMERIC);
     $this->assertEqual(array_keys($expected_big_pipe_placeholders), array_values($placeholder_positions));
     $this->assertEqual(count($expected_big_pipe_placeholders), preg_match_all('/' . preg_quote('<div data-big-pipe-placeholder-id="', '/') . '/', $this->getRawContent()));
-    $this->assertEqual(array_keys($expected_big_pipe_placeholders), array_values($placeholder_replacement_positions));
-    $this->assertEqual(count($expected_big_pipe_placeholders), preg_match_all('/' . preg_quote('<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="', '/') . '/', $this->getRawContent()));
+    $expected_big_pipe_placeholders_with_replacements = array_filter($expected_big_pipe_placeholders);
+    $this->assertEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
+    $this->assertEqual(count($expected_big_pipe_placeholders_with_replacements), preg_match_all('/' . preg_quote('<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="', '/') . '/', $this->getRawContent()));
 
     $this->pass('Verifying BigPipe start/stop signals…', 'Debug');
     $this->assertRaw(BigPipe::START_SIGNAL, 'BigPipe start signal present.');
@@ -290,7 +346,7 @@ protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholde
     $this->assertTrue($start_signal_position < $stop_signal_position, 'BigPipe start signal appears before stop signal.');
 
     $this->pass('Verifying BigPipe placeholder replacements and start/stop signals were streamed in the correct order…', 'Debug');
-    $expected_stream_order = array_keys($expected_big_pipe_placeholders);
+    $expected_stream_order = array_keys($expected_big_pipe_placeholders_with_replacements);
     array_unshift($expected_stream_order, BigPipe::START_SIGNAL);
     array_push($expected_stream_order, BigPipe::STOP_SIGNAL);
     $actual_stream_order = $placeholder_replacement_positions + [
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml b/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml
index e573913..4979406 100644
--- a/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml
+++ b/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml
@@ -6,11 +6,11 @@ big_pipe_test:
   requirements:
     _access: 'TRUE'
 
-big_pipe_test.no_big_pipe:
+no_big_pipe:
   path: '/no_big_pipe'
   defaults:
-    _controller: '\Drupal\big_pipe_test\BigPipeTestController::test'
-    _title: 'BigPipe test'
+    _controller: '\Drupal\big_pipe_test\BigPipeTestController::nope'
+    _title: '_no_big_pipe route option test'
   options:
     _no_big_pipe: TRUE
   requirements:
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
index 3a663fc..2ac5a99 100644
--- a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
+++ b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
@@ -3,6 +3,7 @@
 namespace Drupal\big_pipe_test;
 
 use Drupal\big_pipe\Render\BigPipeMarkup;
+use Drupal\big_pipe_test\EventSubscriber\BigPipeTestSubscriber;
 
 class BigPipeTestController {
 
@@ -34,10 +35,23 @@ public function test() {
     // 5. Edge case: non-#lazy_builder placeholder.
     $build['edge_case__html_non_lazy_builder'] = $cases['edge_case__html_non_lazy_builder']->renderArray;
 
+    // 6. Exception: #lazy_builder that throws an exception.
+    $build['exception__lazy_builder'] = $cases['exception__lazy_builder']->renderArray;
+
+    // 7. Exception: placeholder that causes response filter to throw exception.
+    $build['exception__embedded_response'] = $cases['exception__embedded_response']->renderArray;
+
     return $build;
   }
 
   /**
+   * @return array
+   */
+  public static function nope() {
+    return ['#markup' => '<p>Nope.</p>'];
+  }
+
+  /**
    * #lazy_builder callback; builds <time> markup with current time.
    *
    * Note: does not actually use current time, that would complicate testing.
@@ -63,4 +77,24 @@ public static function helloOrYarhar() {
     ];
   }
 
+  /**
+   * #lazy_builder callback; throws exception.
+   *
+   * @throws \Exception
+   */
+  public static function exception() {
+    throw new \Exception('You are not allowed to say llamas are not cool!');
+  }
+
+  /**
+   * #lazy_builder callback; returns content that will trigger an exception.
+   *
+   * @see \Drupal\big_pipe_test\EventSubscriber\BigPipeTestSubscriber::onRespondTriggerException()
+   *
+   * @return array
+   */
+  public static function responseException() {
+    return ['#plain_text' => BigPipeTestSubscriber::CONTENT_TRIGGER_EXCEPTION];
+  }
+
 }
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/src/EventSubscriber/BigPipeTestSubscriber.php b/core/modules/big_pipe/tests/modules/big_pipe_test/src/EventSubscriber/BigPipeTestSubscriber.php
index 6af1028..3cf9bca 100644
--- a/core/modules/big_pipe/tests/modules/big_pipe_test/src/EventSubscriber/BigPipeTestSubscriber.php
+++ b/core/modules/big_pipe/tests/modules/big_pipe_test/src/EventSubscriber/BigPipeTestSubscriber.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\big_pipe_test\EventSubscriber;
 
+use Drupal\Core\Render\AttachmentsInterface;
 use Drupal\Core\Render\HtmlResponse;
 use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
 use Symfony\Component\HttpKernel\KernelEvents;
@@ -15,12 +16,44 @@
 class BigPipeTestSubscriber implements EventSubscriberInterface {
 
   /**
+   * @see \Drupal\big_pipe_test\BigPipeTestController::responseException()
+   *
+   * @var string
+   */
+  const CONTENT_TRIGGER_EXCEPTION = 'NOPE!NOPE!NOPE!';
+
+  /**
+   * Triggers exception for embedded HTML/AJAX responses with certain content.
+   *
+   * @see \Drupal\big_pipe_test\BigPipeTestController::responseException()
+   *
+   * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+   *   The event to process.
+   *
+   * @throws \Exception
+   */
+  public function onRespondTriggerException(FilterResponseEvent $event) {
+    $response = $event->getResponse();
+
+    if (!$response instanceof AttachmentsInterface) {
+      return;
+    }
+
+    $attachments = $response->getAttachments();
+    if (!isset($attachments['big_pipe_placeholders']) && !isset($attachments['big_pipe_nojs_placeholders'])) {
+      if (strpos($response->getContent(), static::CONTENT_TRIGGER_EXCEPTION) !== FALSE) {
+        throw new \Exception('Oh noes!');
+      }
+    }
+  }
+
+  /**
    * Exposes all BigPipe placeholders (JS and no-JS) via headers for testing.
    *
    * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
    *   The event to process.
    */
-  public function onRespond(FilterResponseEvent $event) {
+  public function onRespondSetBigPipeDebugPlaceholderHeaders(FilterResponseEvent $event) {
     $response = $event->getResponse();
     if (!$response instanceof HtmlResponse) {
       return;
@@ -44,8 +77,11 @@ public function onRespond(FilterResponseEvent $event) {
    * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
-    // Run *just* before \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber::onRespond().
-    $events[KernelEvents::RESPONSE][] = ['onRespond', -99999];
+    // Run just before \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber::onRespond().
+    $events[KernelEvents::RESPONSE][] = ['onRespondSetBigPipeDebugPlaceholderHeaders', -9999];
+
+    // Run just after \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber::onRespond().
+    $events[KernelEvents::RESPONSE][] = ['onRespondTriggerException', -10001];
 
     return $events;
   }
diff --git a/core/modules/big_pipe/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php b/core/modules/big_pipe/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php
index d726a83..da78dd2 100644
--- a/core/modules/big_pipe/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php
+++ b/core/modules/big_pipe/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php
@@ -75,6 +75,8 @@ public function placeholdersProvider() {
       $cases['html_attribute_value_subset']->placeholder      => $cases['html_attribute_value_subset']->placeholderRenderArray,
       $cases['edge_case__invalid_html']->placeholder          => $cases['edge_case__invalid_html']->placeholderRenderArray,
       $cases['edge_case__html_non_lazy_builder']->placeholder => $cases['edge_case__html_non_lazy_builder']->placeholderRenderArray,
+      $cases['exception__lazy_builder']->placeholder          => $cases['exception__lazy_builder']->placeholderRenderArray,
+      $cases['exception__embedded_response']->placeholder     => $cases['exception__embedded_response']->placeholderRenderArray,
     ];
 
     return [
@@ -90,6 +92,8 @@ public function placeholdersProvider() {
         $cases['html_attribute_value_subset']->placeholder      => $cases['html_attribute_value_subset']->bigPipeNoJsPlaceholderRenderArray,
         $cases['edge_case__invalid_html']->placeholder          => $cases['edge_case__invalid_html']->bigPipeNoJsPlaceholderRenderArray,
         $cases['edge_case__html_non_lazy_builder']->placeholder => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderRenderArray,
+        $cases['exception__lazy_builder']->placeholder          => $cases['exception__lazy_builder']->bigPipePlaceholderRenderArray,
+        $cases['exception__embedded_response']->placeholder     => $cases['exception__embedded_response']->bigPipePlaceholderRenderArray,
       ]],
       '_no_big_pipe absent, session, no-JS cookie present: no-JS BigPipe placeholder used for HTML placeholders' => [$placeholders, FALSE, TRUE, TRUE, [
         $cases['html']->placeholder                             => $cases['html']->bigPipeNoJsPlaceholderRenderArray,
@@ -97,6 +101,8 @@ public function placeholdersProvider() {
         $cases['html_attribute_value_subset']->placeholder      => $cases['html_attribute_value_subset']->bigPipeNoJsPlaceholderRenderArray,
         $cases['edge_case__invalid_html']->placeholder          => $cases['edge_case__invalid_html']->bigPipeNoJsPlaceholderRenderArray,
         $cases['edge_case__html_non_lazy_builder']->placeholder => $cases['edge_case__html_non_lazy_builder']->bigPipeNoJsPlaceholderRenderArray,
+        $cases['exception__lazy_builder']->placeholder          => $cases['exception__lazy_builder']->bigPipeNoJsPlaceholderRenderArray,
+        $cases['exception__embedded_response']->placeholder     => $cases['exception__embedded_response']->bigPipeNoJsPlaceholderRenderArray,
       ]],
     ];
   }
