This is an archive of the discontinued LLVM Phabricator instance.

Replace the singleton "ShadowListener" with a primary and N secondary listeners
ClosedPublic

Authored by jingham on Aug 9 2023, 3:40 PM.

Details

Summary

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.

Diff Detail

Event Timeline

jingham created this revision.Aug 9 2023, 3:40 PM
Herald added a project: Restricted Project. · View Herald TranscriptAug 9 2023, 3:40 PM
jingham requested review of this revision.Aug 9 2023, 3:40 PM
Herald added a project: Restricted Project. · View Herald TranscriptAug 9 2023, 3:40 PM
mib accepted this revision.Aug 9 2023, 4:12 PM

LGTM with some comments :)

lldb/source/Target/Process.cpp
371

nit: this could probably be constexpr

lldb/source/Utility/Broadcaster.cpp
274–276

nit: We could simplify the logic here a little bit

283

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

This revision is now accepted and ready to land.Aug 9 2023, 4:12 PM
bulbazord added inline comments.Aug 9 2023, 4:40 PM
lldb/include/lldb/Target/Process.h
348–363

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).

621–624

Why was this moved down?

lldb/source/Utility/Broadcaster.cpp
283

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;
284

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).

290

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.

291

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.

lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py
28

Remove this comment?

40–46

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

58

delete?

JDevlieghere added inline comments.Aug 10 2023, 9:59 AM
lldb/include/lldb/Utility/Event.h
228

Nit: trailing whitespace

lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py
28

Remove?

58

Remove?

clayborg added inline comments.Aug 10 2023, 12:04 PM
lldb/source/Target/Process.cpp
371

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

lldb/source/Utility/Broadcaster.cpp
179–182

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

274–276

I agree this would clean this up nicely.

jingham updated this revision to Diff 550495.Aug 15 2023, 3:05 PM

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.

jingham marked 4 inline comments as done.Aug 15 2023, 3:09 PM

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.

lldb/source/Utility/Broadcaster.cpp
179–182

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.

290

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...

291

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.

lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py
40–46

correct.

bulbazord requested changes to this revision.Aug 16 2023, 10:28 AM

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.

lldb/include/lldb/Utility/Event.h
233

nit: trailing semicolon

lldb/source/Target/Process.cpp
620–621

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

lldb/source/Utility/Broadcaster.cpp
59

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.

179–184

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

198–199
This revision now requires changes to proceed.Aug 16 2023, 10:28 AM
This revision was not accepted when it landed; it landed in state Needs Revision.Aug 16 2023, 10:36 AM
This revision was automatically updated to reflect the committed changes.