diff --git a/library/test/src/lib.rs b/library/test/src/lib.rs
index c38a8c965be..ae38030d497 100644
--- a/library/test/src/lib.rs
+++ b/library/test/src/lib.rs
@@ -506,7 +506,18 @@ pub fn run_test(
         let supports_threads = !cfg!(target_os = "emscripten") && !cfg!(target_arch = "wasm32");
         if concurrency == Concurrent::Yes && supports_threads {
             let cfg = thread::Builder::new().name(name.as_slice().to_owned());
-            Some(cfg.spawn(runtest).unwrap())
+            let mut runtest = Arc::new(Mutex::new(Some(runtest)));
+            let runtest2 = runtest.clone();
+            match cfg.spawn(move || runtest2.lock().unwrap().take().unwrap()()) {
+                Ok(handle) => Some(handle),
+                Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
+                    // `ErrorKind::WouldBlock` means hitting the thread limit on some
+                    // platforms, so run the test synchronously here instead.
+                    Arc::get_mut(&mut runtest).unwrap().get_mut().unwrap().take().unwrap()();
+                    None
+                }
+                Err(e) => panic!("failed to spawn thread to run test: {}", e),
+            }
         } else {
             runtest();
             None
diff --git a/src/test/run-make/libtest-thread-limit/Makefile b/src/test/run-make/libtest-thread-limit/Makefile
new file mode 100644
index 00000000000..29c1bc71d87
--- /dev/null
+++ b/src/test/run-make/libtest-thread-limit/Makefile
@@ -0,0 +1,7 @@
+-include ../../run-make-fulldeps/tools.mk
+
+# only-linux
+
+all:
+	$(RUSTC) test.rs --test --target $(TARGET)
+	$(shell ulimit -p 0 && $(call RUN,test))
diff --git a/src/test/run-make/libtest-thread-limit/test.rs b/src/test/run-make/libtest-thread-limit/test.rs
new file mode 100644
index 00000000000..d899411a49e
--- /dev/null
+++ b/src/test/run-make/libtest-thread-limit/test.rs
@@ -0,0 +1,16 @@
+#![feature(once_cell)]
+
+use std::{io::ErrorKind, lazy::SyncOnceCell, thread::{self, Builder, ThreadId}};
+
+static THREAD_ID: SyncOnceCell<ThreadId> = SyncOnceCell::new();
+
+#[test]
+fn spawn_thread_would_block() {
+    assert_eq!(Builder::new().spawn(|| unreachable!()).unwrap_err().kind(), ErrorKind::WouldBlock);
+    THREAD_ID.set(thread::current().id()).unwrap();
+}
+
+#[test]
+fn run_in_same_thread() {
+    assert_eq!(*THREAD_ID.get().unwrap(), thread::current().id());
+}