Fix RenderThread's notify mechanism#4698
Fix RenderThread's notify mechanism#46983 commits merged intomicrosoft:masterfrom beviu:fix-render-notify
Conversation
|
I wonder if the CI failed because it's bugged or if it's because my code is bad. It failed on a test that uses the |
|
Reran failed x86 leg. That test has been a pain in our side. It may or may not be your fault. |
zadjii-msft
left a comment
There was a problem hiding this comment.
Yea I've read this code about 8 times, and I think it makes sense to me now. I think we'll have enough runway before the next release to make sure that this doesn't break anything mysteriously. Thanks for debugging this!
|
@msftbot make sure @miniksa signs off on this |
|
Hello @zadjii-msft! Because you've given me some instructions on how to help merge this pull request, I'll be modifying my merge approach. Here's how I understand your requirements for merging this pull request:
If this doesn't seem right to you, you can tell me to cancel these instructions and use the auto-merge policy that has been configured for this repository. Try telling me "forget everything I just told you". |
|
Last commit is a little perf improvement: (Sorry for removing |
|
@msftbot make sure @DHowett-MSFT signs off |
| } | ||
|
|
||
| // <-- | ||
| // If `NotifyPaint` is called at this point, then it _will_ set |
There was a problem hiding this comment.
Excellent commenting. Thank you.
miniksa
left a comment
There was a problem hiding this comment.
This looks good to me, but I like having @DHowett-MSFT as a double check on certain things and this is one of them.
|
Hello @DHowett-MSFT! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
|
(It looked good to me; GH didn't show that @zadjii-msft had signed off, but I would have been the third.) |
|
🎉 Once again, thanks for the contribution! This pull request was included in a set of conhost changes that was just |
Summary of the Pull Request
Fix a bug where the
Renderer::PaintFramemethod:RenderThread::NotifyThreadcall but needs to be called because there the terminal was updated (theoretical bug)References
The bug was introduced by #3511.
PR Checklist
Detailed Description of the Pull Request / Additional comments
Before
First bug
In the original code,
_fNextFrameRequestedis set totruein render thread becausestd::atomic_flag::test_and_setis called.This is wrong because it means that the render thread will render the terminal again even if there is no change after the last render.
I think the the goal was to load the boolean value for
_fNextFrameRequestedto check whether the thread should sleep or not.The problem is that there is no method on
std::atomic_flagto load its boolean value. I guess what happened was that the "solution" that was found was to usestd::atomic_flag::test_and_set, followed bystd::atomic_flag::clearif the value wasfalseoriginally (ifstd::atomic_flag::test_and_setreturnedfalse) to restore the original value. I guess that this was believed to be equivalent to just a simple load, without doing any change to the value because it restores it at the end.But it's not: this is dangerous because if the value is changed to
truebetween the call tostd::atomic_flag::test_and_setand the call tostd::atomic_flag::clear, then the value ends up beingfalseat the end which is wrong because we don't want to change it! And if that value ends up beingfalse, it means that we miss a render because we will wait on_hEventduring the next iteration on the render thread.Well actually, here, this not even a problem because when that code is ran,
_fPaintingisfalsewhich means that the other thread that modifies the_fNextFrameRequestedvalue throughRenderThread::NotifyPaintwill not actually modify_fNextFrameRequestedbut rather callSetEvent(see the method's body).But wait! There is a problem there too!
std::atomic_flag::test_and_setis called for_fPaintingwhich sets its value totrue. It was probably unintended. So actually, the next call toRenderThread::NotifyPaintwill end up modifying_fNextFrameRequestedwhich means that the data race I was talking about might happen!Second bug
Let's go back a little bit in my explanation. I was talking about the fact that:
The problem is that the reverse was done in the implementation:
std::atomic_flag::clearis called if the value wastrueoriginally!So at this point, if the value of
_fNextFrameRequestedwasfalse, thenstd::atomic_flag::test_and_setsets its is set totrueand returnsfalse. So for the next iteration,_fNextFrameRequestedistrueand the render thread will re-render but that was not needed.After
I used
std::atomic<bool>instead ofstd::atomic_flagfor_fNextFrameRequestedand the other atomic field because it has aloadand astoremethod so we can actually load the value without changing it.I also replaced
_fPaintingby_fWaiting, which is basically the opposite of_fPaintingbut staystruefor a little shorter than_fPaintingwould stayfalse. Indeed, I think that it makes more sense to directly wrap/scope just the call toWaitForSingleObjectby setting my atomic variable totruejust before and tofalsejust after because:_fWaitingis (that is, to callSetEventfromRenderThread::NotifyPaintif it'strue).truefor a little shorter which means less calls toSetEvent.Warning
I don't really understand std::memory_orders.
So I used the default one (
std::memory_order_seq_cst) which is the safest.I believe that if no read or write are reordered in the two threads (
RenderThread::NotifyPaintandRenderThread::_ThreadProc), then the code I wrote will behave correctly.I think that
std::memory_order_seq_cstenforces that so it should be fine, but I'm not sure.Validation Steps Performed
I tried to reproduce the second bug that I described in the first section of this PR.
I put a breakpoint on
RenderThread::NotifyPaintand onRenderer::PaintFrame. Initially they are disabled. Then I ran the terminal in Release mode, waited a bit for the prompt to display and the cursor to start blinking. Then I enabled the breakpoints.Before
Each
RenderThread::NotifyPaintis followed by 2Renderer::PaintFramecalls. ❌After
Each
RenderThread::NotifyPaintis followed by 1Renderer::PaintFramecall. ✔️