Skip to content

fix(folder-tree): preserve Outlook STA affinity during traversal - #422

Merged
drmoisan merged 5 commits into
mainfrom
bug/folder-tree-dispatcher-thread-affinity-420
Aug 7, 2026
Merged

fix(folder-tree): preserve Outlook STA affinity during traversal#422
drmoisan merged 5 commits into
mainfrom
bug/folder-tree-dispatcher-thread-affinity-420

Conversation

@drmoisan

@drmoisan drmoisan commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Suggested title: fix(folder-tree): preserve Outlook STA affinity during traversal

Summary

  • Marshals folder-tree service composition, cold builds, refreshes, and notification cleanup through the captured Outlook STA dispatcher.
  • Retains dispatcher context across deadline-driven traversal yields while keeping WpfDispatcherYield strict.
  • Replaces synchronous FilterOlFolders initialization waiting with an awaited path and prevents UI or subscription mutation after disposal.
  • Preserves cache coalescing, cancellation, invalidation, publication, retry, and cleanup semantics.
  • Adds deterministic MSTest coverage for dispatcher affinity and lifecycle races.

Why

A cold folder-tree request initiated by EmailDataMiner on a worker could reach WpfDispatcherYield without a current WPF dispatcher and raise InvalidOperationException. A Task.Yield fallback would avoid that immediate exception but would not restore the Outlook STA requirement for subsequent live COM access. The repair therefore establishes dispatcher ownership at service composition and retains it throughout live traversal.

What Changed

  • Composes the session-scoped folder-tree service, live hierarchy reader, and notification sink on the captured Outlook STA dispatcher.
  • Dispatches every uncached build, refresh, and required cleanup through that dispatcher.
  • Retains dispatcher context in the snapshot builder and hierarchy reader after deadline-controlled yields.
  • Awaits FilterOlFolders cold initialization, tracks viewer ownership explicitly, and gates view commits and subscription changes after disposal.
  • Observes ribbon initialization failures without replacing the original exception.
  • Adds focused tests for worker-originated cold builds, post-yield affinity, service coalescing, cancellation, notification cleanup, refresh/disposal races, and post-disposal UI suppression.
  • Updates the issue specification, remediation plan, QA evidence, final inventory, and review artifacts.

Architecture / How It Fits Together

A worker caller enters the session-scoped AppOlObjects service gate. Composition and live operations are marshaled through the captured IUiDispatcher/WpfUiDispatcher to OutlookFolderTreeService, then to FolderTreeSnapshotBuilder and OutlookFolderHierarchyReader. WpfDispatcherYield runs only within that dispatcher context, and the immutable snapshot is returned to the caller after traversal. FilterOlFolders awaits the snapshot and commits UI state only while its viewer and subscriptions remain owned and undisposed.

Verification

Completed

  • dotnet tool run csharpier format . completed successfully; dotnet tool run csharpier check . reported no formatting changes.
  • msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true completed with 0 errors.
  • msbuild TaskMaster.sln /t:Build /p:Configuration=Debug /p:Platform='Any CPU' /p:Nullable=enable /p:TreatWarningsAsErrors=true completed with 0 errors and no nullable diagnostics.
  • pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/vscode/Invoke-MSTestWithCoverage.ps1 -SearchRoot . -Configuration Debug -CoverageOutput docs/features/active/2026-08-04-folder-tree-dispatcher-thread-affinity-420/evidence/qa-gates/remediation-cycle4-coverage-final.cobertura.xml passed 6,166 of 6,166 tests across eight assemblies.
  • Repository coverage was 93,687/110,478 lines (84.8015%) and 21,459/27,698 branches (77.4749%). Changed production coverage was 879/881 lines (99.7730%); every required changed target method, class, and module met the 90% gate.
  • git diff --check origin/main completed with no whitespace errors.
  • The final feature review reported REVIEW_STATUS: PASS, and the policy, code-review, and feature-audit artifacts passed validation.

Completed after rebase onto origin/main @ 02ebd260

The branch was rebased onto current main (which now contains the issue #418 work merged via PR #421). The five feature commits replayed with no conflicts. The following gates were re-run against the rebased head 58b51676:

  • dotnet csharpier check . — checked 1,479 files, no formatting changes.
  • msbuild TaskMaster.sln /t:Build /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true — exit 0.
  • msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true — exit 0, no errors.

Recommended

  • Exercise a worker-originated cold-cache build, deadline yield, refresh, and disposal in the live Outlook VSTO host.
  • Confirm the repository's required GitHub checks remain green for the final PR head.

Backward Compatibility / Migration Notes

No migration is required. IUiDispatcher.BeginInvoke(Action): IAsyncResult remains available, the additional dispatcher operations are additive, and existing public FilterOlFolders call paths remain compatible. No package, configuration, or external-service dependency was added.

Risks and Mitigations

  • Outlook COM access is thread-affine. The service now owns one captured STA dispatcher, and dedicated-STA tests cover cold builds, refreshes, yields, cancellation, and cleanup.
  • Initialization, refresh, and disposal can overlap. Deterministic lifecycle tests cover ownership transfer, delayed completion, queued cleanup, and post-disposal suppression.
  • Generated coverage and evidence files make the diff large. The review order below separates production behavior from supporting artifacts.
  • The automated suite excludes LiveOutlook; the live VSTO exercise remains a release-validation step.

Review Guide

  1. Review AppOlObjects service composition and captured-dispatcher ownership.
  2. Review OutlookFolderTreeService, FolderTreeSnapshotBuilder, and OutlookFolderHierarchyReader traversal and cleanup behavior.
  3. Review FilterOlFolders initialization, viewer ownership, and disposal guards, then the ribbon fault boundary.
  4. Review the deterministic regression tests and project compile entries.
  5. Review the specification and final audit artifacts; inspect generated Cobertura files last.

Follow-ups

  • Complete the live Outlook VSTO exercise described under recommended verification.
  • No additional code remediation was required by the final feature review.

GitHub Auto-close

  • None

@drmoisan

drmoisan commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

Reopening to trigger the repository pull_request CI workflow at the unchanged final head.

@drmoisan drmoisan closed this Aug 6, 2026
@drmoisan drmoisan reopened this Aug 6, 2026
@drmoisan

drmoisan commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

Reopening at 792804d to trigger the native pull_request CI event.

@drmoisan drmoisan closed this Aug 6, 2026
@drmoisan drmoisan reopened this Aug 6, 2026
@drmoisan

drmoisan commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

Deliberate close before a separately confirmed reopen to trigger pull_request CI.

@drmoisan drmoisan closed this Aug 6, 2026
@drmoisan drmoisan reopened this Aug 6, 2026
- Add a Func<Task<TResult>> overload to IUiDispatcher and WpfUiDispatcher so folder-tree snapshot builds await on the captured UI dispatcher instead of a dispatcher-free worker.
- Harden OutlookFolderTreeService lifecycle: linked dispose cancellation, linearized scheduled refreshes, guarded SnapshotChanged dispatch, and disposed-state build authorization.
- Extract FilterOlFoldersController lifecycle into a partial file with an async CreateAsync factory and deterministic subscription teardown.
- Move folder-tree service construction and disposal into the AppOlObjects.FolderTreeService partial and route the ribbon folder-filter callback through async fault reporting.
- Add MSTest coverage for dispatcher affinity, traversal cancellation, invalidation, disposal races, and controller initialization.

Refs: #420
Preserve terminal dispatch state during folder-tree service completion.\nStop filter-view publication after controller disposal.\nAdd lifecycle, cancellation, and dispatcher regression coverage.\n\nRefs: #420
@drmoisan
drmoisan force-pushed the bug/folder-tree-dispatcher-thread-affinity-420 branch from 792804d to 58b5167 Compare August 6, 2026 23:31
@drmoisan
drmoisan merged commit 0d85748 into main Aug 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant