Skip to content

Commit ebb0713

Browse files
committed
[lldb/Core] Fix a race in the Communication class
Summary: Communication::SynchronizeWithReadThread is called whenever a process stops to ensure that we process all of its stdout before we report the stop. If the process exits, we first call this method, and then close the connection. However, when the child process exits, the thread reading its stdout will usually (but not always) read an EOF because the other end of the pty has been closed. In response to an EOF, the Communication read thread closes it's end of the connection too. This can result in a race where the read thread is closing the connection while the synchronizing thread is attempting to get its attention via Connection::InterruptRead. The fix is to hold the synchronization mutex while closing the connection. I've found this issue while tracking down a rare flake in some of the vscode tests. I am not sure this is the cause of those failures (as I would have expected this issue to manifest itself differently), but it is an issue nonetheless. The attached test demonstrates the steps needed to reproduce the race. It will fail under tsan without this patch. Reviewers: clayborg, JDevlieghere Subscribers: mgorny, lldb-commits Tags: #lldb Differential Revision: https://reviews.llvm.org/D77295
1 parent a027570 commit ebb0713

File tree

3 files changed

+48
-9
lines changed

3 files changed

+48
-9
lines changed

lldb/source/Core/Communication.cpp

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -315,28 +315,25 @@ lldb::thread_result_t Communication::ReadThread(lldb::thread_arg_t p) {
315315
Status error;
316316
ConnectionStatus status = eConnectionStatusSuccess;
317317
bool done = false;
318+
bool disconnect = false;
318319
while (!done && comm->m_read_thread_enabled) {
319320
size_t bytes_read = comm->ReadFromConnection(
320321
buf, sizeof(buf), std::chrono::seconds(5), status, &error);
321-
if (bytes_read > 0)
322+
if (bytes_read > 0 || status == eConnectionStatusEndOfFile)
322323
comm->AppendBytesToCache(buf, bytes_read, true, status);
323-
else if ((bytes_read == 0) && status == eConnectionStatusEndOfFile) {
324-
if (comm->GetCloseOnEOF())
325-
comm->Disconnect();
326-
comm->AppendBytesToCache(buf, bytes_read, true, status);
327-
}
328324

329325
switch (status) {
330326
case eConnectionStatusSuccess:
331327
break;
332328

333329
case eConnectionStatusEndOfFile:
334330
done = true;
331+
disconnect = comm->GetCloseOnEOF();
335332
break;
336333
case eConnectionStatusError: // Check GetError() for details
337334
if (error.GetType() == eErrorTypePOSIX && error.GetError() == EIO) {
338335
// EIO on a pipe is usually caused by remote shutdown
339-
comm->Disconnect();
336+
disconnect = comm->GetCloseOnEOF();
340337
done = true;
341338
}
342339
if (error.Fail())
@@ -365,9 +362,15 @@ lldb::thread_result_t Communication::ReadThread(lldb::thread_arg_t p) {
365362
if (log)
366363
LLDB_LOGF(log, "%p Communication::ReadThread () thread exiting...", p);
367364

368-
comm->m_read_thread_did_exit = true;
369-
// Let clients know that this thread is exiting
370365
comm->BroadcastEvent(eBroadcastBitNoMorePendingInput);
366+
{
367+
std::lock_guard<std::mutex> guard(comm->m_synchronize_mutex);
368+
comm->m_read_thread_did_exit = true;
369+
if (disconnect)
370+
comm->Disconnect();
371+
}
372+
373+
// Let clients know that this thread is exiting
371374
comm->BroadcastEvent(eBroadcastBitReadThreadDidExit);
372375
return {};
373376
}

lldb/unittests/Core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
add_lldb_unittest(LLDBCoreTests
2+
CommunicationTest.cpp
23
MangledTest.cpp
34
RichManglingContextTest.cpp
45
StreamCallbackTest.cpp
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//===-- CommunicationTest.cpp ---------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "lldb/Core/Communication.h"
10+
#include "lldb/Host/ConnectionFileDescriptor.h"
11+
#include "lldb/Host/Pipe.h"
12+
#include "llvm/Testing/Support/Error.h"
13+
#include "gtest/gtest.h"
14+
15+
using namespace lldb_private;
16+
17+
TEST(CommunicationTest, SynchronizeWhileClosing) {
18+
// Set up a communication object reading from a pipe.
19+
Pipe pipe;
20+
ASSERT_THAT_ERROR(pipe.CreateNew(/*child_process_inherit=*/false).ToError(),
21+
llvm::Succeeded());
22+
23+
Communication comm("test");
24+
comm.SetConnection(std::make_unique<ConnectionFileDescriptor>(
25+
pipe.ReleaseReadFileDescriptor(), /*owns_fd=*/true));
26+
comm.SetCloseOnEOF(true);
27+
ASSERT_TRUE(comm.StartReadThread());
28+
29+
// Ensure that we can safely synchronize with the read thread while it is
30+
// closing the read end (in response to us closing the write end).
31+
pipe.CloseWriteFileDescriptor();
32+
comm.SynchronizeWithReadThread();
33+
34+
ASSERT_TRUE(comm.StopReadThread());
35+
}

0 commit comments

Comments
 (0)