diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 5300d76..ced9c4c 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -514,10 +514,12 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia
  * drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
  * @endcode
  *
- * @param string|\Drupal\Component\Utility\SafeStringInterface $message
+ * @param string|array|\Drupal\Component\Utility\SafeStringInterface $message
  *   (optional) The translated message to be displayed to the user. For
  *   consistency with other messages, it should begin with a capital letter and
- *   end with a period.
+ *   end with a period. A render array may also be provided for markup, but
+ *   #attached is not supported. Additionally, any cache tags and contexts are
+ *   ignored, however this is okay because page caching is disabled.
  * @param string $type
  *   (optional) The message's type. Defaults to 'status'. These values are
  *   supported:
@@ -566,6 +568,18 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
       $message = \Drupal\Core\Render\SafeString::create($message);
     }
 
+    // If the message is a render array, render it to a string now to avoid
+    // storing render arrays in $_SESSION, because rendering across requests
+    // may have unpredictable results and some render array functionality
+    // (like assets) should not be supported.
+    if (is_array($message)) {
+      $rendered_message = \Drupal::service('renderer')->renderPlain($message);
+      // The rendering will have all attachments bubbled to #attached in the top
+      // level of $message. Assert that there are none.
+      assert('empty($message[\'#attached\']);', 'Render arrays passed to drupal_set_message() cannot have attachments.');
+      $message = $rendered_message;
+    }
+
     // Do not use strict type checking so that equivalent string and
     // SafeStringInterface objects are detected.
     if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
diff --git a/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php
index 1b63324..1d57018 100644
--- a/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php
+++ b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php
@@ -37,6 +37,15 @@ function testDrupalSetMessage() {
     $this->assertUniqueText('Non Duplicated message');
     $this->assertNoUniqueText('Duplicated message');
 
+    // Ensure render arrays are rendered.
+    $this->assertRaw('Render array with <em>markup!</em>');
+    $this->assertUniqueText('Render array with markup!');
+    $this->assertRaw('Render array 2 with <em>markup!</em>');
+
+    // Ensure render arrays with objects are rendered.
+    $this->assertRaw('Render array with <em>SafeString markup!</em>');
+    $this->assertUniqueText('Render array with SafeString markup!');
+
     // Ensure SafeString objects are rendered as expected.
     $this->assertRaw('SafeString with <em>markup!</em>');
     $this->assertUniqueText('SafeString with markup!');
@@ -45,9 +54,26 @@ function testDrupalSetMessage() {
     // Ensure when the same message is of different types it is not duplicated.
     $this->assertUniqueText('Non duplicate SafeString / string.');
     $this->assertNoUniqueText('Duplicate SafeString / string.');
+    $this->assertUniqueText('Duplicate render array / string');
 
     // Ensure that strings that are not marked as safe are escaped.
     $this->assertEscaped('<em>This<span>markup will be</span> escaped</em>.');
+
+    // Ensure render arrays fail with #attached when assertions are enabled.
+    $this->drupalGet('system-test/drupal-set-message-fail');
+    $this->assertText('Render arrays passed to drupal_set_message() cannot have attachments.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function error($message = '', $group = 'Other', array $caller = NULL) {
+    // @todo Clean this up in https://www.drupal.org/node/2536560.
+    if (strpos($message, 'Render arrays passed to drupal_set_message() cannot have attachments.') !== FALSE) {
+      // We're expecting this error.
+      return FALSE;
+    }
+    return parent::error($message, $group, $caller);
   }
 
 }
diff --git a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
index 15ab478..ddb1a83 100644
--- a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
+++ b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
@@ -114,6 +114,18 @@ public function drupalSetMessageTest() {
     drupal_set_message('Duplicated message', 'status', TRUE);
     drupal_set_message('Duplicated message', 'status', TRUE);
 
+    // Add a render array message.
+    drupal_set_message(['#markup' => 'Render array with <em>markup!</em>']);
+    // Test duplicate render arrays.
+    drupal_set_message(['#markup' => 'Render array with <em>markup!</em>']);
+    // Ensure that multiple render array messages work.
+    drupal_set_message(['#markup' => 'Render array 2 with <em>markup!</em>']);
+
+    // Add a render array message containing objects.
+    drupal_set_message(['#markup' => SafeString::create('Render array with <em>SafeString markup!</em>')]);
+    // Test duplicate render array messages.
+    drupal_set_message(['#markup' => SafeString::create('Render array with <em>SafeString markup!</em>')]);
+
     // Add a SafeString message.
     drupal_set_message(SafeString::create('SafeString with <em>markup!</em>'));
     // Test duplicate SafeString messages.
@@ -126,6 +138,8 @@ public function drupalSetMessageTest() {
     drupal_set_message('Non duplicate SafeString / string.');
     drupal_set_message(SafeString::create('Duplicate SafeString / string.'), 'status', TRUE);
     drupal_set_message('Duplicate SafeString / string.', 'status', TRUE);
+    drupal_set_message(['#markup' => 'Duplicate render array / string']);
+    drupal_set_message('Duplicate render array / string');
 
     // Test auto-escape of non safe strings.
     drupal_set_message('<em>This<span>markup will be</span> escaped</em>.');
@@ -134,6 +148,26 @@ public function drupalSetMessageTest() {
   }
 
   /**
+   * Tests setting messages with a render array containing #attached.
+   *
+   * @return array
+   *   Empty array, we just test the setting of messages.
+   */
+  public function drupalSetMessageFailTest() {
+    // Enable assert testing.
+    // @todo Clean this up in https://www.drupal.org/node/2536560.
+    $old_assert_active = assert_options(ASSERT_ACTIVE, 1);
+
+    $array_with_attachments['foo']['bar']['#markup'] = t('Render array with assets');
+    $array_with_attachments['foo']['bar']['#attached']['drupalSettings'] = ['pecary' => TRUE];
+    drupal_set_message($array_with_attachments);
+
+    // Reset assert settings.
+    assert_options(ASSERT_ACTIVE, $old_assert_active);
+    return [];
+  }
+
+  /**
    * Controller to return $_GET['destination'] for testing.
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
diff --git a/core/modules/system/tests/modules/system_test/system_test.routing.yml b/core/modules/system/tests/modules/system_test/system_test.routing.yml
index ecb9921..8062111 100644
--- a/core/modules/system/tests/modules/system_test/system_test.routing.yml
+++ b/core/modules/system/tests/modules/system_test/system_test.routing.yml
@@ -21,6 +21,14 @@ system_test.drupal_set_message:
   requirements:
     _access: 'TRUE'
 
+system_test.drupal_set_message.fail:
+  path: '/system-test/drupal-set-message-fail'
+  defaults:
+    _title: 'Uses #attached in render array with drupal_set_message()'
+    _controller: '\Drupal\system_test\Controller\SystemTestController::drupalSetMessageFailTest'
+  requirements:
+    _access: 'TRUE'
+
 system_test.main_content_fallback:
   path: '/system-test/main-content-fallback'
   defaults:
