Repository navigation
[Android] Fix Screenshot.CaptureAsync deadlock for synchronous UI-thread callers - #37680
Merged
Merged
Conversation
praveenkumarkarunanithi
temporarily deployed
to
copilot-pat-pool
August 20, 2026 13:07 — with
GitHub Actions
Inactive
Contributor
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 37680Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 37680" |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
praveenkumarkarunanithi
temporarily deployed
to
copilot-pat-pool
August 20, 2026 13:08 — with
GitHub Actions
Inactive
praveenkumarkarunanithi
temporarily deployed
to
copilot-pat-pool
August 20, 2026 13:08 — with
GitHub Actions
Inactive
35 of 48 tasks
Contributor
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines: Successfully started running 2 pipeline(s). |
praveenkumarkarunanithi
marked this pull request as ready for review
August 25, 2026 13:10
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
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
PixelCopycallbacks through a sharedHandlerThread(instead ofLooper.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"); |
Contributor
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
26 of 46 tasks
kubaflo
approved these changes
Aug 26, 2026
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`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
PixelCopyto support hardware-rendered content such as WebView. #35384PixelCopydelivered its completion callback onLooper.MainLooper, while Sentry synchronously waited forScreenshot.Default.CaptureAsync().GetAwaiter().GetResult()on the UI thread. This created a circular dependency: the UI thread blocked waiting for the screenshot task, and thePixelCopycallback could not execute because it also required the UI thread, resulting in a deadlock.Description of Change
The fix retains
PixelCopybut moves its completion callback fromLooper.MainLooperto a shared, process-wideHandlerThread, 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; ifPixelCopyfails 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
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
BeforeFix.mov
AfterFix.47.mov