Skip to content

Add WaitForResourceReadyAsync method with WaitBehavior support and integrate with ResourceNotificationService #10849

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ static bool IsContinuableState(WaitBehavior waitBehavior, CustomResourceSnapshot
/// control this behavior use <see cref="WaitForResourceHealthyAsync(string, WaitBehavior, CancellationToken)"/>
/// or configure the default behavior with <see cref="ResourceNotificationServiceOptions.DefaultWaitBehavior"/>.
/// </para>
/// <para>
/// This method can be used independently of or together with the <see cref="WaitForResourceReadyAsync(string, CancellationToken)"/> method.
/// </para>
/// </remarks>
public async Task<ResourceEvent> WaitForResourceHealthyAsync(string resourceName, CancellationToken cancellationToken = default)
{
Expand All @@ -247,6 +250,92 @@ public async Task<ResourceEvent> WaitForResourceHealthyAsync(string resourceName
cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Waits for a resource to be ready.
/// </summary>
/// <param name="resourceName">The name of the resource.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task.</returns>
Copy link
Preview

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value documentation is incomplete. It should specify that it returns 'A task that completes with a ResourceEvent when the resource is ready.' to be consistent with the WaitForResourceHealthyAsync method documentation.

Suggested change
/// <returns>A task.</returns>
/// <returns>A task that completes with a ResourceEvent when the resource is ready.</returns>

Copilot uses AI. Check for mistakes.

/// <remarks>
/// <para>
/// This method returns a task that completes when all subscriptions to the ResourceReadyEvent
/// have completed (if any). If any throw an exception, this method will throw an exception.
/// If none are present this method will return immediately.
/// </para>
/// <para>
/// This method does not explicitly wait for the resource to be healthy and can be used
/// independently of or together with the <see cref="WaitForResourceHealthyAsync(string, CancellationToken)"/> method.
/// </para>
/// </remarks>
public async Task<ResourceEvent> WaitForResourceReadyAsync(string resourceName, CancellationToken cancellationToken = default)
{
return await WaitForResourceReadyAsync(
resourceName,
DefaultWaitBehavior,
cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Waits for a resource to be ready.
/// </summary>
/// <param name="resourceName">The name of the resource.</param>
/// <param name="waitBehavior">The wait behavior.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task.</returns>
/// <remarks>
/// <para>
/// This method returns a task that completes when all subscriptions to the ResourceReadyEvent
/// have completed (if any). If any throw an exception, this method will throw an exception.
/// If none are present this method will return immediately. The
/// <see cref="WaitBehavior"/> controls how the wait operation behaves when the resource
/// enters an unavailable state such as <see cref="KnownResourceStates.FailedToStart"/>.
/// </para>
/// <para>
/// When <see cref="WaitBehavior.WaitOnResourceUnavailable"/> is specified the wait operation
/// will continue to wait until the resource's ResourceReadyEvent is present.
/// </para>
/// <para>
/// When <see cref="WaitBehavior.StopOnResourceUnavailable"/> is specified the wait operation
/// will throw a <see cref="DistributedApplicationException"/> if the resource enters an
/// unavailable state.
/// </para>
/// <para>
/// This method does not explicitly wait for the resource to be healthy and can be used
/// independently of or together with the <see cref="WaitForResourceHealthyAsync(string, CancellationToken)"/> method.
/// </para>
/// </remarks>
public async Task<ResourceEvent> WaitForResourceReadyAsync(string resourceName, WaitBehavior waitBehavior, CancellationToken cancellationToken = default)
{
_logger.LogDebug("Waiting for resource '{Name}' to be ready.", resourceName);
var resourceEvent = await WaitForResourceCoreAsync(resourceName, re => ShouldYield(waitBehavior, re.Snapshot), cancellationToken: cancellationToken).ConfigureAwait(false);

if (resourceEvent.Snapshot.ResourceReadyEvent is null)
{
_logger.LogError("Stopped waiting for resource '{ResourceName}' to be ready because it failed to start.", resourceName);
throw new DistributedApplicationException($"Stopped waiting for resource '{resourceName}' to be ready because it failed to start.");
}

// Then await the EventTask to complete
await resourceEvent.Snapshot.ResourceReadyEvent.EventTask.WaitAsync(cancellationToken).ConfigureAwait(false);

_logger.LogDebug("Finished waiting for resource '{Name}' to be ready.", resourceName);

return resourceEvent;

// Determine if we should yield based on the wait behavior and the snapshot of the resource.
static bool ShouldYield(WaitBehavior waitBehavior, CustomResourceSnapshot snapshot) =>
waitBehavior switch
{
WaitBehavior.WaitOnResourceUnavailable => snapshot.ResourceReadyEvent is not null,
WaitBehavior.StopOnResourceUnavailable => snapshot.ResourceReadyEvent is not null ||
snapshot.State?.Text == KnownResourceStates.Finished ||
snapshot.State?.Text == KnownResourceStates.Exited ||
snapshot.State?.Text == KnownResourceStates.FailedToStart ||
snapshot.State?.Text == KnownResourceStates.RuntimeUnhealthy,
_ => throw new DistributedApplicationException($"Unexpected wait behavior: {waitBehavior}")
};
}

/// <summary>
/// Waits for a resource to become healthy.
/// </summary>
Expand All @@ -271,6 +360,9 @@ public async Task<ResourceEvent> WaitForResourceHealthyAsync(string resourceName
/// will throw a <see cref="DistributedApplicationException"/> if the resource enters an
/// unavailable state.
/// </para>
/// <para>
/// This method can be used independently of or together with the <see cref="WaitForResourceReadyAsync(string, CancellationToken)"/> method.
/// </para>
/// </remarks>
public async Task<ResourceEvent> WaitForResourceHealthyAsync(string resourceName, WaitBehavior waitBehavior, CancellationToken cancellationToken = default)
{
Expand Down
Loading
Loading