Skip to content

CancellationToken Not Honored in IModel.DownloadAsync Method #365

@weiyuanyue

Description

@weiyuanyue

The IModel.DownloadAsync(Action<float>, CancellationToken) method accepts a CancellationToken parameter but does not properly check or honor cancellation requests during the download operation. This results in downloads continuing to completion even after cancellation has been explicitly requested, making it impossible to reliably cancel long-running model downloads.

Environment

  • SDK: Microsoft.AI.Foundry.Local.WinML v0.8.2.1
  • Platform: Windows
  • Application: WinUI 3 Desktop App
  • .NET Version: .NET 8.0

Problem

When calling model.DownloadAsync(progressCallback, cancellationToken) and subsequently canceling the operation via CancellationTokenSource.Cancel(), the download operation continues to execute until completion (100%) despite the cancellation request. The SDK only appears to check the cancellation token during the subsequent LoadAsync() operation, not during the actual download phase.

This behavior violates the standard .NET async cancellation pattern where operations accepting a CancellationToken should periodically check token.IsCancellationRequested and throw OperationCanceledException when cancellation is requested.

Repro Step

  1. Initialize a FoundryLocal catalog and obtain a model reference:
var catalog = await foundryManager.GetCatalogAsync();
var model = await catalog.GetModelAsync(alias);
  1. Start a model download with cancellation support:
var cts = new CancellationTokenSource();
var downloadTask = model.DownloadAsync(
    progressPercent => {
        Debug.WriteLine($"Download progress: {progressPercent}%");
    },
    cts.Token
);
  1. During download (e.g., at 50-60% progress), request cancellation:
cts.Cancel();
  1. Observe that the download continues to 100% completion despite cancellation request.

Actual Behavior

Debug Output Evidence:

[FoundryClient] Download progress: 42.44%
[FoundryClient] Download progress: 49.79%
[App] User clicked cancel button
[App] Calling CancellationTokenSource.Cancel()
[App] CancellationToken AFTER Cancel: IsCancellationRequested: True

[FoundryClient] Progress callback - CancellationToken is REQUESTED!
[FoundryClient] Download progress: 57.19%
[FoundryClient] Download progress: 65.31%
[FoundryClient] Download progress: 73.44%
[FoundryClient] Download progress: 81.56%
[FoundryClient] Download progress: 89.69%
[FoundryClient] Download progress: 97.81%
[FoundryClient] Download progress: 100.00%
[FoundryClient] ========== SDK DownloadAsync COMPLETED ==========

[FoundryClient] Starting model preparation...
[FoundryClient] OperationCanceledException in PrepareModelAsync: The operation was canceled.

Key Observations:

  1. CancellationToken.IsCancellationRequested returns True immediately after Cancel() is called
  2. The progress callback continues to be invoked with increasing percentages (57% → 100%)
  3. Within the progress callback, we can verify token.IsCancellationRequested == True, but the SDK ignores it
  4. DownloadAsync completes successfully without throwing OperationCanceledException
  5. Only the subsequent LoadAsync() operation respects the cancellation token

Expected Behavior

When CancellationToken.Cancel() is called:

  1. The SDK should detect cancellationToken.IsCancellationRequested == True during the download loop
  2. DownloadAsync should stop the download operation promptly
  3. DownloadAsync should throw OperationCanceledException to signal cancellation
  4. Partial downloads should be cleaned up appropriately
  5. The operation should not continue to 100% completion

This aligns with the standard .NET async cancellation pattern documented in Microsoft's async programming guidelines.

Impact

  • Users cannot cancel large model downloads, leading to wasted bandwidth and storage
  • UI "Cancel" buttons appear non-functional, creating confusion
  • No way to interrupt mistaken downloads of large models (multi-GB files)
  • Applications cannot implement reliable cancellation for download operations
  • Poor user experience in applications using Foundry Local

Related Code References

  • IModel.DownloadAsync(Action<float>, CancellationToken) - Primary method affected
  • IModel.LoadAsync(CancellationToken) - Correctly honors cancellation

Additional Context

This issue was discovered during user testing when users attempted to cancel large model downloads. The debug logging was added specifically to trace the cancellation flow, confirming that the application code correctly propagates the cancellation token through all layers, but the SDK's DownloadAsync method does not check it.

The fact that LoadAsync() properly throws OperationCanceledException when the token is canceled demonstrates that the SDK infrastructure supports cancellation - it just needs to be implemented consistently across all async operations, particularly DownloadAsync.


Thank you for considering this issue. Proper cancellation support is critical for production applications managing large model downloads. Please let me know if you need any additional information or testing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions