From 03383ad1021bd0ba34c28d7fda16e634a9ec8df4 Mon Sep 17 00:00:00 2001
From: Josh Stone <jistone@redhat.com>
Date: Thu, 7 Nov 2024 09:51:52 -0800
Subject: [PATCH] Initialize channel `Block`s directly on the heap

The channel's `Block::new` was causing a stack overflow because it held
32 item slots, instantiated on the stack before moving to `Box::new`.
The 32x multiplier made modestly-large item sizes untenable.

That block is now initialized directly on the heap.

Fixes #102246
---
 library/std/src/sync/mpmc/list.rs             |  8 +++---
 .../channel-stack-overflow-issue-102246.rs    | 28 +++++++++++++++++++
 2 files changed, 32 insertions(+), 4 deletions(-)
 create mode 100644 tests/ui/std/channel-stack-overflow-issue-102246.rs

diff --git a/library/std/src/sync/mpmc/list.rs b/library/std/src/sync/mpmc/list.rs
index 88a8c75f7c8..523e6d2f3bb 100644
--- a/library/std/src/sync/mpmc/list.rs
+++ b/library/std/src/sync/mpmc/list.rs
@@ -63,14 +63,14 @@ struct Block<T> {
 
 impl<T> Block<T> {
     /// Creates an empty block.
-    fn new() -> Block<T> {
+    fn new() -> Box<Block<T>> {
         // SAFETY: This is safe because:
         //  [1] `Block::next` (AtomicPtr) may be safely zero initialized.
         //  [2] `Block::slots` (Array) may be safely zero initialized because of [3, 4].
         //  [3] `Slot::msg` (UnsafeCell) may be safely zero initialized because it
         //       holds a MaybeUninit.
         //  [4] `Slot::state` (AtomicUsize) may be safely zero initialized.
-        unsafe { MaybeUninit::zeroed().assume_init() }
+        unsafe { Box::new_zeroed().assume_init() }
     }
 
     /// Waits until the next pointer is set.
@@ -199,13 +199,13 @@ impl<T> Channel<T> {
             // If we're going to have to install the next block, allocate it in advance in order to
             // make the wait for other threads as short as possible.
             if offset + 1 == BLOCK_CAP && next_block.is_none() {
-                next_block = Some(Box::new(Block::<T>::new()));
+                next_block = Some(Block::<T>::new());
             }
 
             // If this is the first message to be sent into the channel, we need to allocate the
             // first block and install it.
             if block.is_null() {
-                let new = Box::into_raw(Box::new(Block::<T>::new()));
+                let new = Box::into_raw(Block::<T>::new());
 
                 if self
                     .tail
diff --git a/tests/ui/std/channel-stack-overflow-issue-102246.rs b/tests/ui/std/channel-stack-overflow-issue-102246.rs
new file mode 100644
index 00000000000..52902fc563a
--- /dev/null
+++ b/tests/ui/std/channel-stack-overflow-issue-102246.rs
@@ -0,0 +1,28 @@
+//@ run-pass
+//@ compile-flags: -Copt-level=0
+
+// The channel's `Block::new` was causing a stack overflow because it held 32 item slots, which is
+// 1MiB for this test's `BigStruct` -- instantiated on the stack before moving to `Box::new`.
+//
+// That block is now initialized directly on the heap.
+//
+// Ref: https://github.com/rust-lang/rust/issues/102246
+
+use std::sync::mpsc::channel;
+use std::thread;
+
+const N: usize = 32_768;
+struct BigStruct {
+    _data: [u8; N],
+}
+
+fn main() {
+    let (sender, receiver) = channel::<BigStruct>();
+
+    let thread1 = thread::spawn(move || {
+        sender.send(BigStruct { _data: [0u8; N] }).unwrap();
+    });
+
+    thread1.join().unwrap();
+    for _data in receiver.try_iter() {}
+}