Skip to content

Content pipeline converts cancellation into failure values #360

Description

@bobtista

The content pipeline reports cancellation as a return value rather than an exception at several layers, so callers can't distinguish a cancelled shutdown from a genuine failure.

#357 / #358 fixed this at the two ends — ContentOrchestrator rethrows for the caller's token, and the three profile reconcilers check the token after every orchestrator call. The layers in between were left alone as out of scope for an alpha-4 backport.

Not reachable today — do this with the UI cancellation work, not before

Every current entry point into the pipeline passes CancellationToken.None or omits the token:

Caller Token
ContentBrowserViewModel.SearchAsync none
PublisherCardViewModelAcquireContentAsync none
PublisherCardViewModelProfileContentService explicit CancellationToken.None (×2)
GameProfileLauncherViewModelPublisherProfileOrchestrator none (×4)
ProfileLauncherFacade.LaunchProfileAsync none (both callers)

ProfileContentService and PublisherProfileOrchestrator do thread tokens down to the orchestrator, but their own callers hand them None. PublisherCardViewModel has a real _cts cancelled on dispose, but it only feeds _profileManager — it never reaches the orchestrator.

The only genuinely cancellable path into the pipeline is the reconcilers on shutdown, which #357/#358 already fixed. So no current code path can produce the state described here.

This matters for sequencing. The expensive part isn't adding rethrows — it's deciding, per awaited dependency, whether a failure observed under a cancelled token is cancellation or a real error. With no reachable scenario, that's a guess. Whoever wires real cancellation into the UI (cancel button on downloads, shutdown propagation through browse/acquire) should do this sweep alongside it, so the semantics are chosen against behaviour that can actually be tested.

Remaining sites

  • BaseContentProvider.PrepareContentAsync — bare catch (Exception) converts cancellation to a failed OperationResult
  • SuperHackersProvider.PrepareContentInternalAsync — same, and runs before the base class, so fixing only BaseContentProvider changes nothing for this provider
  • CommunityOutpostProvider / GeneralsOnlineProvider / GitHubContentProvider / LocalFileSystemContentProvider — same shape
  • FileSystemDeliverer, HttpContentDeliverer
  • ContentOrchestrator.RemoveContentAsync — off the reconciler path, same pattern
  • ContentOrchestrator.AcquireContentAsync / EnsureInstallationPoolPathAsync — dependencies returning a failure value after cancellation are still treated as real errors

One trap worth knowing

Filter on the caller's token. HttpClient timeouts surface as TaskCanceledException, which derives from OperationCanceledException, so an unconditional rethrow lets one slow provider abort an entire aggregate search — this was caught in review on #358 after exactly that mistake. Use catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Content-PipelineComponents of the content-pipeline systemMinorSeverity: minor < major < criticalRefactor

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions