diff --git a/lldb/include/lldb/Host/MainLoop.h b/lldb/include/lldb/Host/MainLoop.h --- a/lldb/include/lldb/Host/MainLoop.h +++ b/lldb/include/lldb/Host/MainLoop.h @@ -14,6 +14,7 @@ #include "llvm/ADT/DenseMap.h" #include #include +#include #if !HAVE_PPOLL && !HAVE_SYS_EVENT_H && !defined(__ANDROID__) #define SIGNAL_POLLING_UNSUPPORTED 1 @@ -59,6 +60,11 @@ SignalHandleUP RegisterSignal(int signo, const Callback &callback, Status &error); + // Add a pending callback that will be executed once after all the pending + // events are processed. The callback will be executed even if termination + // was requested. + void AddPendingCallback(const Callback &callback) override; + Status Run() override; // This should only be performed from a callback. Do not attempt to terminate @@ -104,6 +110,7 @@ llvm::DenseMap m_read_fds; llvm::DenseMap m_signals; + std::vector m_pending_callbacks; #if HAVE_SYS_EVENT_H int m_kqueue; #endif diff --git a/lldb/include/lldb/Host/MainLoopBase.h b/lldb/include/lldb/Host/MainLoopBase.h --- a/lldb/include/lldb/Host/MainLoopBase.h +++ b/lldb/include/lldb/Host/MainLoopBase.h @@ -46,6 +46,13 @@ llvm_unreachable("Not implemented"); } + // Add a pending callback that will be executed once after all the pending + // events are processed. The callback will be executed even if termination + // was requested. + virtual void AddPendingCallback(const Callback &callback) { + llvm_unreachable("Not implemented"); + } + // Waits for registered events and invoke the proper callbacks. Returns when // all callbacks deregister themselves or when someone requests termination. virtual Status Run() { llvm_unreachable("Not implemented"); } diff --git a/lldb/source/Host/common/MainLoop.cpp b/lldb/source/Host/common/MainLoop.cpp --- a/lldb/source/Host/common/MainLoop.cpp +++ b/lldb/source/Host/common/MainLoop.cpp @@ -347,6 +347,10 @@ #endif } +void MainLoop::AddPendingCallback(const Callback &callback) { + m_pending_callbacks.push_back(callback); +} + void MainLoop::UnregisterReadObject(IOObject::WaitableHandle handle) { bool erased = m_read_fds.erase(handle); UNUSED_IF_ASSERT_DISABLED(erased); @@ -401,6 +405,10 @@ return error; impl.ProcessEvents(); + + for (const Callback &callback : m_pending_callbacks) + callback(*this); + m_pending_callbacks.clear(); } return Status(); } diff --git a/lldb/unittests/Host/MainLoopTest.cpp b/lldb/unittests/Host/MainLoopTest.cpp --- a/lldb/unittests/Host/MainLoopTest.cpp +++ b/lldb/unittests/Host/MainLoopTest.cpp @@ -98,6 +98,56 @@ ASSERT_EQ(1u, callback_count); } +TEST_F(MainLoopTest, PendingCallback) { + char X = 'X'; + size_t len = sizeof(X); + ASSERT_TRUE(socketpair[0]->Write(&X, len).Success()); + + MainLoop loop; + Status error; + auto handle = loop.RegisterReadObject( + socketpair[1], + [&](MainLoopBase &loop) { + // Both callbacks should be called before the loop terminates. + loop.AddPendingCallback(make_callback()); + loop.AddPendingCallback(make_callback()); + loop.RequestTermination(); + }, + error); + ASSERT_TRUE(error.Success()); + ASSERT_TRUE(handle); + ASSERT_TRUE(loop.Run().Success()); + ASSERT_EQ(2u, callback_count); +} + +TEST_F(MainLoopTest, PendingCallbackCalledOnlyOnce) { + char X = 'X'; + size_t len = sizeof(X); + ASSERT_TRUE(socketpair[0]->Write(&X, len).Success()); + + MainLoop loop; + Status error; + auto handle = loop.RegisterReadObject( + socketpair[1], + [&](MainLoopBase &loop) { + // Add one pending callback on the first iteration. + if (callback_count == 0) { + loop.AddPendingCallback([&](MainLoopBase &loop) { + callback_count++; + }); + } + // Terminate the loop on second iteration. + if (callback_count++ >= 1) + loop.RequestTermination(); + }, + error); + ASSERT_TRUE(error.Success()); + ASSERT_TRUE(handle); + ASSERT_TRUE(loop.Run().Success()); + // 2 iterations of read callback + 1 call of pending callback. + ASSERT_EQ(3u, callback_count); +} + #ifdef LLVM_ON_UNIX TEST_F(MainLoopTest, DetectsEOF) {