dotnet / dotnet/wpf

Layout loop due to re-entrant UpdateLayout call

Open
#4,370 1 comment 2 reactions 0 assignees View on GitHub
.NET Framework Bug
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

Description

* .NET Core Version: all
* Windows version: all
* Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes
* Is this bug related specifically to tooling in Visual Studio (e.g. XAML Designer, Code editing, etc...)? no

**Problem description:**
UpdateLayout has logic for ignoring re-entrant calls, escaping from runaway layout, and continuing its main loop when callouts create more work. This logic fails in the following circumstances.
1. UpdateLayout begins (outer call), starts main loop ``while(hasDirtiness || _firePostLayoutEvents) {...}``. (Initially both conditions are true.) Each iteration sets ``_isInUpdateLayout`` to true at the beginning and resets it to false near the end, to prevent re-entrancy during the Measure and Arrange phases.
2. An iteration that clears the measure and arrange queues (i.e. ``hasDirtiness==false``) ends by raising the post-layout events. Re-entrancy is allowed during the event phase, although each of the event-firing methods prevents its own re-entrancy.
3. The 3rd (of 4) event-firing method ``fireAutomationEvents()`` is responsible for clearing the ``_firePostLayoutEvents`` flag. It does this after checking for re-entrancy but before raising events.
4. The problem arises when one of the automation event handlers creates dirtiness and calls UpdateLayout synchronously (inner call). The first iteration sets ``_firePostLayoutEvents==true`` (by calling Measure or Arrange), but no iteration clears it - the re-entrancy check in ``fireAutomationEvents`` prevents it. Thus the inner call's main loop continues even after all the dirtiness is gone, and only terminates when the loop counter exceeds the "runaway" threshold.
5. The problem can compound if synchronous re-entrant UpdateLayout calls keep occurring before the outer call's ``fireAutomationEvents`` completes. Each such call spins the main loop the maximum amount, and posts another background layout task.

**Actual behavior:**
The app appears unresponsive.

**Expected behavior:**
App remains responsive.

**Minimal repro:**
Not available. This is something I observed while investigating another issue. It only occurred when attaching a debugger or TTD recorder to the process, so it's sensitive to timing. The re-entrant call happened when raising an automation event to the OS automation component; the COM call or P/Invoke pumped messages, including a WM_SIZE that triggered logic in ``HwndSource.Process_WM_SIZE``.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.