Change:
Hi! I have a project where I define a to use as by linking an function to . I was using Rust 1.80.1 and unit tests were working without issues. However, since bumping to 1.81.0, our CI (Linux runners) started failing though no significant changes had been to the code. The thing is, if you built the project with either version and passed the resulting as it seemed to work without issues. I have set up a minimal reproducer that you can inspect . It is just a "hello world" with a simple unit and integration test, though these tests can be removed and the output will be the same when running the steps below. The reproducer's includes sample commands ran through , as I wrote it from a different OS. the repository and check the code at . with Rust 1.80.1. Should work. with Rust 1.81.0. Should fail with . and call some command passing the resulting as , such as . Should work for Rust 1.80.1. and call some command passing the resulting as , such as . Should work for Rust 1.81.0. I have noticed that if I add to the that stores the function and is linked to the tests run without issues in both versions, but I'm not sure if this is a proper solution and the way to work with testing s or if I'm masking some other problem. I have opened the issue here because I think the failure might lie in how the "test runner executable" is built and ran. If you read the outputs I pasted on the reproducer's for the runs, you'll see that the behavior linked to is present in the test runner as the log line is shown at the beginning, in both Rust toolchain versions. Please let me know if this is not the proper place and point me to the appropriate one to open the report. While I am not very knowledgeable about the internals that might have caused this, I'd like to figure this out, particularly to make sure that this cannot happen on an eventual "production" usage with the compiled project and not only on tests, given the actual problem I'm working with would require performing more than just a and any crash during the preload prevents the actual process from running (placing it in could seemingly brick a host!). I get that this kind of lower level, run-before-main behaviors can be pretty hard to do well, so I really want to be sure I'm doing things right.
Thanks for the report and the detailed reproduction! I have transferred this to rust-lang/rust since this isn't a cargo issue. I believe this was changed by . cc and
Thanks Yeah I was in doubt of where to put it, but since I imagined that managed how the test runner gets built I opened the issue there. Sorry for the inconvenience.
cc
Hi! From that linked thread that I opened we have had a bit of discussion. Just so I have it clear and see if it makes sense to have this bug report open or not: Given the above is true about the call, is this the expected behavior? This is the same doubt written in the Solutions section above: is a way to test code linked to to just make it conditional over not being in a test so it doesn't conflict with the test runner entry-point? I wonder if I might be masking something else that can bite me later. The answer by to the above question is probably_. Aside from the testing suggested, should I fear any other conflicts of this kind? Does the output type or make any difference here?
This falls in the . In short, we don't really guarantee anything in this environment. That said, is one of those features which we have tried to make universally available in the past (e.g. we ensure that the stdio buffer is flushed to ensure output after main is visible), so I think it makes sense to fix this. I have an idea for fixing this, it's just currently blocked on .
I can confirm this when trying to running a test binary compiled to WebAssembly, where I've overridden the methods for getting environment variables. I had some println! calls in there, which started failing in 1.81.0. Removing the println calls makes the test pass again.

Discussion:
sys::init(argc, argv, sigpipe) }; <del> // Set up the current thread to give it the right name. let thread = Thread::new_main(); thread::set_current(thread); </del> <ins> // Set up the current thread handle to give it the right name. // // When code running before main uses `ReentrantLock` (for example by // using `println!`), the thread ID can become initialized before we // create this handle. Since `set_current` fails when the ID of the // handle does not match the current ID, we should attempt to use the // current thread ID here instead of unconditionally creating a new // one. Also see #130210. let thread = Thread::new_main(thread::current_id()); if let Err(_thread) = thread::set_current(thread) { // `thread::current` will create a new handle if none has been set yet. // Thus, if someone uses it before main, this call will fail. That's a // bad idea though, as we then cannot set the main thread name here. // // FIXME: detect the main thread in `thread::current` and use the //        correct name there. rtabort!("code running before main must not use thread::current"); } </ins> } /// Clean up the thread-local runtime state. This *should* be run after all other

} } <del> /// Sets the thread handle for the current thread. /// /// Aborts if the handle or the ID has been set already. pub(crate) fn set_current(thread: Thread) { if CURRENT.get() != NONE || id::get().is_some() { // Using `panic` here can add ~3kB to the binary size. We have complete // control over where this is called, so just abort if there is a bug. rtabort!("thread::set_current should only be called once per thread"); </del> <ins> /// Tries to set the thread handle for the current thread. Fails if a handle was /// already set or if the thread ID of `thread` would change an already-set ID. pub(crate) fn set_current(thread: Thread) -> Result<(), Thread> { if CURRENT.get() != NONE { return Err(thread); </ins> } <del> id::set(thread.id()); </del> <ins> match id::get() { Some(id) if id == thread.id() => {} None => id::set(thread.id()), _ => return Err(thread), } </ins> // Make sure that `crate::rt::thread_cleanup` will be run, which will // call `drop_current`. crate::sys::thread_local::guard::enable(); CURRENT.set(thread.into_raw().cast_mut()); <ins> Ok(()) </ins> } /// Gets the id of the thread that invokes it.

let f = MaybeDangling::new(f); let main = move || { <del> // Immediately store the thread handle to avoid setting it or its ID // twice, which would cause an abort. set_current(their_thread.clone()); </del> <ins> if let Err(_thread) = set_current(their_thread.clone()) { // Both the current thread handle and the ID should not be // initialized yet. Since only the C runtime and some of our // platform code run before this, this point shouldn't be // reachable. Use an abort to save binary size (see #123356). rtabort!("something here is badly broken!"); } </ins> if let Some(name) = their_thread.cname() { imp::Thread::set_name(name); }

pub struct ThreadId(NonZero<u64>); impl ThreadId { <del> // DO NOT rely on this value. const MAIN_THREAD: ThreadId = ThreadId(unsafe { NonZero::new_unchecked(1) }); </del> // Generate a new unique thread ID. pub(crate) fn new() -> ThreadId { #[cold]

if #[cfg(target_has_atomic = "64")] { use crate::sync::atomic::AtomicU64; <del> static COUNTER: AtomicU64 = AtomicU64::new(1); </del> <ins> static COUNTER: AtomicU64 = AtomicU64::new(0); </ins> let mut last = COUNTER.load(Ordering::Relaxed); loop {

} else { use crate::sync::{Mutex, PoisonError}; <del> static COUNTER: Mutex<u64> = Mutex::new(1); </del> <ins> static COUNTER: Mutex<u64> = Mutex::new(0); </ins> let mut counter = COUNTER.lock().unwrap_or_else(PoisonError::into_inner); let Some(id) = counter.checked_add(1) else {

Self::new_inner(id, ThreadName::Unnamed) } <del> // Used in runtime to construct main thread pub(crate) fn new_main() -> Thread { Self::new_inner(ThreadId::MAIN_THREAD, ThreadName::Main) </del> <ins> /// Constructs the thread handle for the main thread. pub(crate) fn new_main(id: ThreadId) -> Thread { Self::new_inner(id, ThreadName::Main) </ins> } fn new_inner(id: ThreadId, name: ThreadName) -> Thread {

<ins> //@ run-pass //@ check-run-results //@ only-gnu //@ only-linux // // Regression test for #130210. // .init_array doesn't work everywhere, so we limit the test to just GNU/Linux. use std::ffi::c_int; use std::thread; #[used] #[link_section = ".init_array"] static INIT: extern "C" fn(c_int, *const *const u8, *const *const u8) = { extern "C" fn init(_argc: c_int, _argv: *const *const u8, _envp: *const *const u8) { print!("Hello from before "); } init }; fn main() { println!("{}!", thread::current().name().unwrap()); } </ins>