-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix potential cursor redrawing crash #2965
Fix potential cursor redrawing crash #2965
Conversation
I found #2924 is also related to this. Feel free to cherry-pick or close this. |
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
You will probably need to merge in |
38f259a
to
405d183
Compare
I think the real problem here is that some writer isn't locking the console buffer before writing, or some reader isn't locking the console before forcing the cursor to redraw. It looks like Cursor is updated in a great number of places, and it'll be hard to audit callers in conhost (to find good examples of where locking is required around cursor manipulation.) |
I was thinking the same thing. Furthermore, an obvious design issue is that cursor redrawing is related to the entire text buffer's refreshing. #2932 unintentionally made cursor redrawing slower and make this crush super severe. |
Yeah, this isn't right. This is just adjusting the parameters of the problem. We need to identify the locking issue for real. The discussion about this is now crossing like 4 different PRs and issues. @DHowett-MSFT, is there a follow on task to investigate where the locking has gone wrong here (I saw that you reverted #2932). |
405d183
to
7b1a9e1
Compare
I find that the read lock in |
Again theres' a catch ... Adding lock in My original comment in #2932 described these two crashes. And I thought #2924 fixed the invalid args. So I only focused on the use-after-free in this PR. It turns out that the invalid-args crash is still there. |
OK, here's what I got so far. There're mainly three threads that's competing for With #2932, there're race conditions which is not handled correctly
This second and third case is obviously more complicated and painful. I may be wrong but I don't think there's easy fix for this. |
Ideally, the execution order of render thread and output thread should be in our control, as in #3075, quote:
The basic infrastructure is exactly like it. The render thread has a sleep interval that's limited to I crafted some PR that's intended to reduce the CPU usage in output thread. But apparently it's not enough. Maybe the ultimate solution would be adding another thread for layout, freeing output thread from all those heavy work. I don't have a clear thought on this yet. |
7b1a9e1
to
30956d7
Compare
There you have it. Now it fixes the crashes. But truly this is a ugly solution. I wish there could be a more elegant way. |
@@ -533,6 +553,8 @@ void Terminal::_InitializeColorTable() | |||
// - isVisible: whether the cursor should be visible | |||
void Terminal::SetCursorVisible(const bool isVisible) noexcept | |||
{ | |||
auto lock = LockForReading(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that you're setting something about the buffer, this would be a LockForWriting()
, not reading.
@@ -425,6 +440,11 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView) | |||
} | |||
} | |||
|
|||
if (proposedCursorPosition.X > bufferSize.RightInclusive()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you intentionally put this back? It looks like @DHowett-MSFT just removed this in #3212
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line the caused regression was proposedCursorPosition.Y++;
. I only took the first line and reset position.X
to a legal value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This unfortunately caused #3277.
auto cursorPosition = cursor.GetPosition(); | ||
if (cursorPosition.X > viewportSize.X) | ||
{ | ||
cursorPosition.X = viewportSize.X - 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused as to why the X dimension is reset to Size.X-1 and the Y direction is reset to Size.Y exactly.
I would think that the rectangle is either inclusive in both dimensions, or exclusive on both directions.
Additionally, the std::min/std::max functions are great for situations like this as a short hand way of writing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The runtime failure is caused by
THROW_HR_IF(E_INVALIDARG, !limits.IsInBounds(pos)); |
The limits.IsInBounds
check is using pos.X < RightExclusive()
so I have to use Size.X - 1
to pass it. Practially I've not seen crashes caused by Size.Y
. But you are right, It should be unifed.
However, I don't really know if viewportSize.X - 1
is the right thing to do. I did it just to pass the runtime assertion
Tagging as blocking. We'd like to either refine this and bring it in before we cut 0.6 or roll back #2932 and then perhaps release a patch release to 0.6 bringing it back later next week. |
The thread dance is too complicated to fix. So I just tried limiting the cursor position. If we decided to rollback #2932 we'd better still keep an eye on the thread problem, which may be the cause of many other crashes like this. |
cursorPosition.X = std::min(cursorPosition.X, static_cast<SHORT>(viewportSize.X - 1)); | ||
cursorPosition.Y = std::min(cursorPosition.Y, static_cast<SHORT>(viewportSize.Y - 1)); | ||
|
||
cursor.StartDeferDrawing(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks strange but it's necessary. Because SetPosition
will call RedrawCursor
, and eventually find its way to Terminal::IsCursorDoubleWidth()
, causing the crash. So I had to disable redrawing first, correct the position, and enable redrawing. IMO the SetPosition
(and related method) are too aggresive doing redrawing, which is not a good design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right. We should likely fix the design on the redrawing and come up with something better. We can take this for now. I know it's ugly and complicated to fix the right way, but I'm glad you found something workable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm good with this. Well... mostly. I'm overall as unhappy as @skyline75489 at how messed up this whole situation is. But as long as this alleviates some of the crashing for now... it's what we need to get 0.6 out and we should revisit the cursor drawing and some of the threading locks before 1.0, hopefully.
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
@DHowett-MSFT brings up the point that this might make some sort of reverse line wrap bug with the X=0, but I'm willing to take it right now to stop crashing and cut a 0.6 candidate. |
# Summary of the Pull Request This PR will allow the cursor to be double width when on top of a double width character. This required changing `IsCursorDoubleWidth` to check whether the glyph the cursor's on top of is double width. This code is exactly the same as the original PR that addressed this issue in #2932. That one got reverted at some point due to the crashes related to it, but due to a combination of Terminal having come further since that PR and other changes to address use-after-frees, some of the crashes may/may not be relevant now. The ones that seemed to be relevant/repro-able, I attempt to address in this PR. The `IsCursorDoubleWidth` check would fail during the `TextBuffer::Reflow` call inside of `Terminal::UserResize` occasionally, particularly when `newCursor.EndDeferDrawing()` is called. This is because when we tell the newCursor to `EndDefer`, the renderer will attempt to redraw the cursor. As part of this redraw, it'll ask if `IsCursorDoubleWidth`, and if the renderer managed to ask this before `UserResize` swapped out the old buffer with the new one from `Reflow`, the renderer will be asking the old buffer if its out-of-bounds cursor is double width. This was pretty easily repro'd using `cmatrix -u0` and resizing the window like a madman. As a solution, I've moved the Start/End DeferDrawing calls out of `Reflow` and into `UserResize`. This way, I can "clamp" the portion of the code where the newBuffer is getting created and reflowed and swapped into the Terminal buffer, and only allow the renderer to draw once the swap is done. This also means that ConHost's `ResizeWithReflow` needed to change slightly. In addition, I've added a WriteLock to `SetCursorOn`. It was mentioned as a fix for a crash in #2965 (although I can't repro), and I also figured it would be good to try to emulate where ConHost locks with regards to Cursor operations, and this seemed to be one that we were missing. ## PR Checklist * [x] Closes #2713 * [x] CLA signed * [x] Tests added/passed ## Validation Steps Performed Manual validation that the cursor is indeed chonky, added a test case to check that we are correctly saying that the cursor is double width (not too sure if I put it in the right place). Also open to other test case ideas and thoughts on what else I should be careful for since I am quite nervous about what other crashes might occur.
Summary of the Pull Request
Fixes crash introduced by #2932
References
PR Checklist
Detailed Description of the Pull Request / Additional comments
See #2932