-
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 deadlocks due to holding locks across WriteFile calls #16224
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,11 +10,11 @@ | |
|
||
#include <DefaultSettings.h> | ||
#include <unicode.hpp> | ||
#include <utils.hpp> | ||
#include <WinUser.h> | ||
#include <LibraryResources.h> | ||
|
||
#include "EventArgs.h" | ||
#include "../../types/inc/GlyphWidth.hpp" | ||
#include "../../buffer/out/search.h" | ||
#include "../../renderer/atlas/AtlasEngine.h" | ||
#include "../../renderer/dx/DxRenderer.hpp" | ||
|
@@ -443,6 +443,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
// - <none> | ||
void ControlCore::_sendInputToConnection(std::wstring_view wstr) | ||
{ | ||
if (wstr.empty()) | ||
{ | ||
return; | ||
} | ||
|
||
// The connection may call functions like WriteFile() which may block indefinitely. | ||
// It's important we don't hold any mutexes across such calls. | ||
_terminal->_assertUnlocked(); | ||
lhecker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (_isReadOnly) | ||
{ | ||
_raiseReadOnlyWarning(); | ||
|
@@ -492,8 +501,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
_handleControlC(); | ||
} | ||
|
||
const auto lock = _terminal->LockForWriting(); | ||
return _terminal->SendCharEvent(ch, scanCode, modifiers); | ||
TerminalInput::OutputType out; | ||
{ | ||
const auto lock = _terminal->LockForReading(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, SendKeyEvent manipulates state and SendCharEvent only reads it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They both manipulate state and I should've used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't necessarily do either of those for the backport version of this PR. I can see the ways in which the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the wrapped mutex class has significant safety benefits, because you cannot forget to lock something. But it wouldn't have prevented the bug this PR addresses. I'm fairly confident that I've fixed all cases where we forgot to lock |
||
out = _terminal->SendCharEvent(ch, scanCode, modifiers); | ||
} | ||
if (out) | ||
{ | ||
_sendInputToConnection(*out); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
void ControlCore::_handleControlC() | ||
|
@@ -601,6 +619,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
const WORD scanCode, | ||
const ControlKeyStates modifiers, | ||
const bool keyDown) | ||
{ | ||
if (!vkey) | ||
{ | ||
return true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not false? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The previous code did it: return vkey ? _terminal->SendKeyEvent(vkey,
scanCode,
modifiers,
keyDown) :
true; |
||
} | ||
|
||
TerminalInput::OutputType out; | ||
{ | ||
const auto lock = _terminal->LockForWriting(); | ||
|
||
|
@@ -637,11 +662,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
// If the terminal translated the key, mark the event as handled. | ||
// This will prevent the system from trying to get the character out | ||
// of it and sending us a CharacterReceived event. | ||
return vkey ? _terminal->SendKeyEvent(vkey, | ||
scanCode, | ||
modifiers, | ||
keyDown) : | ||
true; | ||
out = _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown); | ||
} | ||
if (out) | ||
{ | ||
_sendInputToConnection(*out); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
bool ControlCore::SendMouseEvent(const til::point viewportPos, | ||
|
@@ -650,8 +678,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
const short wheelDelta, | ||
const TerminalInput::MouseButtonState state) | ||
{ | ||
const auto lock = _terminal->LockForWriting(); | ||
return _terminal->SendMouseEvent(viewportPos, uiButton, states, wheelDelta, state); | ||
TerminalInput::OutputType out; | ||
{ | ||
const auto lock = _terminal->LockForReading(); | ||
out = _terminal->SendMouseEvent(viewportPos, uiButton, states, wheelDelta, state); | ||
} | ||
if (out) | ||
{ | ||
_sendInputToConnection(*out); | ||
return true; | ||
} | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔖 |
||
} | ||
|
||
void ControlCore::UserScrollViewport(const int viewTop) | ||
|
@@ -1324,8 +1361,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
// before sending it over the terminal's connection. | ||
void ControlCore::PasteText(const winrt::hstring& hstr) | ||
{ | ||
using namespace ::Microsoft::Console::Utils; | ||
|
||
auto filtered = FilterStringForPaste(hstr, CarriageReturnNewline | ControlCodes); | ||
if (BracketedPasteEnabled()) | ||
{ | ||
filtered.insert(0, L"\x1b[200~"); | ||
filtered.append(L"\x1b[201~"); | ||
} | ||
|
||
// It's important to not hold the terminal lock while calling this function as sending the data may take a long time. | ||
_sendInputToConnection(filtered); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The above block is 1:1 what used to be the |
||
|
||
const auto lock = _terminal->LockForWriting(); | ||
_terminal->WritePastedText(hstr); | ||
_terminal->ClearSelection(); | ||
_updateSelectionUI(); | ||
_terminal->TrySnapOnInput(); | ||
|
@@ -1894,17 +1942,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
const auto endPoint = goRight ? clampedClick : cursorPos; | ||
|
||
const auto delta = _terminal->GetTextBuffer().GetCellDistance(startPoint, endPoint); | ||
|
||
const WORD key = goRight ? VK_RIGHT : VK_LEFT; | ||
|
||
std::wstring buffer; | ||
const auto append = [&](TerminalInput::OutputType&& out) { | ||
if (out) | ||
{ | ||
buffer.append(std::move(*out)); | ||
} | ||
}; | ||
|
||
// Send an up and a down once per cell. This won't | ||
// accurately handle wide characters, or continuation | ||
// prompts, or cases where a single escape character in the | ||
// command (e.g. ^[) takes up two cells. | ||
for (size_t i = 0u; i < delta; i++) | ||
{ | ||
_terminal->SendKeyEvent(key, 0, {}, true); | ||
_terminal->SendKeyEvent(key, 0, {}, false); | ||
append(_terminal->SendKeyEvent(key, 0, {}, true)); | ||
append(_terminal->SendKeyEvent(key, 0, {}, false)); | ||
} | ||
|
||
_sendInputToConnection(buffer); | ||
} | ||
} | ||
} | ||
|
@@ -1915,7 +1973,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
// - Updates the renderer's representation of the selection as well as the selection marker overlay in TermControl | ||
void ControlCore::_updateSelectionUI() | ||
{ | ||
const auto lock = _terminal->LockForWriting(); | ||
_renderer->TriggerSelection(); | ||
// only show the markers if we're doing a keyboard selection or in mark mode | ||
const bool showMarkers{ _terminal->SelectionMode() >= ::Microsoft::Terminal::Core::Terminal::SelectionInteractionMode::Keyboard }; | ||
|
@@ -2247,14 +2304,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
|
||
void ControlCore::_focusChanged(bool focused) | ||
{ | ||
// GH#13461 - temporarily turn off read-only mode, send the focus event, | ||
// then turn it back on. Even in focus mode, focus events are fine to | ||
// send. We don't want to pop a warning every time the control is | ||
// focused. | ||
const auto previous = std::exchange(_isReadOnly, false); | ||
const auto restore = wil::scope_exit([&]() { _isReadOnly = previous; }); | ||
const auto lock = _terminal->LockForWriting(); | ||
_terminal->FocusChanged(focused); | ||
TerminalInput::OutputType out; | ||
{ | ||
const auto lock = _terminal->LockForReading(); | ||
out = _terminal->FocusChanged(focused); | ||
} | ||
if (out && !out->empty()) | ||
lhecker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
// _sendInputToConnection() asserts that we aren't in focus mode, | ||
// but window focus events are always fine to send. | ||
_connection.WriteInput(*out); | ||
} | ||
} | ||
|
||
bool ControlCore::_isBackgroundTransparent() | ||
|
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 issue was reported here: #9232 (comment)
This PR changes a lot of whitespace indentation, so use this for review: https://github.com/microsoft/terminal/pull/16224/files?w=1