Skip to content

[Android] Fix Screenshot.CaptureAsync deadlock for synchronous UI-thread callers - #37680

Merged
kubaflo merged 3 commits into
dotnet:mainfrom
praveenkumarkarunanithi:fix-37638
Aug 26, 2026
Merged

kubaflo merged 3 commits into
dotnet:mainfrom
praveenkumarkarunanithi:fix-37638

Conversation

@praveenkumarkarunanithi

Copy link
Copy Markdown
Contributor

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue.

Root Cause

The regression was introduced when Android screenshot capture switched from synchronous Canvas rendering to asynchronous PixelCopy to support hardware-rendered content such as WebView. #35384

PixelCopy delivered its completion callback on Looper.MainLooper, while Sentry synchronously waited for Screenshot.Default.CaptureAsync().GetAwaiter().GetResult() on the UI thread. This created a circular dependency: the UI thread blocked waiting for the screenshot task, and the PixelCopy callback could not execute because it also required the UI thread, resulting in a deadlock.

Description of Change

The fix retains PixelCopy but moves its completion callback from Looper.MainLooper to a shared, process-wide HandlerThread, allowing the screenshot task to complete even when the UI thread is synchronously blocked.

Additionally, the internal await chain now uses ConfigureAwait(false) to avoid resuming on the UI thread. The legacy Canvas/DrawingCache fallback is guarded to run only on the main thread; if PixelCopy fails asynchronously on a worker thread, screenshot capture safely returns failure instead of risking off-thread Android View access or recreating the deadlock.

Regressed By

#35384

Issues Fixed

Fixes #37638

Platforms Tested

  • iOS
  • MacCatalyst
  • Android
  • Windows

Note

The regression test is Android-only because the deadlock originates in Android’s PixelCopy callback and main-looper implementation. Other platforms use different screenshot implementations and cannot exercise this code path. The test models Sentry’s relevant synchronous behavior directly to avoid adding a third-party dependency to the shared HostApp.

Screenshots

Before Issue Fix After Issue Fix
BeforeFix.mov
AfterFix.47.mov

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 37680

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 37680"

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Aug 20, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

@praveenkumarkarunanithi praveenkumarkarunanithi added platform/android area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info and removed partner/syncfusion Issues / PR's with Syncfusion collaboration labels Aug 20, 2026
@praveenkumarkarunanithi praveenkumarkarunanithi changed the title  [Android] Fix Screenshot.CaptureAsync deadlock for synchronous UI-thread callers  [WIP] [Android] Fix Screenshot.CaptureAsync deadlock for synchronous UI-thread callers Aug 20, 2026
@praveenkumarkarunanithi praveenkumarkarunanithi added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Aug 20, 2026
@kubaflo kubaflo mentioned this pull request Aug 21, 2026
35 of 48 tasks
@vishnumenon2684 vishnumenon2684 changed the title  [WIP] [Android] Fix Screenshot.CaptureAsync deadlock for synchronous UI-thread callers [Android] Fix Screenshot.CaptureAsync deadlock for synchronous UI-thread callers Aug 25, 2026
@vishnumenon2684

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests , maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@praveenkumarkarunanithi
praveenkumarkarunanithi marked this pull request as ready for review August 25, 2026 13:10
Copilot AI lite review requested due to automatic review settings August 25, 2026 13:10
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses an Android-specific regression where Screenshot.CaptureAsync() can deadlock when a consumer blocks synchronously on the UI thread (e.g., GetAwaiter().GetResult()), by moving PixelCopy completion callbacks off the main looper.

Changes:

  • Route PixelCopy callbacks through a shared HandlerThread (instead of Looper.MainLooper) to prevent UI-thread circular waits.
  • Update the Android screenshot await chain to use ConfigureAwait(false) and add a guard to avoid unsafe off-thread view fallbacks.
  • Add an Android-only UI regression test + HostApp repro page for issue #37638.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/Essentials/src/Screenshot/Screenshot.android.cs Moves PixelCopy callbacks to a background looper and adjusts continuation behavior to avoid UI-thread deadlocks.
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue37638.cs Adds an Android-only UI test validating the deadlock scenario is resolved.
src/Controls/tests/TestCases.HostApp/Issues/Issue37638.cs Adds the HostApp issue page that reproduces the synchronous UI-thread caller behavior.

Comment on lines 63 to 74
static async Task<Bitmap?> RenderAsync(View view, Window? window)
{
if (OperatingSystem.IsAndroidVersionAtLeast(26))
{
var bitmap = await RenderUsingPixelCopyAsync(view, window);
var bitmap = await RenderUsingPixelCopyAsync(view, window).ConfigureAwait(false);
if (bitmap is not null)
return bitmap;

// PixelCopy may complete off the UI thread, where view-based fallbacks are unsafe.
if (!MainThread.IsMainThread)
return null;
}
Comment on lines +65 to +66
var screenshot = Screenshot.Default.CaptureAsync().GetAwaiter().GetResult();
return screenshot is null ? string.Empty : Guid.NewGuid().ToString("N");
@kubaflo

kubaflo commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

@kubaflo
kubaflo merged commit 1cdf1b6 into dotnet:main Aug 26, 2026
152 of 166 checks passed
@github-actions github-actions Bot added this to the .NET 10 SR11 milestone Aug 26, 2026
kubaflo pushed a commit that referenced this pull request Aug 31, 2026
Backport of #37680 for #37638.

(cherry picked from commit 1cdf1b6)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b603f8b0-824a-48b4-92e5-c891584266bf
kubaflo added a commit that referenced this pull request Sep 1, 2026
…load (#37746)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!-- release-readiness-agent: human-approval-required -->
## Summary

Integrates the staged **.NET MAUI 10.0.101 (SR10.1)** payload from
`inflight/10.0.101` into `release/10.0.1xx-sr10`, based on the shipped
`10.0.100` release.

The full release inventory and servicing decisions are tracked in
#37723. The [authoritative regression
query](https://github.com/dotnet/maui/issues?q=repo%3Adotnet%2Fmaui%20is%3Aissue%20is%3Aopen%20%28label%3A%22regressed-in-10.0.10%22%20OR%20label%3A%22regressed-in-10.0.20%22%20OR%20label%3A%22regressed-in-10.0.30%22%20OR%20label%3A%22regressed-in-10.0.40%22%20OR%20label%3A%22regressed-in-10.0.50%22%20OR%20label%3A%22regressed-in-10.0.60%22%20OR%20label%3A%22regressed-in-10.0.70%22%20OR%20label%3A%22regressed-in-10.0.80%22%20OR%20label%3A%22regressed-in-10.0.90%22%20OR%20label%3A%22regressed-in-10.0.100%22%29)
remains the source for the servicing-regression scope.

> [!IMPORTANT]
> Checked items under **Staged in this PR** are merged into
`inflight/10.0.101`, not yet into `release/10.0.1xx-sr10`. They reach
the release branch only when this aggregate PR is merged.

## Status at a glance

- [x] Stage the selected regression backports independently on
`inflight/10.0.101`.
- [x] Stage the SkiaSharp 4.150.1 payload and selective
`SKPath.AddCircle` compatibility fix.
- [x] Stage the isolated iOS 26 Liquid Glass TabBar payload from #37542.
- [ ] Complete or explicitly defer the six unresolved servicing
regressions listed below.
- [ ] Complete the release gates and merge this PR into
`release/10.0.1xx-sr10`.

## Staged in this PR

### Servicing regressions

- [x] #35826 — Android MediaPicker completion from child activities.
Source #35944; component backport #37724.
- [x] #36298 — Windows dynamic `ContentPresenter` assignment. Source
#36430; component backport #37725.
- [x] #36749 — preserve custom iOS Button styling. Source #36769;
component backport #37726.
- [x] #36736 — align Android SwipeItem content. Source #36820; component
backport #37727.
- [x] #34563 — per-edge iOS safe-area handling. Source #37033; component
backport #37728.
- [x] #37706 — Android root-page back handling. Source #37709; component
backport #37729.
- [x] #37705 — Android Material 3 status-bar contrast. Source #37710;
component backport #37730.
- [x] #37423 — restore native iOS 26 Liquid Glass TabBar behavior
through the isolated four-file payload from merged PR #37542.
- [x] #37418 — Android off-screen `TranslationY` layout padding. Source
fix #37772; focused backport commit `10799e67df`.
- [x] #37361 — iOS RefreshView pull-to-refresh with an empty
CollectionView. Source fix #37404; focused backport commit `d26faa8e93`.
- [x] #37638 — Android `Screenshot.CaptureAsync` synchronous UI-thread
deadlock. Source fix #37680; focused backport commit `778040a233`.

### Planned Skia payload

- [x] Port the Skia/Svg.Skia update from #36255 through component
backport #37731.
- [x] Include only the required `SKPath.AddCircle` compatibility change
from #36787; exclude its unrelated SourceGen changes.
- [x] Confirm a matching SkiaSharp 4.150.1 native-assets/PDB build for
`_SkiaSharpNativeAssetsVersion`, or record an explicit release-owner
waiver.

### Integration follow-ups

- [x] Apply the aggregate review fixes for safe-area cache invalidation,
Activity-for-result cancellation ownership, and Android theme-test
semantics.
- [x] Keep the final branch payload as focused release commits without
unrelated `inflight/current` history.
- [x] Validate the #37418 and #37638 focused Android UI tests on the
aggregate branch (2/2 passed).
- [ ] Validate the #37361 focused iOS UI test on the aggregate branch.

## Already present on the SR10 base — no new merge needed

- [x] #36852 — Android Shell hidden-fragment leak; fix #36903 is already
in SR10.
- [x] #36735 — Android ActivityIndicator visibility; fix #36748 is
already in SR10.
- [x] #36853 — Android Shell DI-singleton route blank page; fix #36903
is already in SR10.
- [x] #36942 — Android UI event handling; fix #36988 is already in SR10.
- [x] #37281 — Android scrolling inside shadowed containers; backport
#37312 is already in SR10.

## Completed disposition — no backport required

- [x] #37700 — closed as intended SR10 `SwipeView.Threshold` behavior
from #36878; any SwipeItem sizing concern should be tracked separately.

## Not included yet — merge, defer, or explicitly disposition before
payload freeze

- [ ] #35301 — Windows CollectionView applies WinUI styling by default;
no linked fix PR.
- [ ] #34491 — Android CollectionView selection with
`PointerGestureRecognizer`; replacement fix #37952 remains draft against
`main`.
- [ ] #35059 — iOS app becomes unresponsive when opening a ComboBox
dropdown in landscape; no linked fix PR.
- [ ] #37407 — iOS TimePicker default `t` format forces en-US; fix
#37797 remains draft against `main`.
- [ ] #36269 — Android `SafeAreaEdges` with Shell tab navigation and
hidden TabBar; source fix #36474 remains open against `main` and has no
SR10 backport.
- [ ] #37657 — Android Shell/root-page `OnBackButtonPressed()` in
Release builds; no linked fix PR.

## Release completion

### Before merging this PR

- [ ] Refresh the authoritative query and reconcile #37723 with its
current results.
- [ ] Record an include/defer/close decision for every unresolved
regression above.
- [ ] Validate every included regression on its reported OS/version
using the `10.0.101` candidate packages.
- [ ] Complete current-head aggregate CI and resolve or explicitly waive
every required build/device/UI-test failure.
- [ ] Resolve or waive the Skia native-assets/PDB release gate.
- [ ] Bump `eng/Versions.props` from patch `100` to `101` after the
payload is frozen.
- [ ] Obtain the required independent current-head maintainer approvals.
- [ ] Merge #37746 into `release/10.0.1xx-sr10`.

### After merge

- [ ] Confirm BAR promotion and the per-build validation feed used by
the ship assessment.
- [ ] Publish and tag `10.0.101`.
- [ ] Complete the Visual Studio insertion/default-MAUI-version update.

## Notes

- No automated `/backport` command was used for this release payload.
- Supersedes #37745, which GitHub automatically closed when the
integration branch was renamed from `vs/10.0.101` to
`inflight/10.0.101`.
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 26, 2026
@sheiksyedm sheiksyedm added the community ✨ Community Contribution label Oct 1, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Screenshot.CaptureAsync deadlocks when awaited synchronously from the UI thread (regression in 10.0.90)

5 participants