Change:
I tried this code: I expected it to work on Windows 7 the same as it works on other platforms. Instead, this happened: the code get stuck in the initialization of logging, we acquired same-ish stack from multiple people using Windows 7 It was always calling ! On Windows 10 as under Wine the application starts normally My rust code produces DLL that is loaded by C++ application from 10 years ago Latest working version: Earliest version that gets stuck:
seems possibly relevant, but since this is just hanging, I’m not sure we can blame it all on snake oil this time. Though maybe? Wouldn’t be the first time it breaks the system in incomprehensible ways. First and foremost – could you minimize the reproducer? Both from the perspective of the Rust code (sounds like simply calling from should suffice?) and the OS setup (does this occur on a fresh OS install? If not, what modifications are necessary for the reduced reproducer to start hanging)
The nightly that it first started failing under is the first nightly after was merged, which just happens to be directly related to ? That's really whack given that the stack trace above shows the hang taking place just before any of the changes that PR introduced are invoked.
I personally do not have Windows 7, and would prefer not not bother players with more testing unless really necessary. But I can confirm that at least one of the systems was clean Windows 7 Home Premium from . And calling directly seems pretty complicated :( It requires calling which still requires knowing which algorithm to chose, and what flags to use :( I also see this in the documentation: and this in the code: , but unfortunately I can not say how relevant that it.
It's unlikely to be a problem with itself as that works in the earlier version. Is this code being used in ? In any case, I think the fix here is to simplify the rng code so it's as close to the old version as possible.
Yes logging is initialized in DllMain, I will soon post a very small example
So it is related to it being library. It have something to do with initializing the logs in , but I have no clue what specific small detail it is that breaks it <details<summaryExe code (not much interesting)</summary<p</p</details<details<summaryDLL code</summary rust-toolchain </p</details
Ok, I've submitted a fix. See the PR linked above. is tricky because of the loader lock and other tricks the OS does while a dll is loading.
I know is tricky, but is there any better option to do initialization, if I do not control the exe? And I need to fix the exe (by doing memory patches) before it reaches its .
Possibly not, which is why I filed a fix. Using the std in only works on a "best effort" basis. We do try to support it as much as reasonably possible but there are always going to be limitations due to the OS.
I get maybe a bit crazy idea, but my DLL hooks functions anyway, so if I would limit to and before the hooked main do my initialization. (for my use case any time before main is good enough) Would that eliminate wast majority of potential breaks related to ? Or is it not worth it?
Calling load_library in is susceptible to deadlocks unless the library is already loaded. I think you might just need to be very careful about the dependencies you use. Maybe there's a different or lighter weight logger you can use in place of log4js that doesn't use HashSet?
On further inspection it seems unlikely that fixes the issue although I still think it's a good cleanup and simplification (so I stand by my r+). That is, looking at your back trace... the deadlock you're hitting is from inside BCryptGenRandom itself, not from our code that synchronizes while calling it. It looks like BCryptGenRandom does some synchronizing when getting the preferred RNG (I guess). My hunch is for whatever reason this ends up needing to take the loader lock, and because the loader lock is already held inside , this causes a deadlock. If this is the case, I don't think there's a way for us to solve it... we can't detect it this in advance, nor is there a good alternative approach we can take that doesn't make things worse when outside (and/or on newer systems, which apparently works?). So I'm not sure what we can do about this. To be clear, we (at least at the moment) don't really support calling into libstd from initialization contexts like DllMain. We sometimes fix bugs from this it, but mostly on a best-effort basis, which depending on stuff like severity/impact/maintance burden/hackiness/the weather/etc, all of which is mostly judged on an ad-hoc basis. Even when we fix it, there's no guarantee it'd going to keep working in the future. Given that it's only broken on Win7[^1] and there doesn't seem to be a great approach that wouldn't make things worse outside of it (and/or for on newer windows), I'm inclined to just say... sorry, it's not really supported. Anyway, while things from libstd like HashMap (well, the it uses in particular) is unlikely to be viable in DllMain (at least on win7), I think things from liballoc should be fine (so long as your allocator supports it), so my recommendation might be to switch to using /, which shouldn't have this issue. Another option would be to use a different hasher that doesn't need randomness, but be careful about that. [^1]: It may be worth reconsidering if this happens on newer Windows as well. There are some... unpleasant approaches we could consider, but I don't think they're worth it for Win7 given how close to EOL it is, and the fact that we don't really support use from DllMain anyway.
I am loading an dll from DLL main for years now, and you just told me that Microsoft can screw me over at any time Well thanks for warning. I will keep doing that and hope Microsoft will never change that.
If you scroll down this page it has a list of things Microsoft says you should never do in DllMain:
This is what I was saying before about the stack trace not actually reflecting the changes -- but I also mentioned the bizarre coincidence that this hang bisects to exactly the same nightly as the one with the patch. I agree with keeping the revised changes in all cases and libstd support being a best-effort deal, but I'm guessing that something in how/what gets locked/attempted before the call into changes the state in some unsupported way that caused the bug to surface. I'd be curious what Application Verifier finds if you run it against a minimum repro.
I tried Application Verifier, because I was wondering what it does. My code is in the dll, and it seems to not work for only dll, so I needed to enable the tests for the exe directly. did not catch anything. But what is bit more funny is that enabling causes the application to fail so early that none of my code is executed... (at least if I set it up correctly and understand what it is doing)

Discussion:
use crate::io; use crate::mem; <del> use crate::sync; </del> <ins> use crate::ptr; </ins> use crate::sys::c; <del> /// The kinds of HashMap RNG that may be available #[derive(Clone, Copy, Debug, PartialEq)] enum HashMapRng { Preferred, Fallback, } </del> pub fn hashmap_random_keys() -> (u64, u64) { <del> match get_hashmap_rng() { HashMapRng::Preferred => { preferred_rng().expect("couldn't generate random bytes with preferred RNG") } HashMapRng::Fallback => { fallback_rng().expect("couldn't generate random bytes with fallback RNG") } } } /// Returns the HashMap RNG that should be used /// /// Panics if they are both broken fn get_hashmap_rng() -> HashMapRng { // Assume that if the preferred RNG is broken the first time we use it, it likely means // that: the DLL has failed to load, there is no point to calling it over-and-over again, // and we should cache the result static VALUE: sync::OnceLock<HashMapRng> = sync::OnceLock::new(); *VALUE.get_or_init(choose_hashmap_rng) } /// Test whether we should use the preferred or fallback RNG /// /// If the preferred RNG is successful, we choose it. Otherwise, if the fallback RNG is successful, /// we choose that /// /// Panics if both the preferred and the fallback RNG are both non-functional fn choose_hashmap_rng() -> HashMapRng { let preferred_error = match preferred_rng() { Ok(_) => return HashMapRng::Preferred, Err(e) => e, }; match fallback_rng() { Ok(_) => return HashMapRng::Fallback, Err(fallback_error) => panic!( "preferred RNG broken: `{}`, fallback RNG broken: `{}`", preferred_error, fallback_error ), } } /// Generate random numbers using the preferred RNG function (BCryptGenRandom) fn preferred_rng() -> Result<(u64, u64), io::Error> { use crate::ptr; </del> let mut v = (0, 0); let ret = unsafe { c::BCryptGenRandom(

c::BCRYPT_USE_SYSTEM_PREFERRED_RNG, ) }; <del> if ret == 0 { Ok(v) } else { Err(io::Error::last_os_error()) } </del> <ins> if ret != 0 { fallback_rng() } else { v } </ins> } /// Generate random numbers using the fallback RNG function (RtlGenRandom) #[cfg(not(target_vendor = "uwp"))] <del> fn fallback_rng() -> Result<(u64, u64), io::Error> { </del> <ins> #[inline(never)] fn fallback_rng() -> (u64, u64) { </ins> let mut v = (0, 0); let ret = unsafe { c::RtlGenRandom(&mut v as *mut _ as *mut u8, mem::size_of_val(&v) as c::ULONG) }; <del> if ret != 0 { Ok(v) } else { Err(io::Error::last_os_error()) } </del> <ins> if ret != 0 { v } else { panic!("fallback RNG broken: {}", io::Error::last_os_error()) } </ins> } /// We can't use RtlGenRandom with UWP, so there is no fallback #[cfg(target_vendor = "uwp")] <del> fn fallback_rng() -> Result<(u64, u64), io::Error> { Err(io::const_io_error!(io::ErrorKind::Unsupported, "RtlGenRandom() not supported on UWP")) </del> <ins> #[inline(never)] fn fallback_rng() -> (u64, u64) { panic!("fallback RNG broken: RtlGenRandom() not supported on UWP"); </ins> }