Skip to content

Lite and the Darling Viewer: every clipboard write retries instead of crashing when the clipboard is busy (#4594) - #4600

Merged
erikdarlingdata merged 4 commits into
devfrom
fix/4594-guarded-clipboard
Sep 28, 2026
Merged

erikdarlingdata merged 4 commits into
devfrom
fix/4594-guarded-clipboard

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Fixes #4594. Builds on #4593.

What changed

#4593 added ClipboardText.TrySetText (in PerformanceMonitor.Ui), a bounded-retry guard around
Clipboard.SetText that returns false instead of throwing when another process momentarily holds the
Windows clipboard, and routed the shared plan viewer, blocking chain, deadlock graph and grid-export
copies through it. This PR routes the remaining 18 unguarded clipboard writes in Lite and the Darling
Viewer through the same guard.

  • Added ClipboardText.TrySetDataObject(object data, bool copy = false), a SetDataObject sibling to
    TrySetText with the identical bounded retry, for call sites that use SetDataObject (the existing
    convention to avoid WPF's Clipboard.Flush()) and for the chart bitmap copy, which has no text
    equivalent.
  • Routed all 18 sites through TrySetText/TrySetDataObject:
    • Lite: Helpers/ContextMenuHelper.cs (4 sites, including the chart bitmap copy),
      Controls/ServerTab.CopyExport.cs, Controls/RecommendationsTab.xaml.cs (2),
      Windows/SettingsWindow.xaml.cs, Windows/AlertDetailWindow.xaml.cs,
      Windows/EntraDeviceCodeWindow.xaml.cs.
    • The Darling Viewer: MainWindow.xaml.cs (3), ViewerServerTab.CopyExport.cs,
      ViewerServerTab.ChartContextMenu.cs, SettingsWindow.xaml.cs (2), AlertDetailWindow.xaml.cs.
  • Where a site already showed a status line on success (the recommendation card copy/AI-prompt buttons,
    the MCP command/URL copy buttons), a failed copy now shows "Couldn't copy: the clipboard is in use."
    instead of silently dropping it. Where a site had no prior status handling (the grid/row/cell/chart
    copies, the alert detail T-SQL copy, the device-code copy), a failed copy is still swallowed quietly,
    matching TrySetText's own default behavior for those paths.
  • Added Darling/Darling.Tests/ClipboardWriteCensusTests.cs: walks every .cs file under Lite/,
    Darling/ and PerformanceMonitor.Ui/ (via CSharpSourceWalker, so comments/strings can't trip or
    hide it; deprecated/ excluded) and fails if any file other than ClipboardText.cs itself still calls
    Clipboard.SetText( or Clipboard.SetDataObject(.

RED on the pre-fix code

Checked out the census test alone against 2b5d2b3fe (the #4593 tip this branch is stacked on) in a
throwaway worktree. It failed, listing the 11 files carrying the 18 unguarded sites:

unguarded Clipboard.SetText/SetDataObject call(s) found outside ClipboardText.cs (route through
PerformanceMonitor.Ui.ClipboardText.TrySetText / TrySetDataObject): Lite/Controls/ServerTab.CopyExport.cs,
Lite/Controls/RecommendationsTab.xaml.cs, Lite/Windows/SettingsWindow.xaml.cs,
Lite/Windows/AlertDetailWindow.xaml.cs, Lite/Windows/EntraDeviceCodeWindow.xaml.cs,
Lite/Helpers/ContextMenuHelper.cs, Darling/PerformanceMonitor.Darling.Viewer/MainWindow.xaml.cs,
Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.CopyExport.cs,
Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.ChartContextMenu.cs,
Darling/PerformanceMonitor.Darling.Viewer/SettingsWindow.xaml.cs,
Darling/PerformanceMonitor.Darling.Viewer/AlertDetailWindow.xaml.cs

On this branch's code, the same test passes.

Build and test

Built (macOS, -p:EnableWindowsTargeting=true, -v q, all 0 warnings / 0 errors): Darling.Tests,
Lite/PerformanceMonitorLite.csproj, Lite.Tests, PerformanceMonitor.Darling.Viewer,
PerformanceMonitor.Ui.

Ran Darling.Tests.dll in-process (stripped Microsoft.WindowsDesktop.App from the runtimeconfig):
ClipboardWriteCensusTests, Viewer4582Tests, CommentFilterAdoptionTests, DocCommentHygieneTests,
and every *Census* class in the tree (DailyDeadlockWindowCensusTests,
EntraProviderPackageCensusTests, FileGrowthRiseUnitCensusTests, McpPayloadContractCensusTests,
McpServiceParameterDiSeatCensusTests, MeasurementContractCensusTests,
MigrationDataMovingRungCensusPins, PgSettingScrubCandidateCensusTests, PlanForceActionDetailCensus,
PlanForceActionDetailCensusTests, RemoteCollectorServiceCancellationCensusTests,
SameStatementPileupSourceCensusTests, StoreApplicationNameCensusTests,
StoreSessionTimeZonePinCensusTests, WebExceptionTextCensusTests, McpToolsListBudgetTests): Total:
360, Errors: 0, Failed: 0, Skipped: 1, Not Run: 0.

Lite.Tests builds but does not run on macOS (net10.0-windows, PresentationFramework); no test in this
PR's scope touches Lite.Tests directly (Lite's product code changes are the clipboard call sites only, no
new members/log text), so no Lite.Tests class needed a by-hand read.

CHANGELOG

SECTION: Fixed
ENTRY: - Copying from Lite or the Darling Viewer no longer crashes when another program holds the clipboard ([#4600]) - Every remaining unguarded clipboard write (recommendation copy/AI-prompt buttons, MCP command/URL copy, grid/row/cell copy, chart image copy, alert detail T-SQL copy, sign-in device-code copy) now retries briefly instead of throwing when another process is holding the clipboard.
REF: [#4600]: #4600

… on a truncated single-statement copy

The Windows clipboard is a shared resource; another process holding it briefly
(a clipboard manager, Office, RDP) makes an unguarded write throw and crash
the app, the same failure #2833 already fixed for reads. ClipboardText gains
a guarded TrySetText, mirroring TryRead's bounded retry, and every
Clipboard.SetText/SetDataObject call in the shared plan viewer, blocking
chain, deadlock graph, and DataGrid export helpers now routes through it.

Copy Query Text on a truncated single-statement plan now hands back the full
query the plan was captured from instead of the plan's own 4,000-character
showplan-capped copy, matching PerformanceStudio's fix. The guard counts
statements the way the Statements grid counts them (descending into stored
procedure and UDF bodies), not by outer batch, so a plan captured around
EXEC dbo.SomeProc is correctly treated as multi-statement and never hands
back the outer EXEC for a truncated body statement.

Fixes #4582
…ling Viewer through the guarded writer (#4594)

Adds ClipboardText.TrySetDataObject (a SetDataObject sibling to #4593's
TrySetText, same bounded retry) and routes every remaining
Clipboard.SetText / Clipboard.SetDataObject call in Lite and the
Darling Viewer through it, so a busy clipboard retries instead of
crashing the app. Sites that already showed a status line on
success now show a short failure message instead of silently
dropping the copy; sites with no prior status handling keep quietly
swallowing a failed copy, matching TrySetText's own behavior.

Adds ClipboardWriteCensusTests: no source file under Lite/, Darling/
or PerformanceMonitor.Ui/, other than ClipboardText.cs itself, may
call Clipboard.SetText( or Clipboard.SetDataObject( in code.
…oard

# Conflicts:
#	PerformanceMonitor.PlanAnalysis/PlanDisplayText.cs
#	PerformanceMonitor.Ui/ClipboardText.cs
…dText.TrySetText

Merging dev brought in a properties-panel copy path with its own bare-try
guard around Clipboard.SetText, added after #4593/#4600 branched. It didn't
route through the shared ClipboardText helper, so ClipboardWriteCensusTests
failed post-merge. Delegate to ClipboardText.TrySetText instead of
duplicating the guard.
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 28, 2026 16:35
@erikdarlingdata
erikdarlingdata merged commit c650606 into dev Sep 28, 2026
15 of 16 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/4594-guarded-clipboard branch September 28, 2026 16:35
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