[Lldb-commits] [PATCH] D157556: Replace the singleton "ShadowListener" with a primary and N secondary listeners

2023-08-16 Thread Jim Ingham via Phabricator via lldb-commits
This revision was not accepted when it landed; it landed in state "Needs 
Revision".
This revision was automatically updated to reflect the committed changes.
Closed by commit rG2e7aa2ee34eb: Replace the singleton 
"ShadowListener" with a primary and N secondary Listeners (authored 
by jingham).

Changed prior to commit:
  https://reviews.llvm.org/D157556?vs=550495&id=550806#toc

Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D157556/new/

https://reviews.llvm.org/D157556

Files:
  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

Index: lldb/test/API/python_api/event/TestEvents.py
===
--- lldb/test/API/python_api/event/TestEvents.py
+++ lldb/test/API/python_api/event/TestEvents.py
@@ -313,3 +313,121 @@
 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(

[Lldb-commits] [PATCH] D157556: Replace the singleton "ShadowListener" with a primary and N secondary listeners

2023-08-16 Thread Alex Langford via Phabricator via lldb-commits
bulbazord requested changes to this revision.
bulbazord added a comment.
This revision now requires changes to proceed.

Thanks for all the work to clean up some of those interfaces! I know that was 
not straightforward. :)

I left a few more comments about minor things I noticed while re-reading this. 
I'm going to request changes to put this back into your queue.




Comment at: lldb/include/lldb/Utility/Event.h:233
+m_pending_listeners.push_back(pending_listener_sp);
+  };
+

nit: trailing semicolon



Comment at: lldb/source/Target/Process.cpp:620-621
 
-  if (m_listener_sp->GetEventForBroadcaster(this, event_sp,
+  if (GetPrimaryListener()->GetEventForBroadcaster(this, event_sp,
 std::chrono::seconds(0)) &&
   event_sp)

Do you need to check for the validity of the primary listener before using it? 
It can point to nullptr from what I can tell



Comment at: lldb/source/Utility/Broadcaster.cpp:59
+max_count++;
+  listeners.reserve(max_count);
 

I wonder if we should be reserving at all considering we're using an 
`llvm::SmallVector<$type, 4>`.  The point of a SmallVector is that on average 
it should be small and only sometimes should we be exceeding that hardcoded 
size. This means that if there are 8 listeners but only 2 are relevant, we're 
taking a SmallVector of size 4 and allocating space for 8 listeners (always 
triggering a memory allocation) even if it will only ever have 2 listeners in 
it.

I know that's the existing behavior and you're just preserving it, but 
something to think about for the future.



Comment at: lldb/source/Utility/Broadcaster.cpp:179-181
+  // The primary listener listens for all event bits:
+  if (m_primary_listener_sp)
+return true;

This check is a bit redundant since `HasListeners` performs the exact same check



Comment at: lldb/source/Utility/Broadcaster.cpp:198
+
   std::lock_guard guard(m_listeners_mutex);
+  for (auto it = m_listeners.begin(); it != m_listeners.end();) {




Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D157556/new/

https://reviews.llvm.org/D157556

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


[Lldb-commits] [PATCH] D157556: Replace the singleton "ShadowListener" with a primary and N secondary listeners

2023-08-15 Thread Jim Ingham via Phabricator via lldb-commits
jingham marked 4 inline comments as done.
jingham added a comment.

I implemented Alex's suggestion for a GetListeners filter, but doing that 
triggered some bugs due to the fact that we weren't really pruning the 
m_listeners correctly as listeners come and go.  I fixed some bugs there along 
with implementing this suggestion.




Comment at: lldb/source/Utility/Broadcaster.cpp:154-157
+  // The primary listener listens for all event bits:
+  if (m_primary_listener_sp)
+return true;
+

clayborg wrote:
> Does the primary listener need to listen to all event bits?
I don't see a use case for saying "I want to be the one that gets first crack 
at this broadcaster" but only for some bits.  Plus, if we do it that way, we'd 
really have to have a map of "primary listener -> event mask" and add a way to 
make yourself the primary listener for only some bits.

We have this ability for HijackListeners, but we never use it, we always just 
hijack all bits as well...

There's no reason you can't do it that way, but until we see a need for 
multiple primary listeners, I don't think the extra complexity is warranted.



Comment at: lldb/source/Utility/Broadcaster.cpp:253
+  continue;
+if (pair.first != hijacking_listener_sp 
+&& pair.first != m_primary_listener_sp)

bulbazord wrote:
> nit: Checking to see if `pair.first` is `hijacking_listener_sp` is redundant. 
> If you've gotten to this point, `hijacking_listener_sp` must be pointing to 
> `nullptr`.
Interestingly enough, that was effectively a check on empty listeners in 
m_listeners.  There shouldn't have been any, but we weren't being very careful 
about pruning the list...



Comment at: lldb/source/Utility/Broadcaster.cpp:254
+if (pair.first != hijacking_listener_sp 
+&& pair.first != m_primary_listener_sp)
+  event_sp->AddPendingListener(pair.first);

bulbazord wrote:
> Why might `pair.first` be `m_primary_listener_sp`? I would assume that if a 
> primary listener is set, it wouldn't be among the other listeners in a 
> broadcaster But I suppose nothing stops you from doing that.
That's really an oversight in GetListeners.  That correctly doesn't return the 
Hijack listener, since that is a hidden agent, but the primary listener only 
differs from the secondary listeners in how dispatch gets done.  I fixed that 
in this patch.



Comment at: 
lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py:38-44
+self, self.dbg.GetListener(), self.mux_process.GetBroadcaster()
+)
+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)

bulbazord wrote:
> This is to make sure that the primary listener is getting the run/stop events 
> before the `mux_process_listener` right?
correct.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D157556/new/

https://reviews.llvm.org/D157556

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


[Lldb-commits] [PATCH] D157556: Replace the singleton "ShadowListener" with a primary and N secondary listeners

2023-08-15 Thread Jim Ingham via Phabricator via lldb-commits
jingham updated this revision to Diff 550495.
jingham added a comment.

Respond to review comments.

In the course of implementing Alex's suggestion to add a filter to 
GetListeners, I noticed that there we were being lax in our dealing with 
removed listeners.  RemoveListener didn't actually do that, it just removed the 
event bits, and the next pass through GetListeners would actually clear out the 
entry.

Fixing that required a few other little fixes to get this working again.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D157556/new/

https://reviews.llvm.org/D157556

Files:
  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

Index: lldb/test/API/python_api/event/TestEvents.py
===
--- lldb/test/API/python_api/event/TestEvents.py
+++ lldb/test/API/python_api/event/TestEvents.py
@@ -313,3 +313,121 @@
 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.s

[Lldb-commits] [PATCH] D157556: Replace the singleton "ShadowListener" with a primary and N secondary listeners

2023-08-10 Thread Greg Clayton via Phabricator via lldb-commits
clayborg added inline comments.



Comment at: lldb/source/Target/Process.cpp:372
 
+const int Process::g_all_event_bits = eBroadcastBitStateChanged 
+  | eBroadcastBitInterrupt

mib wrote:
> nit: this could probably be `constexpr`
yes, this can be a constexpr and be defined in only in the header file. No need 
to create storage for it by making it a real static variable



Comment at: lldb/source/Utility/Broadcaster.cpp:154-157
+  // The primary listener listens for all event bits:
+  if (m_primary_listener_sp)
+return true;
+

Does the primary listener need to listen to all event bits?



Comment at: lldb/source/Utility/Broadcaster.cpp:229-235
+  ListenerSP primary_listener_sp = hijacking_listener_sp;
+  bool is_hijack = false;
+  
+  if (primary_listener_sp)
+is_hijack = true;
+  else
+primary_listener_sp = m_primary_listener_sp;

mib wrote:
> nit: We could simplify the logic here a little bit
I agree this would clean this up nicely.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D157556/new/

https://reviews.llvm.org/D157556

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


[Lldb-commits] [PATCH] D157556: Replace the singleton "ShadowListener" with a primary and N secondary listeners

2023-08-10 Thread Jonas Devlieghere via Phabricator via lldb-commits
JDevlieghere added inline comments.



Comment at: lldb/include/lldb/Utility/Event.h:228
   void Clear() { m_data_sp.reset(); }
+  
+  /// This is used by Broadcasters with Primary Listeners to store the other

Nit: trailing whitespace



Comment at: 
lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py:26
 @skipUnlessDarwin
-@skipIfDarwin
+#@skipIfDarwin
 def test_passthrough_launch(self):

bulbazord wrote:
> Remove this comment?
Remove?



Comment at: 
lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py:56
 @skipUnlessDarwin
-@skipIfDarwin
+#@skipIfDarwin
 def test_multiplexed_launch(self):

bulbazord wrote:
> delete?
Remove?


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D157556/new/

https://reviews.llvm.org/D157556

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


[Lldb-commits] [PATCH] D157556: Replace the singleton "ShadowListener" with a primary and N secondary listeners

2023-08-09 Thread Alex Langford via Phabricator via lldb-commits
bulbazord added inline comments.



Comment at: lldb/include/lldb/Target/Process.h:348-357
 eBroadcastBitStateChanged = (1 << 0),
 eBroadcastBitInterrupt = (1 << 1),
 eBroadcastBitSTDOUT = (1 << 2),
 eBroadcastBitSTDERR = (1 << 3),
 eBroadcastBitProfileData = (1 << 4),
 eBroadcastBitStructuredData = (1 << 5),
   };

I wonder if it might be better to add a new element to the enum, something like 
`eBroadcastBitAll = eBroadcastBitStageChanged | ... | 
eBroadcastBitStructuredData,`.

If we have to add a new element to this enumeration, I'm not sure everyone will 
realize that `g_all_event_bits` needs to be updated in a separate file (even 
though it's right below the enum definition).



Comment at: lldb/include/lldb/Target/Process.h:616-619
+  void SetShadowListener(lldb::ListenerSP shadow_listener_sp) {
+if (shadow_listener_sp)
+  AddListener(shadow_listener_sp, g_all_event_bits);
+  }

Why was this moved down?



Comment at: lldb/source/Utility/Broadcaster.cpp:246
+// listeners.
+if (!is_hijack) {
+  for (auto &pair : GetListeners()) {

mib wrote:
> Then, as a follow-up to my suggestion, you could check `hijacking_listener_sp 
> ` instead of checking the boolean.
Instead of having the bool flag for `is_hijack`, you can compare 
`primary_listener_sp` against `hijacking_listener_sp`.

Then the setup above gets less complex, like so:
```
ListenerSP primary_listener_sp = hijacking_listener_sp;
if (!primary_listener_sp)
  primary_listener_sp = m_primary_listener_sp;
```



Comment at: lldb/source/Utility/Broadcaster.cpp:247
+if (!is_hijack) {
+  for (auto &pair : GetListeners()) {
+if (!(pair.second & event_type))

I wonder if it might be useful to introduce a variant of `GetListeners` which 
takes an `event_type` and only gives you back the ones you want. That would 
simplify this loop (and the one in the other branching path). You could also 
just pass that vector along to the `Event` instead of filtering and adding them 
one at a time (so you're not paying for potential vector resizing costs).



Comment at: lldb/source/Utility/Broadcaster.cpp:253
+  continue;
+if (pair.first != hijacking_listener_sp 
+&& pair.first != m_primary_listener_sp)

nit: Checking to see if `pair.first` is `hijacking_listener_sp` is redundant. 
If you've gotten to this point, `hijacking_listener_sp` must be pointing to 
`nullptr`.



Comment at: lldb/source/Utility/Broadcaster.cpp:254
+if (pair.first != hijacking_listener_sp 
+&& pair.first != m_primary_listener_sp)
+  event_sp->AddPendingListener(pair.first);

Why might `pair.first` be `m_primary_listener_sp`? I would assume that if a 
primary listener is set, it wouldn't be among the other listeners in a 
broadcaster But I suppose nothing stops you from doing that.



Comment at: 
lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py:26
 @skipUnlessDarwin
-@skipIfDarwin
+#@skipIfDarwin
 def test_passthrough_launch(self):

Remove this comment?



Comment at: 
lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py:38-44
+self, self.dbg.GetListener(), self.mux_process.GetBroadcaster()
+)
+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)

This is to make sure that the primary listener is getting the run/stop events 
before the `mux_process_listener` right?



Comment at: 
lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py:56
 @skipUnlessDarwin
-@skipIfDarwin
+#@skipIfDarwin
 def test_multiplexed_launch(self):

delete?


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D157556/new/

https://reviews.llvm.org/D157556

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


[Lldb-commits] [PATCH] D157556: Replace the singleton "ShadowListener" with a primary and N secondary listeners

2023-08-09 Thread Med Ismail Bennani via Phabricator via lldb-commits
mib accepted this revision.
mib added a comment.
This revision is now accepted and ready to land.

LGTM with some comments :)




Comment at: lldb/source/Target/Process.cpp:372
 
+const int Process::g_all_event_bits = eBroadcastBitStateChanged 
+  | eBroadcastBitInterrupt

nit: this could probably be `constexpr`



Comment at: lldb/source/Utility/Broadcaster.cpp:229-235
+  ListenerSP primary_listener_sp = hijacking_listener_sp;
+  bool is_hijack = false;
+  
+  if (primary_listener_sp)
+is_hijack = true;
+  else
+primary_listener_sp = m_primary_listener_sp;

nit: We could simplify the logic here a little bit



Comment at: lldb/source/Utility/Broadcaster.cpp:246
+// listeners.
+if (!is_hijack) {
+  for (auto &pair : GetListeners()) {

Then, as a follow-up to my suggestion, you could check `hijacking_listener_sp ` 
instead of checking the boolean.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D157556/new/

https://reviews.llvm.org/D157556

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


[Lldb-commits] [PATCH] D157556: Replace the singleton "ShadowListener" with a primary and N secondary listeners

2023-08-09 Thread Jim Ingham via Phabricator via lldb-commits
jingham created this revision.
jingham added reviewers: mib, bulbazord, clayborg, JDevlieghere.
Herald added a project: All.
jingham requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

Allow a Broadcaster to have one "Primary" listener, and potentially many 
secondary Listeners.  The primary listener is guaranteed to run the DoOnRemoval 
first, and only when that is complete will secondary Listeners receive the 
event.

Prior to Ismail's addition of the notion of a "Secondary" listener, you could 
only safely observe process events from the Listener you passed in to the 
Process creation.  That was because the act of pulling the process event off 
the event queue triggered the change of the public state of the process (thus 
keeping the event stream and the state in sync).

Ismail added a privileged "shadow listener" that could also be informed of the 
event.  That wasn't quite the right model, however, because the real singleton 
is the primary listener.

That's what this patch implements.

This isn't quite the full solution for the Process Listener.  The goal is that 
the primary Process Listener not only gets to drive the event stream, but that 
the primary listener gets to react to each event before any other Listener can 
hear about it.  That will allow the process execution logic to run before any 
other agent gets a chance to change the process state out from under it.  For 
that, I'll need to add a receipt mechanism to the event delivery, and have the 
forwarding to the pending listeners happen only after the receipt is 
acknowledged.  But that will be a follow on patch.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D157556

Files:
  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/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py
  lldb/test/API/python_api/event/TestEvents.py

Index: lldb/test/API/python_api/event/TestEvents.py
===
--- lldb/test/API/python_api/event/TestEvents.py
+++ lldb/test/API/python_api/event/TestEvents.py
@@ -313,3 +313,121 @@
 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_