Skip to content

Fix infinite loop in TableMeasurer.CollapseWidths when wrappable columns have zero width (#2131) - #2162

Merged
patriksvensson merged 2 commits into
spectreconsole:mainfrom
Juimun:fix/table-measurer-infinite-loop-2131
Jul 11, 2026
Merged

Fix infinite loop in TableMeasurer.CollapseWidths when wrappable columns have zero width (#2131)#2162
patriksvensson merged 2 commits into
spectreconsole:mainfrom
Juimun:fix/table-measurer-infinite-loop-2131

Conversation

@Juimun

@Juimun Juimun commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

This Pull Request resolves an infinite loop bug within TableMeasurer.CollapseWidths that occurs when one or more wrappable columns already have a width of 0 (e.g., via GetColumnWidth() returning 0 for hidden elements) while non-wrappable columns exceed the maximum console width. This edge case causes the layout engine to freeze, blocking the rendering thread indefinitely.


Fixes #2131

  • I have read the Contribution Guidelines
  • I have checked that there isn't already another pull request that solves the above issue
  • All newly added code is adequately covered by tests
  • All existing tests are still running without errors

Changes

The regression was rooted in the reduction loop calculation when maxColumn dropped to 0. The old logic fallback assigned a value of 1 to secondMaxColumn under certain conditions, yielding a negative columnDifference (0 - 1 = -1). Because the loop termination condition explicitly checked for columnDifference == 0, the negative value bypassed the check, keeping the while loop running forever since Ratio.Reduce cannot perform reductions with negative bounds.

To fully eliminate this lock vector, two changes were introduced to TableMeasurer.cs:

  1. Updated the loop break condition to columnDifference <= 0.
  2. Changed the conditional fallback value inside the secondMaxColumn selector from 1 to 0.

Please upvote 👍 this pull request if you are interested in it.

@Juimun

Juimun commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

Comment thread src/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs
@Juimun

Juimun commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback! Rewrote the test to use TestConsole and the public API instead of reflection.
To reproduce the [0, 0, X] scenario from the issue without touching internals, I used a NoWrap column with an explicit Width - that bypasses the content measurement clamp in MeasureColumn and lets the wrappable columns collapse to 0, which is what actually triggers the infinite loop in CollapseWidths (the negative columnDifference when maxColumn == 0).
Verified it hangs on the unpatched code and passes once the fix (columnDifference <= 0) is applied.

@patriksvensson
patriksvensson merged commit 3ba1023 into spectreconsole:main Jul 11, 2026
3 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.

Infinite loop in TableMeasurer.CollapseWidths when wrappable columns have width 0

2 participants