mirror of
https://github.com/NixOS/nix.git
synced 2025-10-14 02:19:32 +08:00
fix(libstore): fix race condition in AWS credential provider caching
The previous implementation had a check-then-create race condition where multiple threads could simultaneously: 1. Check the cache and find no provider (line 122) 2. Create their own providers (lines 126-145) 3. Insert into cache (line 161) This resulted in multiple credential providers being created when downloading multiple packages in parallel, as each .narinfo download would trigger provider creation on its own thread. Fix by using boost::concurrent_flat_map's try_emplace_and_cvisit, which provides atomic get-or-create semantics: - f1 callback: Called atomically during insertion, creates the provider - f2 callback: Called if key exists, returns cached provider - Other threads are blocked during f1, so no nullptr is ever visible
This commit is contained in:
@@ -118,48 +118,57 @@ AwsCredentials getAwsCredentials(const std::string & profile)
|
||||
// Get or create credential provider with caching
|
||||
std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider;
|
||||
|
||||
// Try to find existing provider
|
||||
credentialProviderCache.visit(profile, [&](const auto & pair) { provider = pair.second; });
|
||||
// Use try_emplace_and_cvisit for atomic get-or-create
|
||||
// This prevents race conditions where multiple threads create providers
|
||||
credentialProviderCache.try_emplace_and_cvisit(
|
||||
profile,
|
||||
nullptr, // Placeholder - will be replaced in f1 before any thread can see it
|
||||
[&](auto & kv) {
|
||||
// f1: Called atomically during insertion with non-const reference
|
||||
// Other threads are blocked until we finish, so nullptr is never visible
|
||||
debug(
|
||||
"[pid=%d] creating new AWS credential provider for profile '%s'",
|
||||
getpid(),
|
||||
profile.empty() ? "(default)" : profile.c_str());
|
||||
|
||||
if (!provider) {
|
||||
// Create new provider if not found
|
||||
debug(
|
||||
"[pid=%d] creating new AWS credential provider for profile '%s'",
|
||||
getpid(),
|
||||
profile.empty() ? "(default)" : profile.c_str());
|
||||
try {
|
||||
initAwsCrt();
|
||||
|
||||
try {
|
||||
initAwsCrt();
|
||||
if (profile.empty()) {
|
||||
Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config;
|
||||
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
|
||||
kv.second = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChainDefault(config);
|
||||
} else {
|
||||
Aws::Crt::Auth::CredentialsProviderProfileConfig config;
|
||||
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
|
||||
// This is safe because the underlying C library will copy this string
|
||||
// c.f. https://github.com/awslabs/aws-c-auth/blob/main/source/credentials_provider_profile.c#L220
|
||||
config.ProfileNameOverride = Aws::Crt::ByteCursorFromCString(profile.c_str());
|
||||
kv.second = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderProfile(config);
|
||||
}
|
||||
|
||||
if (profile.empty()) {
|
||||
Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config;
|
||||
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
|
||||
provider = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChainDefault(config);
|
||||
} else {
|
||||
Aws::Crt::Auth::CredentialsProviderProfileConfig config;
|
||||
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
|
||||
// This is safe because the underlying C library will copy this string
|
||||
// c.f. https://github.com/awslabs/aws-c-auth/blob/main/source/credentials_provider_profile.c#L220
|
||||
config.ProfileNameOverride = Aws::Crt::ByteCursorFromCString(profile.c_str());
|
||||
provider = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderProfile(config);
|
||||
if (!kv.second) {
|
||||
throw AwsAuthError(
|
||||
"Failed to create AWS credentials provider for %s",
|
||||
profile.empty() ? "default profile" : fmt("profile '%s'", profile));
|
||||
}
|
||||
|
||||
provider = kv.second;
|
||||
} catch (Error & e) {
|
||||
// Exception during creation - remove the entry to allow retry
|
||||
credentialProviderCache.erase(profile);
|
||||
e.addTrace({}, "for AWS profile: %s", profile.empty() ? "(default)" : profile);
|
||||
throw;
|
||||
} catch (...) {
|
||||
// Non-Error exception - still need to clean up
|
||||
credentialProviderCache.erase(profile);
|
||||
throw;
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace(
|
||||
{},
|
||||
"while creating AWS credentials provider for %s",
|
||||
profile.empty() ? "default profile" : fmt("profile '%s'", profile));
|
||||
throw;
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
throw AwsAuthError(
|
||||
"Failed to create AWS credentials provider for %s",
|
||||
profile.empty() ? "default profile" : fmt("profile '%s'", profile));
|
||||
}
|
||||
|
||||
// Insert into cache (try_emplace is thread-safe and won't overwrite if another thread added it)
|
||||
credentialProviderCache.try_emplace(profile, provider);
|
||||
}
|
||||
},
|
||||
[&](const auto & kv) {
|
||||
// f2: Called if key already exists (const reference)
|
||||
provider = kv.second;
|
||||
});
|
||||
|
||||
return getCredentialsFromProvider(provider);
|
||||
}
|
||||
|
Reference in New Issue
Block a user