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..3b414af 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,6 +54,7 @@ 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>.');
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..63cd091 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>.');
