@@ -24,15 +24,16 @@ using namespace Microsoft::Console::VirtualTerminal;
24
24
// - hPipe - a handle to the file representing the read end of the VT pipe.
25
25
// - inheritCursor - a bool indicating if the state machine should expect a
26
26
// cursor positioning sequence. See MSFT:15681311.
27
- VtInputThread::VtInputThread (_In_ wil::unique_hfile hPipe,
27
+ VtInputThread::VtInputThread (wil::unique_hfile hPipe,
28
+ wil::shared_event shutdownEvent,
28
29
const bool inheritCursor) :
29
30
_hFile{ std::move (hPipe) },
31
+ _shutdownEvent{ shutdownEvent },
30
32
_hThread{},
31
33
_utf8Parser{ CP_UTF8 },
32
- _dwThreadId{ 0 },
33
- _exitRequested{ false },
34
- _exitResult{ S_OK }
34
+ _dwThreadId{ 0 }
35
35
{
36
+ THROW_HR_IF (E_HANDLE, !_shutdownEvent);
36
37
THROW_HR_IF (E_HANDLE, _hFile.get () == INVALID_HANDLE_VALUE);
37
38
38
39
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals ().getConsoleInformation ();
@@ -45,6 +46,30 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe,
45
46
46
47
_pInputStateMachine = std::make_unique<StateMachine>(engine.release ());
47
48
THROW_IF_NULL_ALLOC (_pInputStateMachine.get ());
49
+
50
+ _shutdownWatchdog = std::async (std::launch::async, [&] {
51
+ _shutdownEvent.wait ();
52
+ if (_dwThreadId != 0 )
53
+ {
54
+ wil::unique_handle threadHandle (OpenThread (STANDARD_RIGHTS_ALL | THREAD_TERMINATE, FALSE , _dwThreadId));
55
+ LOG_LAST_ERROR_IF_NULL (threadHandle.get ());
56
+ if (threadHandle)
57
+ {
58
+ LOG_IF_WIN32_BOOL_FALSE (CancelSynchronousIo (threadHandle.get ()));
59
+ }
60
+ }
61
+ });
62
+ }
63
+
64
+ VtInputThread::~VtInputThread ()
65
+ {
66
+ if (_shutdownEvent)
67
+ {
68
+ _shutdownEvent.SetEvent ();
69
+
70
+ // Wait for watchdog future to get the memo or we might try to destroy it before it gets to work.
71
+ _shutdownWatchdog.wait ();
72
+ }
48
73
}
49
74
50
75
// Method Description:
@@ -96,44 +121,54 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter)
96
121
return pInstance->_InputThread ();
97
122
}
98
123
99
- // Method Description:
100
- // - Do a single ReadFile from our pipe, and try and handle it. If handling
101
- // failed, throw or log, depending on what the caller wants.
124
+ // Routine Description:
125
+ // - A public way of pumping a single input message through the VT input channel
126
+ // - Reading input can be a blocking operation. This function will capture
127
+ // the thread ID of whomever calls it so it can be unblocked on shutdown events
128
+ // by a watchdog thread.
129
+ // - This function cannot be called by two public methods simultaneously.
130
+ // If another is already waiting in a blocked read on the VT input thread,
131
+ // an invalid state error will be returned.
132
+ // - This function is only valid during startup. Once the real VtInputThread starts
133
+ // to process the input, it will fill the thread ID field permanently until shutdown.
102
134
// Arguments:
103
- // - throwOnFail: If true, throw an exception if there was an error processing
104
- // the input recieved. Otherwise, log the error.
135
+ // - <none>
105
136
// Return Value:
137
+ // - S_OK, a ReadFile error, an error processing input, or an invalid state error if another thread is already waiting.
138
+ [[nodiscard]] HRESULT VtInputThread::DoReadInput ()
139
+ {
140
+ // If there's already a thread pumping VT input, it's not valid to read this from the outside.
141
+ RETURN_HR_IF (HRESULT_FROM_WIN32 (ERROR_INVALID_STATE), _dwThreadId != 0 );
142
+
143
+ // Store which thread is attempting to read VT input. It may get blocked indefinitely and need
144
+ // to get unstuck by a shutdown event.
145
+ _dwThreadId = GetCurrentThreadId ();
146
+
147
+ // Set it back to 0 on the way out.
148
+ auto restoreThreadId = wil::scope_exit ([&] {
149
+ _dwThreadId = 0 ;
150
+ });
151
+
152
+ // Perform the blocking read operation.
153
+ return _ReadInput ();
154
+ }
155
+
156
+ // Method Description:
157
+ // - Do a single ReadFile from our pipe, and try and handle it.
158
+ // Arguments:
106
159
// - <none>
107
- void VtInputThread::DoReadInput (const bool throwOnFail)
160
+ // Return Value:
161
+ // - S_OK or relevant error
162
+ [[nodiscard]] HRESULT VtInputThread::_ReadInput ()
108
163
{
109
164
byte buffer[256 ];
110
165
DWORD dwRead = 0 ;
111
- bool fSuccess = !!ReadFile (_hFile.get (), buffer, ARRAYSIZE (buffer), &dwRead, nullptr );
112
166
113
- // If we failed to read because the terminal broke our pipe (usually due
114
- // to dying itself), close gracefully with ERROR_BROKEN_PIPE.
115
- // Otherwise throw an exception. ERROR_BROKEN_PIPE is the only case that
116
- // we want to gracefully close in.
117
- if (!fSuccess )
118
- {
119
- _exitRequested = true ;
120
- _exitResult = HRESULT_FROM_WIN32 (GetLastError ());
121
- return ;
122
- }
167
+ RETURN_IF_WIN32_BOOL_FALSE (ReadFile (_hFile.get (), buffer, ARRAYSIZE (buffer), &dwRead, nullptr ));
123
168
124
- HRESULT hr = _HandleRunInput (buffer, dwRead);
125
- if (FAILED (hr))
126
- {
127
- if (throwOnFail)
128
- {
129
- _exitResult = hr;
130
- _exitRequested = true ;
131
- }
132
- else
133
- {
134
- LOG_IF_FAILED (hr);
135
- }
136
- }
169
+ RETURN_IF_FAILED (_HandleRunInput (buffer, dwRead));
170
+
171
+ return S_OK;
137
172
}
138
173
139
174
// Method Description:
@@ -145,13 +180,19 @@ void VtInputThread::DoReadInput(const bool throwOnFail)
145
180
// have caused us to exit.
146
181
DWORD VtInputThread::_InputThread ()
147
182
{
148
- while (!_exitRequested)
183
+ auto onExitTriggerShutdown = wil::scope_exit ([&] {
184
+ _shutdownEvent.SetEvent ();
185
+ });
186
+
187
+ while (true )
149
188
{
150
- DoReadInput (true );
189
+ // NOTE: From inside the thread itself, we don't need to stash the thread handle each call
190
+ // because it was done permanently for us when the thread was created. No one else is allowed
191
+ // in through the public method while the actual VtInputThread is running. Only during startup.
192
+ RETURN_IF_FAILED (_ReadInput ());
151
193
}
152
- ServiceLocator::LocateGlobals ().getConsoleInformation ().GetVtIo ()->CloseInput ();
153
194
154
- return _exitResult ;
195
+ return S_OK ;
155
196
}
156
197
157
198
// Method Description:
@@ -173,6 +214,9 @@ DWORD VtInputThread::_InputThread()
173
214
174
215
RETURN_LAST_ERROR_IF_NULL (hThread);
175
216
_hThread.reset (hThread);
217
+
218
+ // This will permanently shut the door on the public read method until shutdown.
219
+ // Once the thread is servicing messages, we don't want any other threads getting in here.
176
220
_dwThreadId = dwThreadId;
177
221
178
222
return S_OK;
0 commit comments