My following change from [bugs:#2511] seems introduced or exposed data race for multi-threaded wrap block:
- cache[pos].reset();
+ cache[pos]->ReSet(lineNumber, maxChars);
The root cause I think is that multiple significant lines may mapped to same cache position, when one thread is using the layout (in view.LayoutLine), another thread may resize it and use it. with old cache[pos].reset() both thread will works on different layout (previous thread's layout is no longer in the cache, but not dangling).
Following is test program to show multiple significant lines may mapped to same cache position:
with following change for Notepad4:
I can reproduce the race when running (x64 Debug or Release) from Visual Studio 2026 (it's hard to reproduce outside the debugger), open
scintilla\win32\SurfaceGDI.cxx, put caret betweenclassandSurfaceGDI, toggle fold for level 2 (collapse allSurfaceGDImethods), then resize editor width multiples, VS will stop atthrow iByte. change Notepad4'sLineLayoutCachefromstd::unique_ptr<LineLayout>tostd::shared_ptr<LineLayout>does not fix the race, but following fixes it.check layout
use_count()afterlinesAfterWrap[i] = ll->lines;, it's positive.I have not yet reproduce the race in Scintilla with similar change:
here is no need to mannerly reset:
I should have noticed the problem with the
ReSetchange.The above change looks good so has been committed as [e87fc0].
This is probably worth a release since there may be writes outside allocations.
Related
Commit: [e87fc0]
ReSetcan only be called from main thread (the only thread that uses layout cache) or whenlineNumberis same (same document line number is only handled by one thread).Adjust
LineMayCachecould make significant lines maps to unique cache position (change||to&&seems work in Notepad4), but that requires some calculation and proof, and is hard to get right.