diff --git a/lldb/source/Host/posix/MainLoopPosix.cpp b/lldb/source/Host/posix/MainLoopPosix.cpp index c6fe7814bd22e..2d95100ffe7e6 100644 --- a/lldb/source/Host/posix/MainLoopPosix.cpp +++ b/lldb/source/Host/posix/MainLoopPosix.cpp @@ -107,6 +107,11 @@ class MainLoopPosix::RunImpl { #if HAVE_SYS_EVENT_H std::vector in_events; + enum FDState : bool { + eFDStateReading = false, + eFDStateEOF = true, + }; + std::map eof_fds; struct kevent out_events[4]; int num_events = -1; @@ -123,8 +128,12 @@ MainLoopPosix::RunImpl::RunImpl(MainLoopPosix &loop) : loop(loop) { Status MainLoopPosix::RunImpl::Poll() { in_events.resize(loop.m_read_fds.size()); unsigned i = 0; - for (auto &fd : loop.m_read_fds) - EV_SET(&in_events[i++], fd.first, EVFILT_READ, EV_ADD, 0, 0, 0); + for (auto &fd : loop.m_read_fds) { + if (eof_fds.find(fd.first) == eof_fds.end()) + eof_fds[fd.first] = eFDStateReading; + EV_SET(&in_events[i++], fd.first, EVFILT_READ, + eof_fds[fd.first] == eFDStateEOF ? EV_DELETE : EV_ADD, 0, 0, 0); + } num_events = kevent(loop.m_kqueue, in_events.data(), in_events.size(), out_events, @@ -149,6 +158,10 @@ void MainLoopPosix::RunImpl::ProcessReadEvents() { switch (out_events[i].filter) { case EVFILT_READ: loop.ProcessReadObject(out_events[i].ident); + // A read event with `data == 0` indicates the file has reached EOF and is + // closed. Remember this to prevent triggering the callback again. + if (out_events[i].data == 0) + eof_fds[out_events[i].ident] = eFDStateEOF; break; default: llvm_unreachable("Unknown event"); diff --git a/lldb/unittests/Host/MainLoopTest.cpp b/lldb/unittests/Host/MainLoopTest.cpp index 8a248100c936a..d16e0ee508de3 100644 --- a/lldb/unittests/Host/MainLoopTest.cpp +++ b/lldb/unittests/Host/MainLoopTest.cpp @@ -104,6 +104,52 @@ TEST_F(MainLoopTest, ReadPipeObject) { ASSERT_EQ(1u, callback_count); } +TEST_F(MainLoopTest, ReadAcrossMultipleRuns) { + Pipe pipe; + + ASSERT_TRUE(pipe.CreateNew().Success()); + + MainLoop loop; + + char X = 'X'; + size_t len = sizeof(X); + ASSERT_THAT_EXPECTED(pipe.Write(&X, len), llvm::HasValue(1)); + + Status error; + auto handle = loop.RegisterReadObject( + std::make_shared(pipe.GetReadFileDescriptor(), + File::eOpenOptionReadOnly, false), + make_callback(), error); + ASSERT_TRUE(error.Success()); + ASSERT_TRUE(handle); + + // Callback invoked once per 'Run' since data is not consumed. + ASSERT_TRUE(loop.Run().Success()); + ASSERT_TRUE(loop.Run().Success()); + ASSERT_EQ(2u, callback_count); + + // Consume buffer, so next 'Run' should NOT trigger the handle. + char buf[10]; + ASSERT_THAT_EXPECTED(pipe.Read(buf, sizeof(buf)), llvm::HasValue(1)); + loop.AddCallback([](auto &loop) { loop.RequestTermination(); }, + std::chrono::milliseconds(10)); + + // This run operation should NOT trigger the read handle, since the data has + // already been consumed. + ASSERT_TRUE(loop.Run().Success()); + ASSERT_EQ(2u, callback_count); + + // Close the write side of the pair to ensure we have a callback for the + // hangup. + pipe.CloseWriteFileDescriptor(); + ASSERT_TRUE(loop.Run().Success()); + ASSERT_EQ(3u, callback_count); + + // The file handle is still registered, so it will trigger the handle again. + ASSERT_TRUE(loop.Run().Success()); + ASSERT_EQ(4u, callback_count); +} + TEST_F(MainLoopTest, MultipleReadsPipeObject) { Pipe pipe; @@ -501,12 +547,15 @@ TEST_F(MainLoopTest, DetectsEOF) { Status error; MainLoop loop; - auto handle = - loop.RegisterReadObject(conn->GetReadObject(), make_callback(), error); + auto handle = loop.RegisterReadObject( + conn->GetReadObject(), [&](auto &loop) { callback_count++; }, error); + loop.AddCallback([](MainLoopBase &loop) { loop.RequestTermination(); }, + std::chrono::milliseconds(100)); ASSERT_TRUE(error.Success()); term.CloseSecondaryFileDescriptor(); ASSERT_TRUE(loop.Run().Success()); + // Ensure we only receive one event for the EOF event. ASSERT_EQ(1u, callback_count); }