Skip to content

Commit e7e1870

Browse files
Copilotaaronpowelldavidfowl
authored
Fix blocking ParameterResource.Value calls to prevent deadlocks in Aspire 9.4+ (#763)
* Initial plan * Fix DbGate ParameterResource.Value blocking calls Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> * Update tests to use async GetValueAsync instead of blocking .Value calls Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> * Apply IValueProvider cast pattern for GetValueAsync calls as requested Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> * Apply suggestions from code review * Apply suggestions from code review * Working around a compiler bug * Migrate remaining ParameterResource.Value calls to async GetValueAsync pattern Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> * Revert test file migrations to async GetValueAsync pattern Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> * Adding another place where we need to await the parameter resource value * Whoops, stack overflowgit add -A --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> Co-authored-by: David Fowler <davidfowl@gmail.com> Co-authored-by: Aaron Powell <me@aaron-powell.com>
1 parent 2ed8154 commit e7e1870

File tree

12 files changed

+77
-42
lines changed

12 files changed

+77
-42
lines changed

src/CommunityToolkit.Aspire.Hosting.ActiveMQ/ActiveMQBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ private static IResourceBuilder<T> WithJolokiaHealthCheck<T>(
177177
{
178178
Uri baseUri = new Uri(endpoint.Url, UriKind.Absolute);
179179
string userName = (await builder.Resource.UserNameReference.GetValueAsync(ct))!;
180-
string password = builder.Resource.PasswordParameter.Value;
180+
string password = (await ((IValueProvider)builder.Resource.PasswordParameter).GetValueAsync(ct))!;
181181
basicAuthentication = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{userName}:{password}"));
182182
uri = new UriBuilder(baseUri)
183183
{

src/CommunityToolkit.Aspire.Hosting.Minio/MinioBuilderExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ public static IResourceBuilder<MinioContainerResource> AddMinioContainer(
4646
.WithImageRegistry(MinioContainerImageTags.Registry)
4747
.WithHttpEndpoint(targetPort: 9000, port: port, name: MinioContainerResource.PrimaryEndpointName)
4848
.WithHttpEndpoint(targetPort: consoleTargetPort, name: MinioContainerResource.ConsoleEndpointName)
49-
.WithEnvironment(RootUserEnvVarName, resource.RootUser.Value)
50-
.WithEnvironment(RootPasswordEnvVarName, resource.PasswordParameter.Value)
49+
.WithEnvironment(RootUserEnvVarName, $"{resource.RootUser}")
50+
.WithEnvironment(RootPasswordEnvVarName, $"{resource.PasswordParameter}")
5151
.WithArgs("server", "/data", "--console-address", $":{consoleTargetPort}");
5252

5353
var endpoint = builderWithResource.Resource.GetEndpoint(MinioContainerResource.PrimaryEndpointName);

src/CommunityToolkit.Aspire.Hosting.MySql.Extensions/MySqlBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ private static void ConfigureDbGateContainer(EnvironmentCallbackContext context,
109109
context.EnvironmentVariables.Add($"LABEL_mysql{counter}", mySqlServerResource.Name);
110110
context.EnvironmentVariables.Add($"SERVER_mysql{counter}", mySqlServerResource.Name);
111111
context.EnvironmentVariables.Add($"USER_mysql{counter}", "root");
112-
context.EnvironmentVariables.Add($"PASSWORD_mysql{counter}", mySqlServerResource.PasswordParameter.Value);
112+
context.EnvironmentVariables.Add($"PASSWORD_mysql{counter}", mySqlServerResource.PasswordParameter);
113113
context.EnvironmentVariables.Add($"PORT_mysql{counter}", mySqlServerResource.PrimaryEndpoint.TargetPort!.ToString()!);
114114
context.EnvironmentVariables.Add($"ENGINE_mysql{counter}", "mysql@dbgate-plugin-mysql");
115115

src/CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions/PostgresBuilderExtensions.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,16 @@ private static void ConfigureDbGateContainer(EnvironmentCallbackContext context,
105105

106106
foreach (var postgresServer in postgresInstances)
107107
{
108-
var user = postgresServer.UserNameParameter?.Value ?? "postgres";
108+
var userParameter = postgresServer.UserNameParameter is null
109+
? ReferenceExpression.Create($"postgres")
110+
: ReferenceExpression.Create($"{postgresServer.UserNameParameter}");
109111

110112
// DbGate assumes Postgres is being accessed over a default Aspire container network and hardcodes the resource address
111113
// This will need to be refactored once updated service discovery APIs are available
112114
context.EnvironmentVariables.Add($"LABEL_postgres{counter}", postgresServer.Name);
113115
context.EnvironmentVariables.Add($"SERVER_postgres{counter}", postgresServer.Name);
114-
context.EnvironmentVariables.Add($"USER_postgres{counter}", user);
115-
context.EnvironmentVariables.Add($"PASSWORD_postgres{counter}", postgresServer.PasswordParameter.Value);
116+
context.EnvironmentVariables.Add($"USER_postgres{counter}", userParameter);
117+
context.EnvironmentVariables.Add($"PASSWORD_postgres{counter}", postgresServer.PasswordParameter);
116118
context.EnvironmentVariables.Add($"PORT_postgres{counter}", postgresServer.PrimaryEndpoint.TargetPort!.ToString()!);
117119
context.EnvironmentVariables.Add($"ENGINE_postgres{counter}", "postgres@dbgate-plugin-postgres");
118120

src/CommunityToolkit.Aspire.Hosting.Redis.Extensions/RedisBuilderExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ private static void ConfigureDbGateContainer(EnvironmentCallbackContext context,
6565

6666
// DbGate assumes Redis is being accessed over a default Aspire container network and hardcodes the resource address
6767
var redisUrl = redisResource.PasswordParameter is not null ?
68-
$"redis://:{redisResource.PasswordParameter.Value}@{redisResource.Name}:{redisResource.PrimaryEndpoint.TargetPort}" : $"redis://{redisResource.Name}:{redisResource.PrimaryEndpoint.TargetPort}";
68+
ReferenceExpression.Create($"redis://:{redisResource.PasswordParameter}@{redisResource.Name}:{redisResource.PrimaryEndpoint.TargetPort?.ToString()}") :
69+
ReferenceExpression.Create($"redis://{redisResource.Name}:{redisResource.PrimaryEndpoint.TargetPort?.ToString()}");
6970

7071
context.EnvironmentVariables.Add($"LABEL_redis{counter}", redisResource.Name);
7172
context.EnvironmentVariables.Add($"URL_redis{counter}", redisUrl);

src/CommunityToolkit.Aspire.Hosting.SqlServer.Extensions/SqlServerBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ private static void ConfigureDbGateContainer(EnvironmentCallbackContext context,
109109
context.EnvironmentVariables.Add($"LABEL_sqlserver{counter}", sqlServerResource.Name);
110110
context.EnvironmentVariables.Add($"SERVER_sqlserver{counter}", sqlServerResource.Name);
111111
context.EnvironmentVariables.Add($"USER_sqlserver{counter}", "sa");
112-
context.EnvironmentVariables.Add($"PASSWORD_sqlserver{counter}", sqlServerResource.PasswordParameter.Value);
112+
context.EnvironmentVariables.Add($"PASSWORD_sqlserver{counter}", sqlServerResource.PasswordParameter);
113113
context.EnvironmentVariables.Add($"PORT_sqlserver{counter}", sqlServerResource.PrimaryEndpoint.TargetPort!.ToString()!);
114114
context.EnvironmentVariables.Add($"ENGINE_sqlserver{counter}", "mssql@dbgate-plugin-mssql");
115115

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
using Aspire.Hosting.ApplicationModel;
22

33
namespace CommunityToolkit.Aspire.Hosting.Dapr;
4-
internal sealed record DaprComponentConfigurationAnnotation(Action<DaprComponentSchema> Configure) : IResourceAnnotation;
4+
internal sealed record DaprComponentConfigurationAnnotation(Func<DaprComponentSchema, Task> Configure) : IResourceAnnotation;

src/Shared/Dapr/Core/DaprComponentSecretAnnotation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
namespace CommunityToolkit.Aspire.Hosting.Dapr;
44

5-
internal record DaprComponentSecretAnnotation(string Key, string Value) : IResourceAnnotation;
5+
internal record DaprComponentSecretAnnotation(string Key, ParameterResource Value) : IResourceAnnotation;

src/Shared/Dapr/Core/DaprDistributedApplicationLifecycleHook.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Diagnostics.CodeAnalysis;
1313
using System.Globalization;
1414
using System.Net.Sockets;
15+
using System.Threading.Tasks;
1516
using static CommunityToolkit.Aspire.Hosting.Dapr.CommandLineArgs;
1617

1718
namespace CommunityToolkit.Aspire.Hosting.Dapr;
@@ -81,7 +82,7 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
8182
{
8283
foreach (var secretAnnotation in secretAnnotations)
8384
{
84-
secrets[secretAnnotation.Key] = secretAnnotation.Value;
85+
secrets[secretAnnotation.Key] = (await ((IValueProvider)secretAnnotation).GetValueAsync(cancellationToken))!;
8586
}
8687
// We need to append the secret store path to the resources path
8788
onDemandResourcesPaths.TryGetValue("secretstore", out var secretStorePath);
@@ -491,7 +492,7 @@ private async Task<string> GetComponentAsync(DaprComponentResource component, Fu
491492
{
492493
// We should try to read content from a known location (such as aspire root directory)
493494
logger.LogInformation("Unvalidated configuration {specType} for component '{ComponentName}'.", component.Type, component.Name);
494-
return await contentWriter(GetDaprComponent(component, component.Type)).ConfigureAwait(false);
495+
return await contentWriter(await GetDaprComponent(component, component.Type)).ConfigureAwait(false);
495496
}
496497
private async Task<string> GetBuildingBlockComponentAsync(DaprComponentResource component, Func<string, Task<string>> contentWriter, string defaultProvider, CancellationToken cancellationToken)
497498
{
@@ -544,19 +545,19 @@ private static async Task<string> GetDefaultContent(DaprComponentResource compon
544545
string defaultContent = await File.ReadAllTextAsync(defaultContentPath, cancellationToken).ConfigureAwait(false);
545546
string yaml = defaultContent.Replace($"name: {component.Type}", $"name: {component.Name}");
546547
DaprComponentSchema content = DaprComponentSchema.FromYaml(yaml);
547-
ConfigureDaprComponent(component, content);
548+
await ConfigureDaprComponent(component, content);
548549
return content.ToString();
549550
}
550551

551552

552-
private static string GetDaprComponent(DaprComponentResource component, string type)
553+
private static async Task<string> GetDaprComponent(DaprComponentResource component, string type)
553554
{
554555
var content = new DaprComponentSchema(component.Name, type);
555-
ConfigureDaprComponent(component, content);
556+
await ConfigureDaprComponent(component, content);
556557
return content.ToString();
557558
}
558559

559-
private static void ConfigureDaprComponent(DaprComponentResource component, DaprComponentSchema content)
560+
private static async Task ConfigureDaprComponent(DaprComponentResource component, DaprComponentSchema content)
560561
{
561562
if (component.TryGetAnnotationsOfType<DaprComponentSecretAnnotation>(out var secrets) && secrets.Any())
562563
{
@@ -566,7 +567,7 @@ private static void ConfigureDaprComponent(DaprComponentResource component, Dapr
566567
{
567568
foreach (var annotation in annotations)
568569
{
569-
annotation.Configure(content);
570+
await annotation.Configure(content);
570571
}
571572
}
572573
}

src/Shared/Dapr/Core/DaprMetadataResourceBuilderExtensions.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public static IResourceBuilder<IDaprComponentResource> WithMetadata(this IResour
2828
Name = name,
2929
Value = value
3030
});
31+
return Task.CompletedTask;
3132
}));
3233

3334

@@ -42,7 +43,7 @@ public static IResourceBuilder<IDaprComponentResource> WithMetadata(this IResour
4243
{
4344
if (parameterResource.Secret)
4445
{
45-
return builder.WithAnnotation(new DaprComponentSecretAnnotation(parameterResource.Name, parameterResource.Value))
46+
return builder.WithAnnotation(new DaprComponentSecretAnnotation(parameterResource.Name, parameterResource))
4647
.WithAnnotation(new DaprComponentConfigurationAnnotation(schema =>
4748
{
4849
var existing = schema.Spec.Metadata.Find(m => m.Name == name);
@@ -59,9 +60,22 @@ public static IResourceBuilder<IDaprComponentResource> WithMetadata(this IResour
5960
Key = parameterResource.Name
6061
}
6162
});
63+
return Task.CompletedTask;
6264
}));
6365
}
6466

65-
return builder.WithMetadata(name, parameterResource.Value);
67+
return builder.WithAnnotation(new DaprComponentConfigurationAnnotation(async schema =>
68+
{
69+
var existing = schema.Spec.Metadata.Find(m => m.Name == name);
70+
if (existing is not null)
71+
{
72+
schema.Spec.Metadata.Remove(existing);
73+
}
74+
schema.Spec.Metadata.Add(new DaprComponentSpecMetadataValue
75+
{
76+
Name = name,
77+
Value = (await ((IValueProvider)parameterResource).GetValueAsync(default))!
78+
});
79+
}));
6680
}
6781
}

0 commit comments

Comments
 (0)