Fix infinite Text redraw for SWT.SEARCH with custom background on macOS#7
Fix infinite Text redraw for SWT.SEARCH with custom background on macOS#7
Conversation
There was a problem hiding this comment.
Code Review
This pull request addresses an infinite redraw loop in the Cocoa implementation of the Text widget by fetching the string value from NSCell instead of NSControl. It adds the necessary stringValue method to NSCell and includes a regression test to ensure the UI stops refreshing. A review comment suggests optimizing the paint loop by caching the cancelButtonCell to reduce redundant native calls and object creation.
| NSString cellValue = _cell.stringValue(); | ||
| if (cell.cancelButtonCell() != null && cellValue != null && cellValue.length() > 0) { | ||
| cell.cancelButtonCell().drawInteriorWithFrame(cell.cancelButtonRectForBounds(cellFrame), view); | ||
| } |
There was a problem hiding this comment.
In the paint loop, it is more efficient to store the result of cell.cancelButtonCell() in a local variable to avoid redundant native calls and the creation of extra Java wrapper objects.
NSString cellValue = _cell.stringValue();
NSButtonCell cancelButtonCell = cell.cancelButtonCell();
if (cancelButtonCell != null && cellValue != null && cellValue.length() > 0) {
cancelButtonCell.drawInteriorWithFrame(cell.cancelButtonRectForBounds(cellFrame), view);
}|
I've fixed the test to fail more reliably and it fails even after this change. I've merged latest SWT master for testing. This branch seems to be quite old. |
a3de8a7 to
6ff2c49
Compare
|
Please merge #8 too. This revision of test fails on my workstation reliably again. |
…gger Picks up the more reliable reproducer from PR eclipse-platform#3260 (shell.setLayout + requestLayout + shell.open) and adds temporary instrumentation so CI logs reveal which native call in drawInteriorWithFrame_inView_searchfield posts the re-entrant setNeedsDisplay that drives the loop. - Text.drawInteriorWithFrame_inView / _searchfield: print a stable step marker to stderr before and after every significant native call; update Text.DEBUG_PAINT_STEP so Widget.setNeedsDisplay can attribute the trigger. - Widget.setNeedsDisplay / setNeedsDisplayInRect: when called re-entrantly during paint (isPainting contains view) and a paint step is armed, log the offending step, view id and widget class, plus a stack trace for the first 3 occurrences. Temporary: to be removed once the trigger is identified. Refs eclipse-platform/eclipse.platform.ui#3920 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6ff2c49 to
9badc5a
Compare
I added some tracing code (I don't have a Mac myself) and see if I can spot what is wrong. |
|
Traces on my workstation |
|
Can you now reintroduce the failed fix? I assume its traces would be useful too. |
I don't have a Mac hence I can only test on the CI |
Trace zip from basilevs's workstation confirms the re-entrant setNeedsDisplay originates from NSControl.stringValue() inside drawInteriorWithFrame_inView_searchfield (~800 paints in 1s, every one hitting the same step). Stack: Widget.setNeedsDisplay Display.windowProc <- AppKit callback OS.objc_msgSend (native) NSControl.stringValue (line 122) <- our caller Text.drawInteriorWithFrame_inView_searchfield Switch the length check to NSCell.stringValue() (read the stored cell value directly, no NSControl editing-path side effect) and add the NSCell.stringValue wrapper this requires. Instrumentation is kept so the next CI run (and basilevs on a real macOS workstation) can confirm the [SWT-DEBUG-SND] markers disappear once the fix is applied. Refs eclipse-platform/eclipse.platform.ui#3920 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@basilevs thanks for the trace zip — it was conclusive. What the trace showedAll ~800 paints in your 1-second window hit the same re-entrant Stack (every time): So your original hypothesis was exactly right: The GitHub Actions macOS runners still can't reproduce the loop (only paint 1 ever happens — presumably headless/offscreen skips the repaint flush), so CI won't settle this. I need your help to verify. What I just pushed
How to verify on your workstationgit fetch origin fix-text-infinite-redraw
git checkout fix-text-infinite-redraw
# run test_finiteRedrawIf the fix works, the CI/local log should show:
If the fix does not work, you will still see Either way, if you can attach another |
Test fails after changes. Traces: test_output.txt.zip NSCell does not seem any different from NSSearchField. |
|
Thanks @basilevs — the new trace settles it. 876 paints, 876 re-entrant Stack: So the side effect is in AppKit's implementation of the
Proposed next attempt: Java-side text tracking. Maintain a Will push that on top of the current branch shortly so you can verify on your workstation that the |
basilevs's second trace confirmed that both NSControl.stringValue and NSCell.stringValue dispatch to the same AppKit selector, which posts setNeedsDisplay: synchronously while the search field is being drawn. Swapping wrappers cannot break the loop; the paint method must stop querying native state entirely. Introduce a searchFieldHasText boolean on Text, refreshed at every SWT.Modify emission point (append, cut, insert, paste/replace, setText, setTextChars, verify-listener replacement, and textDidChange). The helper only queries stringValue when the display is not currently painting, so the Java-side read cannot re-arm the loop. The paint method in drawInteriorWithFrame_inView_searchfield consults the cache instead of calling stringValue on the cell. Drop the unused NSCell.stringValue wrapper added in the previous attempt. Instrumentation is kept so the next trace from basilevs can confirm the [SWT-DEBUG-SND] re-entrant markers disappear. Refs eclipse-platform/eclipse.platform.ui#3920 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@basilevs new revision pushed ( What changed
Instrumentation is kept. What the trace should look like if the fix worksExpected properties:
What I cannot verify from Linux and what I need you to checkUX regression check — this is the part I can't test: the
If any of these don't update the icon correctly, that's a mutation path I missed and the cache needs an update hook there too. If you can attach another |
|
Test passes on Java 23 (Temurin) and Java 25 (Homebrew): test_output.txt Turns out my test has never failed on Java 23. Eclipse PlatformJava 23When running Eclipse Platform on Java 23 (Temurin): 1-2. The refresh of the filtered tree is now reliable despite the infinite logging. Java 25When running Eclipse Platform on Java 25 (Homebrew): |
|
I think we have to suspend this effort until I can better isolate effects of different JVMs on both my test and Eclipse Platform UI. I suspect that neither Java 23 nor Java 25 are official "default" JVM Eclipse is tested on. |
|
@Phillipus has explained in eclipse-platform/eclipse.platform.ui#3920 that I should probably use Java build based on a specific MacOS SDK. On Java that closely matches (major version) SDK 26.4 used by SWT master: the bug is no longer reproduced in this branch. However, I suspect that SDK 26.4 is not the intended target for SWT in the near perspective, so I suggest to wait for a confirmation from @HeikoKlare. |
I (and some others) would be in favor of building SWT and Equinox against latest macOS SDK (26.x) as soon as possible. But first issues that would currently occur when just building against macOS SDK 26 (as we did few weeks ago with the Equinox binaries on which of the issues were reported) have to be resolved. Wherever possible, those fixes should be compatible with builds against an older as well as a newer macOS SDK both. But that's quite some effort to test and I am not sure if it's possible in all (or even some) of the cases. In general, as discussed in eclipse-platform#3264, it does not seem to be so easy to define a common macOS SDK version that we support / build against due to the different executables involved that may be built against different SDKs. |
And this will not be easy to test from a development setup where running an Eclipse or RCP instance in dev mode will be executed using the And if it ever is built against latest macOS SDK I predict bad news for downstream RCP apps... ;-) |
|
I've managed to reproduce the problem on Mac OS SDKs:
I've then confirmed that the fix from d6ec708 fixes the problem on all of these environments. @vogella thanks a lot! |
Summary
test_finiteRedraw), which spinsreadAndDispatch()for 1s against aTextstyledSWT.SEARCH | SWT.ICON_CANCELwith a custom background and fails if it never returnsfalse.Text.drawInteriorWithFrame_inView_searchfieldfromNSControl.stringValue()toNSCell.stringValue(), avoiding the re-entrantsetNeedsDisplay:that drove the infinite paint loop.stringValue()wrapper toNSCell.javato support the call.Root cause
NSControl.stringValue()(called on theNSSearchField) postssetNeedsDisplay:on the view from inside the paint pass.Widget.setNeedsDisplayenqueues the view indisplay.needsDisplaybecauseisPaintingcontains it, that queue is flushed after the paint, the view is marked dirty again, and the nextreadAndDispatch()iteration paints it once more. See eclipse-platform/eclipse.platform.ui#3920 for the captured stack.NSCell.stringValue()returns the same string without touching the control's editing/binding machinery, so the cancel-icon visibility contract is preserved.Test plan
test_finiteRedrawand passes on macOS (it is the only platform where the buggy draw path exists).SWT.SEARCHText, which is valid everywhere; the loop only reproduces on cocoa).🤖 Generated with Claude Code