diff options
3 files changed, 214 insertions, 33 deletions
diff --git a/lldb/gtest/unittest/Plugins/Process/Linux/ThreadStateCoordinatorTest.cpp b/lldb/gtest/unittest/Plugins/Process/Linux/ThreadStateCoordinatorTest.cpp index 3be6805f3bf..a6bc40fd622 100644 --- a/lldb/gtest/unittest/Plugins/Process/Linux/ThreadStateCoordinatorTest.cpp +++ b/lldb/gtest/unittest/Plugins/Process/Linux/ThreadStateCoordinatorTest.cpp @@ -170,6 +170,15 @@ ASSERT_EQ (true, HasError ()); } void + CallAfterRunningThreadsStop (lldb::tid_t deferred_tid) + { + m_coordinator.CallAfterRunningThreadsStop (deferred_tid, + GetStopRequestFunction (), + GetDeferredStopNotificationFunction (), + GetErrorFunction ()); + } + + void NotifyThreadCreate (lldb::tid_t stopped_tid, bool thread_is_stopped) { m_coordinator.NotifyThreadCreate (stopped_tid, thread_is_stopped, GetErrorFunction ()); @@ -671,3 +680,94 @@ TEST_F (ThreadStateCoordinatorTest, ResumedThreadAlreadyMarkedDoesNotHoldUpPendi ASSERT_EQ (true, DidFireDeferredNotification ()); ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); } + +TEST_F (ThreadStateCoordinatorTest, CallAfterRunningThreadsStopFiresWhenNoRunningThreads) +{ + // Let the coordinator know about our thread. + SetupKnownStoppedThread (TRIGGERING_TID); + + // Notify we have a trigger that needs to be fired when all running threads have stopped. + CallAfterRunningThreadsStop (TRIGGERING_TID); + + // Notification trigger shouldn't go off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Process next event. This will pick up the call after threads stop event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the trigger should have fired, since there were no threads that needed to first stop. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); + + // And no stop requests should have been made. + ASSERT_EQ (0, GetRequestedStopCount ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterRunningThreadsStopRequestsTwoPendingStops) +{ + // Let the coordinator know about our threads. + SetupKnownStoppedThread (TRIGGERING_TID); + SetupKnownRunningThread (PENDING_STOP_TID); + SetupKnownRunningThread (PENDING_STOP_TID_02); + + // Notify we have a trigger that needs to be fired when all running threads have stopped. + CallAfterRunningThreadsStop (TRIGGERING_TID); + + // Notification trigger shouldn't go off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Process next event. This will pick up the call after threads stop event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // We should have two stop requests for the two threads currently running. + ASSERT_EQ (2, GetRequestedStopCount ()); + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID)); + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID_02)); + + // But the deferred stop notification should not have fired yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Now notify the two threads stopped. + NotifyThreadStop (PENDING_STOP_TID); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + ASSERT_EQ (false, DidFireDeferredNotification ()); + + NotifyThreadStop (PENDING_STOP_TID_02); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the trigger should have fired, since there were no threads that needed to first stop. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} + +TEST_F (ThreadStateCoordinatorTest, CallAfterRunningThreadsStopRequestsStopTwoOtherThreadsOneRunning) +{ + // Let the coordinator know about our threads. PENDING_STOP_TID_02 will already be stopped. + SetupKnownStoppedThread (TRIGGERING_TID); + SetupKnownRunningThread (PENDING_STOP_TID); + SetupKnownStoppedThread (PENDING_STOP_TID_02); + + // Notify we have a trigger that needs to be fired when all running threads have stopped. + CallAfterRunningThreadsStop (TRIGGERING_TID); + + // Notification trigger shouldn't go off yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Process next event. This will pick up the call after threads stop event. + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // We should have two stop requests for the two threads currently running. + ASSERT_EQ (1, GetRequestedStopCount ()); + ASSERT_EQ (true, DidRequestStopForTid (PENDING_STOP_TID)); + + // But the deferred stop notification should not have fired yet. + ASSERT_EQ (false, DidFireDeferredNotification ()); + + // Now notify the two threads stopped. + NotifyThreadStop (PENDING_STOP_TID); + ASSERT_PROCESS_NEXT_EVENT_SUCCEEDS (); + + // Now the trigger should have fired, since there were no threads that needed to first stop. + ASSERT_EQ (true, DidFireDeferredNotification ()); + ASSERT_EQ (TRIGGERING_TID, GetDeferredNotificationTID ()); +} diff --git a/lldb/source/Plugins/Process/Linux/ThreadStateCoordinator.cpp b/lldb/source/Plugins/Process/Linux/ThreadStateCoordinator.cpp index 7f20717f3bf..b6b9412db15 100644 --- a/lldb/source/Plugins/Process/Linux/ThreadStateCoordinator.cpp +++ b/lldb/source/Plugins/Process/Linux/ThreadStateCoordinator.cpp @@ -73,7 +73,23 @@ public: m_original_wait_for_stop_tids (wait_for_stop_tids), m_request_thread_stop_function (request_thread_stop_function), m_call_after_function (call_after_function), - m_error_function (error_function) + m_error_function (error_function), + m_request_stop_on_all_unstopped_threads (false) + { + } + + EventCallAfterThreadsStop (lldb::tid_t triggering_tid, + const ThreadIDFunction &request_thread_stop_function, + const ThreadIDFunction &call_after_function, + const ErrorFunction &error_function) : + EventBase (), + m_triggering_tid (triggering_tid), + m_wait_for_stop_tids (), + m_original_wait_for_stop_tids (), + m_request_thread_stop_function (request_thread_stop_function), + m_call_after_function (call_after_function), + m_error_function (error_function), + m_request_stop_on_all_unstopped_threads (true) { } @@ -116,42 +132,16 @@ public: return eventLoopResultContinue; } - // Request a stop for all the thread stops that need to be stopped - // and are not already known to be stopped. Keep a list of all the - // threads from which we still need to hear a stop reply. - - ThreadIDSet sent_tids; - for (auto tid : m_wait_for_stop_tids) + if (m_request_stop_on_all_unstopped_threads) { - // Validate we know about all tids for which we must first receive a stop before - // triggering the deferred stop notification. - auto find_it = coordinator.m_tid_stop_map.find (tid); - if (find_it == coordinator.m_tid_stop_map.end ()) - { - // This is an error. We shouldn't be asking for waiting pids that aren't known. - // NOTE: we may be stripping out the specification of wait tids and handle this - // automatically, in which case this state can never occur. - std::ostringstream error_message; - error_message << "error: deferred notification for tid " << m_triggering_tid << " specified an unknown/untracked pending stop tid " << m_triggering_tid; - m_error_function (error_message.str ()); - - // Bail out here. + RequestStopOnAllRunningThreads (coordinator); + } + else + { + if (!RequestStopOnAllSpecifiedThreads (coordinator)) return eventLoopResultContinue; - } - - // If the pending stop thread is currently running, we need to send it a stop request. - if (!find_it->second) - { - m_request_thread_stop_function (tid); - sent_tids.insert (tid); - } } - // We only need to wait for the sent_tids - so swap our wait set - // to the sent tids. The rest are already stopped and we won't - // be receiving stop notifications for them. - m_wait_for_stop_tids.swap (sent_tids); - if (m_wait_for_stop_tids.empty ()) { // We're not waiting for any threads. Fire off the deferred signal delivery event. @@ -208,12 +198,81 @@ private: m_call_after_function (m_triggering_tid); } + bool + RequestStopOnAllSpecifiedThreads (const ThreadStateCoordinator &coordinator) + { + // Request a stop for all the thread stops that need to be stopped + // and are not already known to be stopped. Keep a list of all the + // threads from which we still need to hear a stop reply. + + ThreadIDSet sent_tids; + for (auto tid : m_wait_for_stop_tids) + { + // Validate we know about all tids for which we must first receive a stop before + // triggering the deferred stop notification. + auto find_it = coordinator.m_tid_stop_map.find (tid); + if (find_it == coordinator.m_tid_stop_map.end ()) + { + // This is an error. We shouldn't be asking for waiting pids that aren't known. + // NOTE: we may be stripping out the specification of wait tids and handle this + // automatically, in which case this state can never occur. + std::ostringstream error_message; + error_message << "error: deferred notification for tid " << m_triggering_tid << " specified an unknown/untracked pending stop tid " << m_triggering_tid; + m_error_function (error_message.str ()); + + // Bail out here. + return false; + } + + // If the pending stop thread is currently running, we need to send it a stop request. + if (!find_it->second) + { + m_request_thread_stop_function (tid); + sent_tids.insert (tid); + } + } + + // We only need to wait for the sent_tids - so swap our wait set + // to the sent tids. The rest are already stopped and we won't + // be receiving stop notifications for them. + m_wait_for_stop_tids.swap (sent_tids); + + // Succeeded, keep running. + return true; + } + + void + RequestStopOnAllRunningThreads (const ThreadStateCoordinator &coordinator) + { + // Request a stop for all the thread stops that need to be stopped + // and are not already known to be stopped. Keep a list of all the + // threads from which we still need to hear a stop reply. + + ThreadIDSet sent_tids; + for (auto it = coordinator.m_tid_stop_map.begin(); it != coordinator.m_tid_stop_map.end(); ++it) + { + // We only care about threads not stopped. + const bool running = !it->second; + if (running) + { + // Request this thread stop. + const lldb::tid_t tid = it->first; + m_request_thread_stop_function (tid); + sent_tids.insert (tid); + } + } + + // Set the wait list to the set of tids for which we requested stops. + m_wait_for_stop_tids.swap (sent_tids); + } + const lldb::tid_t m_triggering_tid; ThreadIDSet m_wait_for_stop_tids; const ThreadIDSet m_original_wait_for_stop_tids; ThreadIDFunction m_request_thread_stop_function; ThreadIDFunction m_call_after_function; ErrorFunction m_error_function; + const bool m_request_stop_on_all_unstopped_threads; }; //===----------------------------------------------------------------------===// @@ -467,6 +526,18 @@ ThreadStateCoordinator::CallAfterThreadsStop (const lldb::tid_t triggering_tid, } void +ThreadStateCoordinator::CallAfterRunningThreadsStop (const lldb::tid_t triggering_tid, + const ThreadIDFunction &request_thread_stop_function, + const ThreadIDFunction &call_after_function, + const ErrorFunction &error_function) +{ + EnqueueEvent (EventBaseSP (new EventCallAfterThreadsStop (triggering_tid, + request_thread_stop_function, + call_after_function, + error_function))); +} + +void ThreadStateCoordinator::ThreadDidStop (lldb::tid_t tid, ErrorFunction &error_function) { // Ensure we know about the thread. diff --git a/lldb/source/Plugins/Process/Linux/ThreadStateCoordinator.h b/lldb/source/Plugins/Process/Linux/ThreadStateCoordinator.h index 3d2ed946163..4e318b53d6e 100644 --- a/lldb/source/Plugins/Process/Linux/ThreadStateCoordinator.h +++ b/lldb/source/Plugins/Process/Linux/ThreadStateCoordinator.h @@ -71,6 +71,16 @@ namespace lldb_private const ThreadIDFunction &call_after_function, const ErrorFunction &error_function); + // This method is the main purpose of the class: triggering a deferred + // action after all non-stopped threads stop. The triggering_tid is the + // thread id passed to the call_after_function. The error_function will + // be fired if the triggering tid is unknown at the time of execution. + void + CallAfterRunningThreadsStop (lldb::tid_t triggering_tid, + const ThreadIDFunction &request_thread_stop_function, + const ThreadIDFunction &call_after_function, + const ErrorFunction &error_function); + // Notify the thread stopped. Will trigger error at time of execution if we // already think it is stopped. void |