about summary refs log tree commit diff
diff options
context:
space:
mode:
authorOneirical <manchot@videotron.ca>2024-08-01 13:17:34 -0400
committerOneirical <manchot@videotron.ca>2024-08-23 10:29:27 -0400
commit318dfb405f1744dbdb94e83c5ba6969d712c09aa (patch)
treeaf9f3c40e3d3dadc818d1aa3235141b4991933ef
parent0f6e1ae67854c3c44726e8376144c16b465fe7c6 (diff)
downloadrust-318dfb405f1744dbdb94e83c5ba6969d712c09aa.tar.gz
rust-318dfb405f1744dbdb94e83c5ba6969d712c09aa.zip
rewrite libtest-thread-limit to rmake
-rw-r--r--Cargo.lock1
-rw-r--r--src/tools/run-make-support/Cargo.toml1
-rw-r--r--src/tools/run-make-support/src/lib.rs1
-rw-r--r--src/tools/tidy/src/allowed_run_make_makefiles.txt1
-rw-r--r--tests/run-make/libtest-thread-limit/Makefile7
-rw-r--r--tests/run-make/libtest-thread-limit/rmake.rs64
-rw-r--r--tests/run-make/libtest-thread-limit/test.rs7
7 files changed, 73 insertions, 9 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a18219b5683..97146fcfd51 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3129,6 +3129,7 @@ dependencies = [
  "bstr",
  "build_helper",
  "gimli 0.31.0",
+ "libc",
  "object 0.36.3",
  "regex",
  "serde_json",
diff --git a/src/tools/run-make-support/Cargo.toml b/src/tools/run-make-support/Cargo.toml
index 1a13d56b0e4..77df6e7beb5 100644
--- a/src/tools/run-make-support/Cargo.toml
+++ b/src/tools/run-make-support/Cargo.toml
@@ -12,3 +12,4 @@ regex = "1.8" # 1.8 to avoid memchr 2.6.0, as 2.5.0 is pinned in the workspace
 gimli = "0.31.0"
 build_helper = { path = "../build_helper" }
 serde_json = "1.0"
+libc = "0.2"
diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index fc20fd3b2e8..d010ed29f1d 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -36,6 +36,7 @@ pub mod rfs {
 // Re-exports of third-party library crates.
 pub use bstr;
 pub use gimli;
+pub use libc;
 pub use object;
 pub use regex;
 pub use serde_json;
diff --git a/src/tools/tidy/src/allowed_run_make_makefiles.txt b/src/tools/tidy/src/allowed_run_make_makefiles.txt
index f55abb513b8..0f7213c536a 100644
--- a/src/tools/tidy/src/allowed_run_make_makefiles.txt
+++ b/src/tools/tidy/src/allowed_run_make_makefiles.txt
@@ -6,7 +6,6 @@ run-make/incr-add-rust-src-component/Makefile
 run-make/issue-84395-lto-embed-bitcode/Makefile
 run-make/jobserver-error/Makefile
 run-make/libs-through-symlinks/Makefile
-run-make/libtest-thread-limit/Makefile
 run-make/macos-deployment-target/Makefile
 run-make/split-debuginfo/Makefile
 run-make/symbol-mangling-hashed/Makefile
diff --git a/tests/run-make/libtest-thread-limit/Makefile b/tests/run-make/libtest-thread-limit/Makefile
deleted file mode 100644
index 9496fa30159..00000000000
--- a/tests/run-make/libtest-thread-limit/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-include ../tools.mk
-
-# only-linux
-
-all:
-	$(RUSTC) test.rs --test --target $(TARGET)
-	$(shell ulimit -p 0 && $(call RUN,test))
diff --git a/tests/run-make/libtest-thread-limit/rmake.rs b/tests/run-make/libtest-thread-limit/rmake.rs
new file mode 100644
index 00000000000..be0eeaf1717
--- /dev/null
+++ b/tests/run-make/libtest-thread-limit/rmake.rs
@@ -0,0 +1,64 @@
+// libtest used to panic if it hit the thread limit. This often resulted in spurious test failures
+// (thread 'main' panicked at 'called Result::unwrap() on an Err value: Os
+// { code: 11, kind: WouldBlock, message: "Resource temporarily unavailable" }' ...
+// error: test failed, to rerun pass '--lib').
+// Since the fix in #81546, the test should continue to run synchronously
+// if it runs out of threads. Therefore, this test's final execution step
+// should succeed without an error.
+// See https://github.com/rust-lang/rust/pull/81546
+
+//@ only-linux
+// Reason: thread limit modification
+//@ ignore-cross-compile
+// Reason: this test fails armhf-gnu, reasons unknown
+
+use std::ffi::{self, CStr, CString};
+use std::path::PathBuf;
+
+use run_make_support::{libc, run, rustc};
+
+fn main() {
+    rustc().input("test.rs").arg("--test").run();
+
+    // We need to emulate an environment for libtest where threads are exhausted and spawning
+    // new threads are guaranteed to fail. This was previously achieved by ulimit shell builtin
+    // that called out to prlimit64 underneath to set resource limits (specifically thread
+    // number limits). Now that we don't have a shell, we need to implement that ourselves.
+    // See https://linux.die.net/man/2/setrlimit
+
+    // The fork + exec is required because we cannot first try to limit the number of
+    // processes/threads to 1 and then try to spawn a new process to run the test. We need to
+    // setrlimit and run the libtest test program in the same process.
+    let pid = unsafe { libc::fork() };
+    assert!(pid >= 0);
+
+    // If the process ID is 0, this is the child process responsible for running the test
+    // program.
+    if pid == 0 {
+        let test = CString::new("test").unwrap();
+        // The argv array should be terminated with a NULL pointer.
+        let argv = [test.as_ptr(), std::ptr::null()];
+        // rlim_cur is soft limit, rlim_max is hard limit.
+        // By setting the limit very low (max 1), we ensure that libtest is unable to create new
+        // threads.
+        let rlimit = libc::rlimit { rlim_cur: 1, rlim_max: 1 };
+        // RLIMIT_NPROC: The maximum number of processes (or, more precisely on Linux,
+        // threads) that can be created for the real user ID of the calling process. Upon
+        // encountering this limit, fork(2) fails with the error EAGAIN.
+        // Therefore, set the resource limit to RLIMIT_NPROC.
+        let ret = unsafe { libc::setrlimit(libc::RLIMIT_NPROC, &rlimit as *const libc::rlimit) };
+        assert_eq!(ret, 0);
+
+        // Finally, execute the 2 tests in test.rs.
+        let ret = unsafe { libc::execv(test.as_ptr(), argv.as_ptr()) };
+        assert_eq!(ret, 0);
+    } else {
+        // Otherwise, other process IDs indicate that this is the parent process.
+
+        let mut status: libc::c_int = 0;
+        let ret = unsafe { libc::waitpid(pid, &mut status as *mut libc::c_int, 0) };
+        assert_eq!(ret, pid);
+        assert!(libc::WIFEXITED(status));
+        assert_eq!(libc::WEXITSTATUS(status), 0);
+    }
+}
diff --git a/tests/run-make/libtest-thread-limit/test.rs b/tests/run-make/libtest-thread-limit/test.rs
index 87e1d519171..d4eb1242615 100644
--- a/tests/run-make/libtest-thread-limit/test.rs
+++ b/tests/run-make/libtest-thread-limit/test.rs
@@ -10,7 +10,12 @@ fn spawn_thread_would_block() {
     THREAD_ID.set(thread::current().id()).unwrap();
 }
 
+// Tests are run in alphabetical order, and the second test is dependent on the
+// first to set THREAD_ID. Do not rename the tests in such a way that `test_run_in_same_thread`
+// would run before `spawn_thread_would_block`.
+// See https://doc.rust-lang.org/rustc/tests/index.html#--shuffle
+
 #[test]
-fn run_in_same_thread() {
+fn test_run_in_same_thread() {
     assert_eq!(*THREAD_ID.get().unwrap(), thread::current().id());
 }