Skip to content

Commit 44d9692

Browse files
authored
Private process events were being delivered to the secondary listener (#98571)
This fixes a bug where Process events were being delivered to secondary listeners when the Private state thread listener was processing the event. That meant the secondary listener could get an event before the Primary listener did. That in turn meant the state when the secondary listener got the event wasn't right yet. Plus it meant that the secondary listener saw more events than the primary (not all events get forwarded from the private to the public Process listener.) This bug became much more evident when we had a stop hook that did some work, since that delays the Primary listener event delivery. So I also added a stop-hook to the test, and put a little delay in as well.
1 parent 63b16af commit 44d9692

File tree

5 files changed

+78
-10
lines changed

5 files changed

+78
-10
lines changed

lldb/include/lldb/Target/Process.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,8 @@ class Process : public std::enable_shared_from_this<Process>,
465465
static bool SetUpdateStateOnRemoval(Event *event_ptr);
466466

467467
private:
468+
bool ForwardEventToPendingListeners(Event *event_ptr) override;
469+
468470
void SetUpdateStateOnRemoval() { m_update_state++; }
469471

470472
void SetRestarted(bool new_value) { m_restarted = new_value; }

lldb/include/lldb/Utility/Event.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ class EventData {
4848
virtual void Dump(Stream *s) const;
4949

5050
private:
51+
/// This will be queried for a Broadcaster with a primary and some secondary
52+
/// listeners after the primary listener pulled the event from the event queue
53+
/// and ran its DoOnRemoval, right before the event is delivered.
54+
/// If it returns true, the event will also be forwarded to the secondary
55+
/// listeners, and if false, event propagation stops at the primary listener.
56+
/// Some broadcasters (particularly the Process broadcaster) fetch events on
57+
/// a private Listener, and then forward the event to the Public Listeners
58+
/// after some processing. The Process broadcaster does not want to forward
59+
/// to the secondary listeners at the private processing stage.
60+
virtual bool ForwardEventToPendingListeners(Event *event_ptr) { return true; }
61+
5162
virtual void DoOnRemoval(Event *event_ptr) {}
5263

5364
EventData(const EventData &) = delete;

lldb/source/Target/Process.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4236,7 +4236,22 @@ bool Process::ProcessEventData::ShouldStop(Event *event_ptr,
42364236
return still_should_stop;
42374237
}
42384238

4239+
bool Process::ProcessEventData::ForwardEventToPendingListeners(
4240+
Event *event_ptr) {
4241+
// STDIO and the other async event notifications should always be forwarded.
4242+
if (event_ptr->GetType() != Process::eBroadcastBitStateChanged)
4243+
return true;
4244+
4245+
// For state changed events, if the update state is zero, we are handling
4246+
// this on the private state thread. We should wait for the public event.
4247+
return m_update_state == 1;
4248+
}
4249+
42394250
void Process::ProcessEventData::DoOnRemoval(Event *event_ptr) {
4251+
// We only have work to do for state changed events:
4252+
if (event_ptr->GetType() != Process::eBroadcastBitStateChanged)
4253+
return;
4254+
42404255
ProcessSP process_sp(m_process_wp.lock());
42414256

42424257
if (!process_sp)

lldb/source/Utility/Event.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,20 @@ void Event::Dump(Stream *s) const {
8383
void Event::DoOnRemoval() {
8484
std::lock_guard<std::mutex> guard(m_listeners_mutex);
8585

86-
if (m_data_sp)
87-
m_data_sp->DoOnRemoval(this);
86+
if (!m_data_sp)
87+
return;
88+
89+
m_data_sp->DoOnRemoval(this);
90+
8891
// Now that the event has been handled by the primary event Listener, forward
8992
// it to the other Listeners.
93+
9094
EventSP me_sp = shared_from_this();
91-
for (auto listener_sp : m_pending_listeners)
92-
listener_sp->AddEvent(me_sp);
93-
m_pending_listeners.clear();
95+
if (m_data_sp->ForwardEventToPendingListeners(this)) {
96+
for (auto listener_sp : m_pending_listeners)
97+
listener_sp->AddEvent(me_sp);
98+
m_pending_listeners.clear();
99+
}
94100
}
95101

96102
#pragma mark -

lldb/test/API/python_api/event/TestEvents.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from lldbsuite.test.decorators import *
88
from lldbsuite.test.lldbtest import *
99
from lldbsuite.test import lldbutil
10-
10+
import random
1111

1212
@skipIfLinux # llvm.org/pr25924, sometimes generating SIGSEGV
1313
class EventAPITestCase(TestBase):
@@ -20,6 +20,7 @@ def setUp(self):
2020
self.line = line_number(
2121
"main.c", '// Find the line number of function "c" here.'
2222
)
23+
random.seed()
2324

2425
@expectedFailureAll(
2526
oslist=["linux"], bugnumber="llvm.org/pr23730 Flaky, fails ~1/10 cases"
@@ -318,6 +319,7 @@ def wait_for_next_event(self, expected_state, test_shadow=False):
318319
"""Wait for an event from self.primary & self.shadow listener.
319320
If test_shadow is true, we also check that the shadow listener only
320321
receives events AFTER the primary listener does."""
322+
import stop_hook
321323
# Waiting on the shadow listener shouldn't have events yet because
322324
# we haven't fetched them for the primary listener yet:
323325
event = lldb.SBEvent()
@@ -328,12 +330,23 @@ def wait_for_next_event(self, expected_state, test_shadow=False):
328330

329331
# But there should be an event for the primary listener:
330332
success = self.primary_listener.WaitForEvent(5, event)
333+
331334
self.assertTrue(success, "Primary listener got the event")
332335

333336
state = lldb.SBProcess.GetStateFromEvent(event)
337+
primary_event_type = event.GetType()
334338
restart = False
335339
if state == lldb.eStateStopped:
336340
restart = lldb.SBProcess.GetRestartedFromEvent(event)
341+
# This counter is matching the stop hooks, which don't get run
342+
# for auto-restarting stops.
343+
if not restart:
344+
self.stop_counter += 1
345+
self.assertEqual(
346+
stop_hook.StopHook.counter[self.instance],
347+
self.stop_counter,
348+
"matching stop hook",
349+
)
337350

338351
if expected_state is not None:
339352
self.assertEqual(
@@ -344,15 +357,18 @@ def wait_for_next_event(self, expected_state, test_shadow=False):
344357
# listener:
345358
success = self.shadow_listener.WaitForEvent(5, event)
346359
self.assertTrue(success, "Shadow listener got event too")
360+
shadow_event_type = event.GetType()
361+
self.assertEqual(
362+
primary_event_type, shadow_event_type, "It was the same event type"
363+
)
347364
self.assertEqual(
348-
state, lldb.SBProcess.GetStateFromEvent(event), "It was the same event"
365+
state, lldb.SBProcess.GetStateFromEvent(event), "It was the same state"
349366
)
350367
self.assertEqual(
351368
restart,
352369
lldb.SBProcess.GetRestartedFromEvent(event),
353370
"It was the same restarted",
354371
)
355-
356372
return state, restart
357373

358374
@expectedFlakeyLinux("llvm.org/pr23730") # Flaky, fails ~1/100 cases
@@ -386,6 +402,20 @@ def test_shadow_listener(self):
386402
)
387403
self.dbg.SetAsync(True)
388404

405+
# Now make our stop hook - we want to ensure it stays up to date with
406+
# the events. We can't get our hands on the stop-hook instance directly,
407+
# so we'll pass in an instance key, and use that to retrieve the data from
408+
# this instance of the stop hook:
409+
self.instance = f"Key{random.randint(0,10000)}"
410+
stop_hook_path = os.path.join(self.getSourceDir(), "stop_hook.py")
411+
self.runCmd(f"command script import {stop_hook_path}")
412+
import stop_hook
413+
414+
self.runCmd(
415+
f"target stop-hook add -P stop_hook.StopHook -k instance -v {self.instance}"
416+
)
417+
self.stop_counter = 0
418+
389419
self.process = target.Launch(launch_info, error)
390420
self.assertSuccess(error, "Process launched successfully")
391421

@@ -395,6 +425,7 @@ def test_shadow_listener(self):
395425
# Events in the launch sequence might be platform dependent, so don't
396426
# expect any particular event till we get the stopped:
397427
state = lldb.eStateInvalid
428+
398429
while state != lldb.eStateStopped:
399430
state, restart = self.wait_for_next_event(None, False)
400431

@@ -412,8 +443,6 @@ def test_shadow_listener(self):
412443
self.cur_thread.GetStopReasonDataAtIndex(0),
413444
"Hit the right breakpoint",
414445
)
415-
# Disable the first breakpoint so it doesn't get in the way...
416-
bkpt1.SetEnabled(False)
417446

418447
self.cur_thread.StepOver()
419448
# We'll run the test for "shadow listener blocked by primary listener
@@ -450,4 +479,9 @@ def test_shadow_listener(self):
450479
)
451480
if state == lldb.eStateStopped and not restarted:
452481
self.process.Continue()
482+
453483
state, restarted = self.wait_for_next_event(None, False)
484+
485+
# Now make sure we agree with the stop hook counter:
486+
self.assertEqual(self.stop_counter, stop_hook.StopHook.counter[self.instance])
487+
self.assertEqual(stop_hook.StopHook.non_stops[self.instance], 0, "No non stops")

0 commit comments

Comments
 (0)