Author: Jim Ingham
Date: 2023-08-16T10:35:32-07:00
New Revision: 2e7aa2ee34eb53347396731dc8a3b2dbc6a3df45

URL: 
https://github.com/llvm/llvm-project/commit/2e7aa2ee34eb53347396731dc8a3b2dbc6a3df45
DIFF: 
https://github.com/llvm/llvm-project/commit/2e7aa2ee34eb53347396731dc8a3b2dbc6a3df45.diff

LOG: Replace the singleton "ShadowListener" with a primary and N secondary 
Listeners

Before the addition of the process "Shadow Listener" you could only have one
Listener observing the Process Broadcaster.  That was necessary because 
fetching the
Process event is what switches the public process state, and for the execution
control logic to be manageable you needed to keep other listeners from causing
this to happen before the main process control engine was ready.

Ismail added the notion of a "ShadowListener" - which allowed you ONE
extra process listener.  This patch inverts that setup by designating the
first listener as primary - and giving it priority in fetching events.

Differential Revision: https://reviews.llvm.org/D157556

Added: 
    

Modified: 
    lldb/include/lldb/Target/Process.h
    lldb/include/lldb/Utility/Broadcaster.h
    lldb/include/lldb/Utility/Event.h
    lldb/source/Target/Process.cpp
    lldb/source/Utility/Broadcaster.cpp
    lldb/source/Utility/Event.cpp
    lldb/source/Utility/Listener.cpp
    lldb/test/API/api/listeners/TestListener.py
    
lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py
    lldb/test/API/python_api/event/TestEvents.py

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Target/Process.h 
b/lldb/include/lldb/Target/Process.h
index 6ea6f82548aaf8..2186eea7ef5054 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -352,6 +352,14 @@ class Process : public 
std::enable_shared_from_this<Process>,
     eBroadcastBitProfileData = (1 << 4),
     eBroadcastBitStructuredData = (1 << 5),
   };
+  // This is all the event bits the public process broadcaster broadcasts.
+  // The process shadow listener signs up for all these bits...
+  static constexpr int g_all_event_bits = eBroadcastBitStateChanged 
+                                        | eBroadcastBitInterrupt
+                                        | eBroadcastBitSTDOUT 
+                                        | eBroadcastBitSTDERR
+                                        | eBroadcastBitProfileData 
+                                        | eBroadcastBitStructuredData;
 
   enum {
     eBroadcastInternalStateControlStop = (1 << 0),
@@ -382,11 +390,7 @@ class Process : public 
std::enable_shared_from_this<Process>,
   ConstString &GetBroadcasterClass() const override {
     return GetStaticBroadcasterClass();
   }
-
-  void SetShadowListener(lldb::ListenerSP listener_sp) override {
-    Broadcaster::SetShadowListener(listener_sp);
-  }
-
+  
 /// A notification structure that can be used by clients to listen
 /// for changes in a process's lifetime.
 ///
@@ -610,6 +614,15 @@ class Process : public 
std::enable_shared_from_this<Process>,
     return error;
   }
 
+  /// The "ShadowListener" for a process is just an ordinary Listener that 
+  /// listens for all the Process event bits.  It's convenient because you can
+  /// specify it in the LaunchInfo or AttachInfo, so it will get events from
+  /// the very start of the process.
+  void SetShadowListener(lldb::ListenerSP shadow_listener_sp) {
+    if (shadow_listener_sp)
+      AddListener(shadow_listener_sp, g_all_event_bits);
+  }
+
   // FUTURE WORK: GetLoadImageUtilityFunction are the first use we've
   // had of having other plugins cache data in the Process.  This is handy for
   // long-living plugins - like the Platform - which manage interactions whose
@@ -2979,8 +2992,6 @@ void PruneThreadPlans();
   std::vector<Notifications> m_notifications; ///< The list of notifications
                                               ///that this process can deliver.
   std::vector<lldb::addr_t> m_image_tokens;
-  lldb::ListenerSP m_listener_sp; ///< Shared pointer to the listener used for
-                                  ///public events.  Can not be empty.
   BreakpointSiteList m_breakpoint_site_list; ///< This is the list of 
breakpoint
                                              ///locations we intend to insert 
in
                                              ///the target.

diff  --git a/lldb/include/lldb/Utility/Broadcaster.h 
b/lldb/include/lldb/Utility/Broadcaster.h
index f9887566a1609d..8444c38f6ecc65 100644
--- a/lldb/include/lldb/Utility/Broadcaster.h
+++ b/lldb/include/lldb/Utility/Broadcaster.h
@@ -312,8 +312,12 @@ class Broadcaster {
 
   lldb::BroadcasterManagerSP GetManager();
 
-  virtual void SetShadowListener(lldb::ListenerSP listener_sp) {
-    m_broadcaster_sp->m_shadow_listener = listener_sp;
+  void SetPrimaryListener(lldb::ListenerSP listener_sp) {
+    m_broadcaster_sp->SetPrimaryListener(listener_sp);
+  }
+
+  lldb::ListenerSP GetPrimaryListener() {
+    return m_broadcaster_sp->m_primary_listener_sp;
   }
 
 protected:
@@ -377,6 +381,8 @@ class Broadcaster {
 
     bool EventTypeHasListeners(uint32_t event_type);
 
+    void SetPrimaryListener(lldb::ListenerSP listener_sp);
+
     bool RemoveListener(lldb_private::Listener *listener,
                         uint32_t event_mask = UINT32_MAX);
 
@@ -400,7 +406,9 @@ class Broadcaster {
     typedef std::map<uint32_t, std::string> event_names_map;
 
     llvm::SmallVector<std::pair<lldb::ListenerSP, uint32_t &>, 4>
-    GetListeners();
+    GetListeners(uint32_t event_mask = UINT32_MAX, bool include_primary = 
true);
+
+    bool HasListeners(uint32_t event_mask);
 
     /// The broadcaster that this implements.
     Broadcaster &m_broadcaster;
@@ -409,6 +417,27 @@ class Broadcaster {
     /// event bit.
     event_names_map m_event_names;
 
+    /// A Broadcaster can have zero, one or many listeners.  A Broadcaster with
+    /// zero listeners is a no-op, with one Listener is trivial.
+    /// In most cases of multiple Listeners,the Broadcaster treats all its
+    /// Listeners as equal, sending each event to all of the Listeners in no
+    /// guaranteed order.
+    /// However, some Broadcasters - in particular the Process broadcaster, can
+    /// designate one Listener to be the "Primary Listener".  In the case of
+    /// the Process Broadcaster, the Listener passed to the Process constructor
+    /// will be the Primary Listener.
+    /// If the broadcaster has a Primary Listener, then the event gets
+    /// sent first to the Primary Listener, and then when the Primary Listener
+    /// pulls the event and the the event's DoOnRemoval finishes running,
+    /// the event is forwarded to all the other Listeners.
+    /// The other wrinkle is that a Broadcaster may be serving a Hijack
+    /// Listener.  If the Hijack Listener is present, events are only sent to
+    /// the Hijack Listener.  We use that, for instance, to absorb all the
+    /// events generated by running an expression so that they don't show up to
+    /// the driver or UI as starts and stops.
+    /// If a Broadcaster has both a Primary and a Hijack Listener, the top-most
+    /// Hijack Listener is treated as the current Primary Listener.
+
     /// A list of Listener / event_mask pairs that are listening to this
     /// broadcaster.
     collection m_listeners;
@@ -416,6 +445,11 @@ class Broadcaster {
     /// A mutex that protects \a m_listeners.
     std::recursive_mutex m_listeners_mutex;
 
+    /// See the discussion of Broadcasters and Listeners above.
+    lldb::ListenerSP m_primary_listener_sp;
+    // The primary listener listens to all bits:
+    uint32_t m_primary_listener_mask = UINT32_MAX;
+
     /// A simple mechanism to intercept events from a broadcaster
     std::vector<lldb::ListenerSP> m_hijacking_listeners;
 
@@ -423,10 +457,6 @@ class Broadcaster {
     /// for now this is just for private hijacking.
     std::vector<uint32_t> m_hijacking_masks;
 
-    /// A optional listener that all private events get also broadcasted to,
-    /// on top the hijacked / default listeners.
-    lldb::ListenerSP m_shadow_listener = nullptr;
-
   private:
     BroadcasterImpl(const BroadcasterImpl &) = delete;
     const BroadcasterImpl &operator=(const BroadcasterImpl &) = delete;

diff  --git a/lldb/include/lldb/Utility/Event.h 
b/lldb/include/lldb/Utility/Event.h
index 72e390dbee89c8..3de0401191caa7 100644
--- a/lldb/include/lldb/Utility/Event.h
+++ b/lldb/include/lldb/Utility/Event.h
@@ -176,7 +176,7 @@ class EventDataStructuredData : public EventData {
 };
 
 // lldb::Event
-class Event {
+class Event : public std::enable_shared_from_this<Event> {
   friend class Listener;
   friend class EventData;
   friend class Broadcaster::BroadcasterImpl;
@@ -226,6 +226,12 @@ class Event {
 
   void Clear() { m_data_sp.reset(); }
 
+  /// This is used by Broadcasters with Primary Listeners to store the other
+  /// Listeners till after the Event's DoOnRemoval has completed.
+  void AddPendingListener(lldb::ListenerSP pending_listener_sp) {
+    m_pending_listeners.push_back(pending_listener_sp);
+  };
+
 private:
   // This is only called by Listener when it pops an event off the queue for
   // the listener.  It calls the Event Data's DoOnRemoval() method, which is
@@ -244,6 +250,8 @@ class Event {
       m_broadcaster_wp;        // The broadcaster that sent this event
   uint32_t m_type;             // The bit describing this event
   lldb::EventDataSP m_data_sp; // User specific data for this event
+  std::vector<lldb::ListenerSP> m_pending_listeners;
+  std::mutex m_listeners_mutex;
 
   Event(const Event &) = delete;
   const Event &operator=(const Event &) = delete;

diff  --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index ea08ddb6bfedaa..fc08cce096103f 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -438,7 +438,7 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP 
listener_sp,
       m_exit_status_mutex(), m_thread_mutex(), m_thread_list_real(this),
       m_thread_list(this), m_thread_plans(*this), m_extended_thread_list(this),
       m_extended_thread_stop_id(0), m_queue_list(this), 
m_queue_list_stop_id(0),
-      m_notifications(), m_image_tokens(), m_listener_sp(listener_sp),
+      m_notifications(), m_image_tokens(),
       m_breakpoint_site_list(), m_dynamic_checkers_up(),
       m_unix_signals_sp(unix_signals_sp), m_abi_sp(), m_process_input_reader(),
       m_stdio_communication("process.stdio"), m_stdio_communication_mutex(),
@@ -474,10 +474,9 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP 
listener_sp,
   m_private_state_control_broadcaster.SetEventName(
       eBroadcastInternalStateControlResume, "control-resume");
 
-  m_listener_sp->StartListeningForEvents(
-      this, eBroadcastBitStateChanged | eBroadcastBitInterrupt |
-                eBroadcastBitSTDOUT | eBroadcastBitSTDERR |
-                eBroadcastBitProfileData | eBroadcastBitStructuredData);
+  // The listener passed into process creation is the primary listener:
+  // It always listens for all the event bits for Process:
+  SetPrimaryListener(listener_sp);
 
   m_private_state_listener_sp->StartListeningForEvents(
       &m_private_state_broadcaster,
@@ -618,7 +617,7 @@ void Process::SynchronouslyNotifyStateChanged(StateType 
state) {
 StateType Process::GetNextEvent(EventSP &event_sp) {
   StateType state = eStateInvalid;
 
-  if (m_listener_sp->GetEventForBroadcaster(this, event_sp,
+  if (GetPrimaryListener()->GetEventForBroadcaster(this, event_sp,
                                             std::chrono::seconds(0)) &&
       event_sp)
     state = Process::ProcessEventData::GetStateFromEvent(event_sp.get());
@@ -966,7 +965,7 @@ StateType Process::GetStateChangedEvents(EventSP &event_sp,
 
   ListenerSP listener_sp = hijack_listener_sp;
   if (!listener_sp)
-    listener_sp = m_listener_sp;
+    listener_sp = GetPrimaryListener();
 
   StateType state = eStateInvalid;
   if (listener_sp->GetEventForBroadcasterWithType(
@@ -988,7 +987,7 @@ Event *Process::PeekAtStateChangedEvents() {
   LLDB_LOGF(log, "Process::%s...", __FUNCTION__);
 
   Event *event_ptr;
-  event_ptr = m_listener_sp->PeekAtNextEventForBroadcasterWithType(
+  event_ptr = GetPrimaryListener()->PeekAtNextEventForBroadcasterWithType(
       this, eBroadcastBitStateChanged);
   if (log) {
     if (event_ptr) {
@@ -4104,8 +4103,8 @@ void Process::ProcessEventData::DoOnRemoval(Event 
*event_ptr) {
   if (!still_should_stop && does_anybody_have_an_opinion) {
     // We've been asked to continue, so do that here.
     SetRestarted(true);
-    // Use the public resume method here, since this is just extending a
-    // public resume.
+    // Use the private resume method here, since we aren't changing the run
+    // lock state.
     process_sp->PrivateResume();
   } else {
     bool hijacked = process_sp->IsHijackedForEvent(eBroadcastBitStateChanged) 
&&

diff  --git a/lldb/source/Utility/Broadcaster.cpp 
b/lldb/source/Utility/Broadcaster.cpp
index c9ecd4a7d2a912..5192502addbdd7 100644
--- a/lldb/source/Utility/Broadcaster.cpp
+++ b/lldb/source/Utility/Broadcaster.cpp
@@ -50,22 +50,42 @@ void Broadcaster::CheckInWithManager() {
 }
 
 llvm::SmallVector<std::pair<ListenerSP, uint32_t &>, 4>
-Broadcaster::BroadcasterImpl::GetListeners() {
+Broadcaster::BroadcasterImpl::GetListeners(uint32_t event_mask,
+                                           bool include_primary) {
   llvm::SmallVector<std::pair<ListenerSP, uint32_t &>, 4> listeners;
-  listeners.reserve(m_listeners.size());
+  size_t max_count = m_listeners.size();
+  if (include_primary)
+    max_count++;
+  listeners.reserve(max_count);
 
   for (auto it = m_listeners.begin(); it != m_listeners.end();) {
     lldb::ListenerSP curr_listener_sp(it->first.lock());
-    if (curr_listener_sp && it->second) {
-      listeners.emplace_back(std::move(curr_listener_sp), it->second);
+    if (curr_listener_sp) {
+      if (it->second & event_mask)
+        listeners.emplace_back(std::move(curr_listener_sp), it->second);
       ++it;
     } else
+      // If our listener_wp didn't resolve, then we should remove this entry.
       it = m_listeners.erase(it);
   }
+  if (include_primary && m_primary_listener_sp)
+    listeners.emplace_back(m_primary_listener_sp, m_primary_listener_mask);
 
   return listeners;
 }
 
+bool Broadcaster::BroadcasterImpl::HasListeners(uint32_t event_mask) {
+  if (m_primary_listener_sp)
+    return true;
+  for (auto it = m_listeners.begin(); it != m_listeners.end(); it++) {
+    // Don't return a listener if the other end of the WP is gone:
+    lldb::ListenerSP curr_listener_sp(it->first.lock());
+    if (curr_listener_sp && (it->second & event_mask))
+      return true;
+  }
+  return false;
+}
+
 void Broadcaster::BroadcasterImpl::Clear() {
   std::lock_guard<std::recursive_mutex> guard(m_listeners_mutex);
 
@@ -75,6 +95,7 @@ void Broadcaster::BroadcasterImpl::Clear() {
     pair.first->BroadcasterWillDestruct(&m_broadcaster);
 
   m_listeners.clear();
+  m_primary_listener_sp.reset();
 }
 
 Broadcaster *Broadcaster::BroadcasterImpl::GetBroadcaster() {
@@ -122,7 +143,11 @@ Broadcaster::BroadcasterImpl::AddListener(const 
lldb::ListenerSP &listener_sp,
 
   bool handled = false;
 
-  for (auto &pair : GetListeners()) {
+  if (listener_sp == m_primary_listener_sp)
+    // This already handles all bits so just return the mask:
+    return event_mask;
+
+  for (auto &pair : GetListeners(UINT32_MAX, false)) {
     if (pair.first == listener_sp) {
       handled = true;
       pair.second |= event_mask;
@@ -151,11 +176,11 @@ bool 
Broadcaster::BroadcasterImpl::EventTypeHasListeners(uint32_t event_type) {
   if (!m_hijacking_listeners.empty() && event_type & m_hijacking_masks.back())
     return true;
 
-  for (auto &pair : GetListeners()) {
-    if (pair.second & event_type)
-      return true;
-  }
-  return false;
+  // The primary listener listens for all event bits:
+  if (m_primary_listener_sp)
+    return true;
+
+  return HasListeners(event_type);
 }
 
 bool Broadcaster::BroadcasterImpl::RemoveListener(
@@ -163,12 +188,33 @@ bool Broadcaster::BroadcasterImpl::RemoveListener(
   if (!listener)
     return false;
 
+  if (listener == m_primary_listener_sp.get()) {
+    // Primary listeners listen for all the event bits for their broadcaster,
+    // so remove this altogether if asked:
+    m_primary_listener_sp.reset();
+    return true;
+  }
+
   std::lock_guard<std::recursive_mutex> guard(m_listeners_mutex);
-  for (auto &pair : GetListeners()) {
-    if (pair.first.get() == listener) {
-      pair.second &= ~event_mask;
-      return true;
+  for (auto it = m_listeners.begin(); it != m_listeners.end();) {
+    lldb::ListenerSP curr_listener_sp(it->first.lock());
+
+    if (!curr_listener_sp) {
+      // The weak pointer for this listener didn't resolve, lets' prune it
+      // as we go.
+      m_listeners.erase(it);
+      continue;
     }
+
+    if (curr_listener_sp.get() == listener) {
+      it->second &= ~event_mask;
+      // If we removed all the event bits from a listener, remove it from
+      // the list as well.
+      if (!it->second)
+        m_listeners.erase(it);
+      return true;
+    } else
+      it++;
   }
   return false;
 }
@@ -222,25 +268,34 @@ void 
Broadcaster::BroadcasterImpl::PrivateBroadcastEvent(EventSP &event_sp,
              event_description.GetData(), unique,
              static_cast<void *>(hijacking_listener_sp.get()));
   }
+  ListenerSP primary_listener_sp
+      = hijacking_listener_sp ? hijacking_listener_sp : m_primary_listener_sp;
 
-  if (hijacking_listener_sp) {
-    if (unique && hijacking_listener_sp->PeekAtNextEventForBroadcasterWithType(
+  if (primary_listener_sp) {
+    if (unique && primary_listener_sp->PeekAtNextEventForBroadcasterWithType(
                       &m_broadcaster, event_type))
       return;
-    hijacking_listener_sp->AddEvent(event_sp);
-    if (m_shadow_listener)
-      m_shadow_listener->AddEvent(event_sp);
+    // Add the pending listeners but not if the event is hijacked, since that
+    // is given sole access to the event stream it is hijacking.
+    // Make sure to do this before adding the event to the primary or it might
+    // start handling the event before we're done adding all the pending
+    // listeners.
+    if (!hijacking_listener_sp) {
+      for (auto &pair : GetListeners(event_type, false)) {
+        if (unique && pair.first->PeekAtNextEventForBroadcasterWithType(
+                          &m_broadcaster, event_type))
+          continue;
+        event_sp->AddPendingListener(pair.first);
+      }
+    }
+    primary_listener_sp->AddEvent(event_sp);
   } else {
-    for (auto &pair : GetListeners()) {
-      if (!(pair.second & event_type))
-        continue;
+    for (auto &pair : GetListeners(event_type)) {
       if (unique && pair.first->PeekAtNextEventForBroadcasterWithType(
                         &m_broadcaster, event_type))
         continue;
 
       pair.first->AddEvent(event_sp);
-      if (m_shadow_listener)
-        m_shadow_listener->AddEvent(event_sp);
     }
   }
 }
@@ -263,6 +318,15 @@ void Broadcaster::BroadcasterImpl::BroadcastEventIfUnique(
   PrivateBroadcastEvent(event_sp, true);
 }
 
+void Broadcaster::BroadcasterImpl::SetPrimaryListener(lldb::ListenerSP
+                                                      listener_sp) {
+  // This might have already been added as a normal listener, make sure we
+  // don't hold two copies.
+  RemoveListener(listener_sp.get(), UINT32_MAX);
+  m_primary_listener_sp = listener_sp;
+                                                      
+}
+
 bool Broadcaster::BroadcasterImpl::HijackBroadcaster(
     const lldb::ListenerSP &listener_sp, uint32_t event_mask) {
   std::lock_guard<std::recursive_mutex> guard(m_listeners_mutex);

diff  --git a/lldb/source/Utility/Event.cpp b/lldb/source/Utility/Event.cpp
index fcc367f43f935e..cac118182c75da 100644
--- a/lldb/source/Utility/Event.cpp
+++ b/lldb/source/Utility/Event.cpp
@@ -11,6 +11,7 @@
 #include "lldb/Utility/Broadcaster.h"
 #include "lldb/Utility/DataExtractor.h"
 #include "lldb/Utility/Endian.h"
+#include "lldb/Utility/Listener.h"
 #include "lldb/Utility/Stream.h"
 #include "lldb/Utility/StreamString.h"
 #include "lldb/lldb-enumerations.h"
@@ -80,8 +81,16 @@ void Event::Dump(Stream *s) const {
 }
 
 void Event::DoOnRemoval() {
+  std::lock_guard<std::mutex> guard(m_listeners_mutex);
+
   if (m_data_sp)
     m_data_sp->DoOnRemoval(this);
+  // Now that the event has been handled by the primary event Listener, forward
+  // it to the other Listeners.
+  EventSP me_sp = shared_from_this();
+  for (auto listener_sp : m_pending_listeners)
+    listener_sp->AddEvent(me_sp);
+  m_pending_listeners.clear();
 }
 
 #pragma mark -

diff  --git a/lldb/source/Utility/Listener.cpp 
b/lldb/source/Utility/Listener.cpp
index 48ea5fca899e30..6a74c530ad2574 100644
--- a/lldb/source/Utility/Listener.cpp
+++ b/lldb/source/Utility/Listener.cpp
@@ -231,8 +231,7 @@ bool Listener::FindNextEventInternal(
       // to return it so it should be okay to get the next event off the queue
       // here - and it might be useful to do that in the "DoOnRemoval".
       lock.unlock();
-      if (!m_is_shadow)
-        event_sp->DoOnRemoval();
+      event_sp->DoOnRemoval();
     }
     return true;
   }

diff  --git a/lldb/test/API/api/listeners/TestListener.py 
b/lldb/test/API/api/listeners/TestListener.py
index 8987a2e6187972..55c8072e6fafe7 100644
--- a/lldb/test/API/api/listeners/TestListener.py
+++ b/lldb/test/API/api/listeners/TestListener.py
@@ -52,7 +52,6 @@ def test_receiving_breakpoint_added_from_debugger(self):
         self.build()
 
         my_listener = lldb.SBListener("test_listener")
-
         my_listener.StartListeningForEventClass(
             self.dbg,
             lldb.SBTarget.GetBroadcasterClassName(),

diff  --git 
a/lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py
 
b/lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py
index 6c531550c95e5d..528c9fdce69ce3 100644
--- 
a/lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py
+++ 
b/lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py
@@ -22,8 +22,10 @@ def setUp(self):
         self.script_module = "interactive_scripted_process"
         self.script_file = self.script_module + ".py"
 
+    # These tests are flakey and sometimes timeout.  They work most of the time
+    # so the basic event flow is right, but somehow the handling is off.
     @skipUnlessDarwin
-    @skipIfDarwin
+    @skipIfDarwin 
     def test_passthrough_launch(self):
         """Test a simple pass-through process launch"""
         self.passthrough_launch()
@@ -34,6 +36,15 @@ def test_passthrough_launch(self):
         self.assertSuccess(error, "Resuming multiplexer scripted process")
         self.assertTrue(self.mux_process.IsValid(), "Got a valid process")
 
+        event = lldbutil.fetch_next_event(
+            self, self.dbg.GetListener(), self.mux_process.GetBroadcaster(), 
timeout=20
+        )
+        self.assertState(lldb.SBProcess.GetStateFromEvent(event), 
lldb.eStateRunning)
+        event = lldbutil.fetch_next_event(
+            self, self.dbg.GetListener(), self.mux_process.GetBroadcaster()
+        )
+        self.assertState(lldb.SBProcess.GetStateFromEvent(event), 
lldb.eStateStopped)
+
         event = lldbutil.fetch_next_event(
             self, self.mux_process_listener, self.mux_process.GetBroadcaster()
         )
@@ -178,6 +189,13 @@ def fetch_process_event(self, execution_events):
             )
             execution_events[event_target_idx][state] = True
 
+        for _ in range((self.dbg.GetNumTargets() - 1) * 2):
+            fetch_process_event(self, execution_events)
+
+        for target_index, event_states in execution_events.items():
+            for state, is_set in event_states.items():
+                self.assertTrue(is_set, f"Target {target_index} has state 
{state} set")
+
         event = lldbutil.fetch_next_event(
             self, self.mux_process_listener, self.mux_process.GetBroadcaster()
         )
@@ -188,13 +206,6 @@ def fetch_process_event(self, execution_events):
         )
         self.assertState(lldb.SBProcess.GetStateFromEvent(event), 
lldb.eStateStopped)
 
-        for _ in range((self.dbg.GetNumTargets() - 1) * 2):
-            fetch_process_event(self, execution_events)
-
-        for target_index, event_states in execution_events.items():
-            for state, is_set in event_states.items():
-                self.assertTrue(is_set, f"Target {target_index} has state 
{state} set")
-
     def duplicate_target(self, driving_target):
         exe = driving_target.executable.fullpath
         triple = driving_target.triple
@@ -248,14 +259,14 @@ def passthrough_launch(self):
         self.assertSuccess(error, "Launched multiplexer scripted process")
         self.assertTrue(self.mux_process.IsValid(), "Got a valid process")
 
-        # Check that the mux process started running
+        # Check that the real process started running
         event = lldbutil.fetch_next_event(
-            self, self.mux_process_listener, self.mux_process.GetBroadcaster()
+            self, self.dbg.GetListener(), self.mux_process.GetBroadcaster()
         )
         self.assertState(lldb.SBProcess.GetStateFromEvent(event), 
lldb.eStateRunning)
-        # Check that the real process started running
+        # Check that the mux process started running
         event = lldbutil.fetch_next_event(
-            self, self.dbg.GetListener(), self.mux_process.GetBroadcaster()
+            self, self.mux_process_listener, self.mux_process.GetBroadcaster()
         )
         self.assertState(lldb.SBProcess.GetStateFromEvent(event), 
lldb.eStateRunning)
 

diff  --git a/lldb/test/API/python_api/event/TestEvents.py 
b/lldb/test/API/python_api/event/TestEvents.py
index e009fb3bfaa7aa..a8a67a94a84345 100644
--- a/lldb/test/API/python_api/event/TestEvents.py
+++ b/lldb/test/API/python_api/event/TestEvents.py
@@ -313,3 +313,121 @@ def run(self):
         self.assertEqual(
             self.state, "stopped", "Both expected state changed events 
received"
         )
+
+    def wait_for_next_event(self, expected_state, test_shadow = False):
+        """Wait for an event from self.primary & self.shadow listener.
+           If test_shadow is true, we also check that the shadow listener only 
+           receives events AFTER the primary listener does."""
+        # Waiting on the shadow listener shouldn't have events yet because
+        # we haven't fetched them for the primary listener yet:
+        event = lldb.SBEvent()
+
+        if test_shadow:
+            success = self.shadow_listener.WaitForEvent(1, event)
+            self.assertFalse(success, "Shadow listener doesn't pull events")
+
+        # But there should be an event for the primary listener:
+        success = self.primary_listener.WaitForEvent(5, event)
+        self.assertTrue(success, "Primary listener got the event")
+
+        state = lldb.SBProcess.GetStateFromEvent(event)
+        restart = False
+        if state == lldb.eStateStopped:
+            restart = lldb.SBProcess.GetRestartedFromEvent(event)
+
+        if expected_state != None:
+            self.assertEqual(state, expected_state, "Primary thread got the 
correct event")
+            
+        # And after pulling that one there should be an equivalent event for 
the shadow
+        # listener:
+        success = self.shadow_listener.WaitForEvent(5, event)
+        self.assertTrue(success, "Shadow listener got event too")
+        self.assertEqual(state, lldb.SBProcess.GetStateFromEvent(event), "It 
was the same event")
+        self.assertEqual(restart, lldb.SBProcess.GetRestartedFromEvent(event), 
"It was the same restarted")
+            
+        return state, restart
+
+    def test_shadow_listener(self):
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+
+        # Create a target by the debugger.
+        target = self.dbg.CreateTarget(exe)
+        self.assertTrue(target, VALID_TARGET)
+
+        # Now create a breakpoint on main.c by name 'c'.
+        bkpt1 = target.BreakpointCreateByName("c", "a.out")
+        self.trace("breakpoint:", bkpt1)
+        self.assertTrue(bkpt1.GetNumLocations() == 1, VALID_BREAKPOINT)
+
+        self.primary_listener = lldb.SBListener("my listener")
+        self.shadow_listener = lldb.SBListener("shadow listener")
+
+        self.cur_thread = None
+        
+        error = lldb.SBError()
+        launch_info = target.GetLaunchInfo()
+        launch_info.SetListener(self.primary_listener)
+        launch_info.SetShadowListener(self.shadow_listener)
+
+        self.runCmd("settings set target.process.extra-startup-command 
QSetLogging:bitmask=LOG_PROCESS|LOG_EXCEPTIONS|LOG_RNB_PACKETS|LOG_STEP;")
+        self.dbg.SetAsync(True)
+
+        self.process = target.Launch(launch_info, error)
+        self.assertSuccess(error, "Process launched successfully")
+
+        # Keep fetching events from the primary to trigger the do on removal 
and
+        # then from the shadow listener, and make sure they match:
+
+        # Events in the launch sequence might be platform dependent, so don't
+        # expect any particular event till we get the stopped:
+        state = lldb.eStateInvalid
+        while state != lldb.eStateStopped:
+            state, restart = self.wait_for_next_event(None, False)
+
+        # Okay, we're now at a good stop, so try a next:
+        self.cur_thread = self.process.threads[0]
+
+        # Make sure we're at our expected breakpoint:
+        self.assertTrue(self.cur_thread.IsValid(), "Got a zeroth thread")
+        self.assertEqual(self.cur_thread.stop_reason, 
lldb.eStopReasonBreakpoint)
+        self.assertEqual(self.cur_thread.GetStopReasonDataCount(), 2, "Only 
one breakpoint/loc here")
+        self.assertEqual(bkpt1.GetID(), 
self.cur_thread.GetStopReasonDataAtIndex(0), "Hit the right breakpoint")
+        # Disable the first breakpoint so it doesn't get in the way...
+        bkpt1.SetEnabled(False)
+
+        self.cur_thread.StepOver()
+        # We'll run the test for "shadow listener blocked by primary listener
+        # for the first couple rounds, then we'll skip the 1 second pause...
+        self.wait_for_next_event(lldb.eStateRunning, True)
+        self.wait_for_next_event(lldb.eStateStopped, True)
+
+        # Next try an auto-continue breakpoint and make sure the shadow 
listener got
+        # the resumed info as well.  Note that I'm not explicitly counting
+        # running events here.  At the point when I wrote this lldb sometimes
+        # emits two running events in a row.  Apparently the code to coalesce 
running
+        # events isn't working.  But that's not what this test is testing, 
we're really
+        # testing that the primary & shadow listeners hear the same thing and 
in the
+        # right order.
+
+        main_spec = lldb.SBFileSpec("main.c")
+        bkpt2 = target.BreakpointCreateBySourceRegex("b.2. returns %d", 
main_spec)
+        self.assertTrue(bkpt2.GetNumLocations() > 0, "BP2 worked")
+        bkpt2.SetAutoContinue(True)
+
+        bkpt3 = target.BreakpointCreateBySourceRegex("a.3. returns %d", 
main_spec)
+        self.assertTrue(bkpt3.GetNumLocations() > 0, "BP3 worked")
+
+        state = lldb.eStateStopped
+        restarted = False
+
+        # Put in a counter to make sure we don't spin forever if there is some
+        # error in the logic.
+        counter = 0
+        while state != lldb.eStateExited:
+            counter += 1
+            self.assertLess(counter, 50, "Took more than 50 events to hit two 
breakpoints.")
+            if state == lldb.eStateStopped and not restarted:
+                self.process.Continue()
+            state, restarted  = self.wait_for_next_event(None, False)
+            


        
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to