Skip to content

Commit ba29e60

Browse files
authored
[lldb-dap] Change the launch sequence (#138219)
This PR changes how we treat the launch sequence in lldb-dap. - Send the initialized event after we finish handling the initialize request, rather than after we finish attaching or launching. - Delay handling the launch and attach request until we have handled the configurationDone request. The latter is now largely a NO-OP and only exists to signal lldb-dap that it can handle the launch and attach requests. - Delay handling the initial threads requests until we have handled the launch or attach request. - Make all attaching and launching synchronous, including when we have attach or launch commands. This removes the need to synchronize between the request and event thread. Background: https://discourse.llvm.org/t/reliability-of-the-lldb-dap-tests/86125
1 parent 0fb5720 commit ba29e60

24 files changed

+252
-219
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ def __init__(self, recv, send, init_commands, log_file=None):
132132
self.exit_status = None
133133
self.initialize_body = None
134134
self.thread_stop_reasons = {}
135-
self.breakpoint_events = []
136135
self.progress_events = []
137136
self.reverse_requests = []
138137
self.module_events = []
@@ -244,13 +243,6 @@ def handle_recv_packet(self, packet):
244243
self._process_stopped()
245244
tid = body["threadId"]
246245
self.thread_stop_reasons[tid] = body
247-
elif event == "breakpoint":
248-
# Breakpoint events come in when a breakpoint has locations
249-
# added or removed. Keep track of them so we can look for them
250-
# in tests.
251-
self.breakpoint_events.append(packet)
252-
# no need to add 'breakpoint' event packets to our packets list
253-
return keepGoing
254246
elif event.startswith("progress"):
255247
# Progress events come in as 'progressStart', 'progressUpdate',
256248
# and 'progressEnd' events. Keep these around in case test
@@ -412,6 +404,15 @@ def wait_for_stopped(self, timeout=None):
412404
self.threads = []
413405
return stopped_events
414406

407+
def wait_for_breakpoint_events(self, timeout=None):
408+
breakpoint_events = []
409+
while True:
410+
event = self.wait_for_event("breakpoint", timeout=timeout)
411+
if not event:
412+
break
413+
breakpoint_events.append(event)
414+
return breakpoint_events
415+
415416
def wait_for_exited(self):
416417
event_dict = self.wait_for_event("exited")
417418
if event_dict is None:
@@ -591,6 +592,7 @@ def request_attach(
591592
attachCommands=None,
592593
terminateCommands=None,
593594
coreFile=None,
595+
stopOnAttach=True,
594596
postRunCommands=None,
595597
sourceMap=None,
596598
gdbRemotePort=None,
@@ -620,6 +622,8 @@ def request_attach(
620622
args_dict["attachCommands"] = attachCommands
621623
if coreFile:
622624
args_dict["coreFile"] = coreFile
625+
if stopOnAttach:
626+
args_dict["stopOnEntry"] = stopOnAttach
623627
if postRunCommands:
624628
args_dict["postRunCommands"] = postRunCommands
625629
if sourceMap:
@@ -632,7 +636,7 @@ def request_attach(
632636
response = self.send_recv(command_dict)
633637

634638
if response["success"]:
635-
self.wait_for_events(["process", "initialized"])
639+
self.wait_for_event("process")
636640
return response
637641

638642
def request_breakpointLocations(
@@ -666,10 +670,6 @@ def request_configurationDone(self):
666670
response = self.send_recv(command_dict)
667671
if response:
668672
self.configuration_done_sent = True
669-
# Client requests the baseline of currently existing threads after
670-
# a successful launch or attach.
671-
# Kick off the threads request that follows
672-
self.request_threads()
673673
return response
674674

675675
def _process_stopped(self):
@@ -887,7 +887,7 @@ def request_launch(
887887
response = self.send_recv(command_dict)
888888

889889
if response["success"]:
890-
self.wait_for_events(["process", "initialized"])
890+
self.wait_for_event("process")
891891
return response
892892

893893
def request_next(self, threadId, granularity="statement"):
@@ -1325,6 +1325,26 @@ def attach_options_specified(options):
13251325

13261326
def run_vscode(dbg, args, options):
13271327
dbg.request_initialize(options.sourceInitFile)
1328+
1329+
if options.sourceBreakpoints:
1330+
source_to_lines = {}
1331+
for file_line in options.sourceBreakpoints:
1332+
(path, line) = file_line.split(":")
1333+
if len(path) == 0 or len(line) == 0:
1334+
print('error: invalid source with line "%s"' % (file_line))
1335+
1336+
else:
1337+
if path in source_to_lines:
1338+
source_to_lines[path].append(int(line))
1339+
else:
1340+
source_to_lines[path] = [int(line)]
1341+
for source in source_to_lines:
1342+
dbg.request_setBreakpoints(source, source_to_lines[source])
1343+
if options.funcBreakpoints:
1344+
dbg.request_setFunctionBreakpoints(options.funcBreakpoints)
1345+
1346+
dbg.request_configurationDone()
1347+
13281348
if attach_options_specified(options):
13291349
response = dbg.request_attach(
13301350
program=options.program,
@@ -1353,23 +1373,6 @@ def run_vscode(dbg, args, options):
13531373
)
13541374

13551375
if response["success"]:
1356-
if options.sourceBreakpoints:
1357-
source_to_lines = {}
1358-
for file_line in options.sourceBreakpoints:
1359-
(path, line) = file_line.split(":")
1360-
if len(path) == 0 or len(line) == 0:
1361-
print('error: invalid source with line "%s"' % (file_line))
1362-
1363-
else:
1364-
if path in source_to_lines:
1365-
source_to_lines[path].append(int(line))
1366-
else:
1367-
source_to_lines[path] = [int(line)]
1368-
for source in source_to_lines:
1369-
dbg.request_setBreakpoints(source, source_to_lines[source])
1370-
if options.funcBreakpoints:
1371-
dbg.request_setFunctionBreakpoints(options.funcBreakpoints)
1372-
dbg.request_configurationDone()
13731376
dbg.wait_for_stopped()
13741377
else:
13751378
if "message" in response:

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ def attach(
340340
exitCommands=None,
341341
attachCommands=None,
342342
coreFile=None,
343+
stopOnAttach=True,
343344
disconnectAutomatically=True,
344345
terminateCommands=None,
345346
postRunCommands=None,
@@ -364,6 +365,8 @@ def cleanup():
364365
self.addTearDownHook(cleanup)
365366
# Initialize and launch the program
366367
self.dap_server.request_initialize(sourceInitFile)
368+
self.dap_server.wait_for_event("initialized")
369+
self.dap_server.request_configurationDone()
367370
response = self.dap_server.request_attach(
368371
program=program,
369372
pid=pid,
@@ -376,6 +379,7 @@ def cleanup():
376379
attachCommands=attachCommands,
377380
terminateCommands=terminateCommands,
378381
coreFile=coreFile,
382+
stopOnAttach=stopOnAttach,
379383
postRunCommands=postRunCommands,
380384
sourceMap=sourceMap,
381385
gdbRemotePort=gdbRemotePort,
@@ -434,6 +438,9 @@ def cleanup():
434438

435439
# Initialize and launch the program
436440
self.dap_server.request_initialize(sourceInitFile)
441+
self.dap_server.wait_for_event("initialized")
442+
self.dap_server.request_configurationDone()
443+
437444
response = self.dap_server.request_launch(
438445
program,
439446
args=args,

lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ def spawn_and_wait(program, delay):
2727
@skip
2828
class TestDAP_attach(lldbdap_testcase.DAPTestCaseBase):
2929
def set_and_hit_breakpoint(self, continueToExit=True):
30+
self.dap_server.wait_for_stopped()
31+
3032
source = "main.c"
3133
breakpoint1_line = line_number(source, "// breakpoint 1")
3234
lines = [breakpoint1_line]

lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@
1818
import socket
1919

2020

21-
@skip
2221
class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase):
2322
default_timeout = 20
2423

2524
def set_and_hit_breakpoint(self, continueToExit=True):
25+
self.dap_server.wait_for_stopped()
26+
2627
source = "main.c"
27-
main_source_path = os.path.join(os.getcwd(), source)
28-
breakpoint1_line = line_number(main_source_path, "// breakpoint 1")
28+
breakpoint1_line = line_number(source, "// breakpoint 1")
2929
lines = [breakpoint1_line]
3030
# Set breakpoint in the thread function so we can step the threads
31-
breakpoint_ids = self.set_source_breakpoints(main_source_path, lines)
31+
breakpoint_ids = self.set_source_breakpoints(source, lines)
3232
self.assertEqual(
3333
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
3434
)

lldb/test/API/tools/lldb-dap/breakpoint-events/TestDAP_breakpointEvents.py

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -81,52 +81,27 @@ def test_breakpoint_events(self):
8181
breakpoint["verified"], "expect foo breakpoint to not be verified"
8282
)
8383

84-
# Get the stop at the entry point
85-
self.continue_to_next_stop()
84+
# Make sure we're stopped.
85+
self.dap_server.wait_for_stopped()
8686

87-
# We are now stopped at the entry point to the program. Shared
88-
# libraries are not loaded yet (at least on macOS they aren't) and only
89-
# the breakpoint in the main executable should be resolved.
90-
self.assertEqual(len(self.dap_server.breakpoint_events), 1)
91-
event = self.dap_server.breakpoint_events[0]
92-
body = event["body"]
93-
self.assertEqual(
94-
body["reason"], "changed", "breakpoint event should say changed"
95-
)
96-
breakpoint = body["breakpoint"]
97-
self.assertEqual(breakpoint["id"], main_bp_id)
98-
self.assertTrue(breakpoint["verified"], "main breakpoint should be resolved")
99-
100-
# Clear the list of breakpoint events so we don't see this one again.
101-
self.dap_server.breakpoint_events.clear()
87+
# Flush the breakpoint events.
88+
self.dap_server.wait_for_breakpoint_events(timeout=5)
10289

10390
# Continue to the breakpoint
10491
self.continue_to_breakpoints(dap_breakpoint_ids)
10592

106-
# When the process launches, we first expect to see both the main and
107-
# foo breakpoint as unresolved.
108-
for event in self.dap_server.breakpoint_events[:2]:
109-
body = event["body"]
110-
self.assertEqual(
111-
body["reason"], "changed", "breakpoint event should say changed"
112-
)
113-
breakpoint = body["breakpoint"]
114-
self.assertIn(str(breakpoint["id"]), dap_breakpoint_ids)
115-
self.assertFalse(breakpoint["verified"], "breakpoint should be unresolved")
93+
verified_breakpoint_ids = []
94+
unverified_breakpoint_ids = []
95+
for breakpoint_event in self.dap_server.wait_for_breakpoint_events(timeout=5):
96+
breakpoint = breakpoint_event["body"]["breakpoint"]
97+
id = breakpoint["id"]
98+
if breakpoint["verified"]:
99+
verified_breakpoint_ids.append(id)
100+
else:
101+
unverified_breakpoint_ids.append(id)
116102

117-
# Then, once the dynamic loader has given us a load address, they
118-
# should show up as resolved again.
119-
for event in self.dap_server.breakpoint_events[3:]:
120-
body = event["body"]
121-
self.assertEqual(
122-
body["reason"], "changed", "breakpoint event should say changed"
123-
)
124-
breakpoint = body["breakpoint"]
125-
self.assertIn(str(breakpoint["id"]), dap_breakpoint_ids)
126-
self.assertTrue(breakpoint["verified"], "breakpoint should be resolved")
127-
self.assertNotIn(
128-
"source",
129-
breakpoint,
130-
"breakpoint event should not return a source object",
131-
)
132-
self.assertIn("line", breakpoint, "breakpoint event should have line")
103+
self.assertIn(main_bp_id, unverified_breakpoint_ids)
104+
self.assertIn(foo_bp_id, unverified_breakpoint_ids)
105+
106+
self.assertIn(main_bp_id, verified_breakpoint_ids)
107+
self.assertIn(foo_bp_id, verified_breakpoint_ids)

lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ def verify_completions(self, actual_list, expected_list, not_expected_list=[]):
4444
self.assertNotIn(not_expected_item, actual_list)
4545

4646

47-
def setup_debugee(self):
47+
def setup_debugee(self, stopOnEntry=False):
4848
program = self.getBuildArtifact("a.out")
49-
self.build_and_launch(program)
49+
self.build_and_launch(program, stopOnEntry=stopOnEntry)
5050

5151
source = "main.cpp"
5252
breakpoint1_line = line_number(source, "// breakpoint 1")
@@ -235,7 +235,7 @@ def test_auto_completions(self):
235235
"""
236236
Tests completion requests in "repl-mode=auto"
237237
"""
238-
self.setup_debugee()
238+
self.setup_debugee(stopOnEntry=True)
239239

240240
res = self.dap_server.request_evaluate(
241241
"`lldb-dap repl-mode auto", context="repl"

lldb/test/API/tools/lldb-dap/console/TestDAP_console.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def test_exit_status_message_ok(self):
167167

168168
def test_diagnositcs(self):
169169
program = self.getBuildArtifact("a.out")
170-
self.build_and_launch(program)
170+
self.build_and_launch(program, stopOnEntry=True)
171171

172172
core = self.getBuildArtifact("minidump.core")
173173
self.yaml2obj("minidump.yaml", core)

lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,19 @@ def test_launch(self):
3131
created.
3232
"""
3333
program = self.getBuildArtifact("a.out")
34-
self.build_and_launch(program, disconnectAutomatically=False)
34+
self.build_and_launch(program, stopOnEntry=True, disconnectAutomatically=False)
3535

3636
# We set a breakpoint right before the side effect file is created
3737
self.set_source_breakpoints(
3838
self.source, [line_number(self.source, "// breakpoint")]
3939
)
4040
self.continue_to_next_stop()
4141

42+
# verify we haven't produced the side effect file yet
43+
self.assertFalse(os.path.exists(program + ".side_effect"))
44+
4245
self.dap_server.request_disconnect()
46+
4347
# verify we didn't produce the side effect file
4448
time.sleep(1)
4549
self.assertFalse(os.path.exists(program + ".side_effect"))

lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from lldbsuite.test.decorators import *
1111
from lldbsuite.test.lldbtest import *
1212

13+
1314
# DAP tests are flakey, see https://github.com/llvm/llvm-project/issues/137660.
1415
@skip
1516
class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase):
@@ -42,7 +43,9 @@ def run_test_evaluate_expressions(
4243
self.context = context
4344
program = self.getBuildArtifact("a.out")
4445
self.build_and_launch(
45-
program, enableAutoVariableSummaries=enableAutoVariableSummaries
46+
program,
47+
enableAutoVariableSummaries=enableAutoVariableSummaries,
48+
stopOnEntry=True,
4649
)
4750
source = "main.cpp"
4851
self.set_source_breakpoints(

lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ def test_stopOnEntry(self):
8888
"""
8989
program = self.getBuildArtifact("a.out")
9090
self.build_and_launch(program, stopOnEntry=True)
91-
self.set_function_breakpoints(["main"])
92-
stopped_events = self.continue_to_next_stop()
91+
92+
stopped_events = self.dap_server.wait_for_stopped()
9393
for stopped_event in stopped_events:
9494
if "body" in stopped_event:
9595
body = stopped_event["body"]

lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def verify_progress_events(
5050
@skipIfWindows
5151
def test(self):
5252
program = self.getBuildArtifact("a.out")
53-
self.build_and_launch(program)
53+
self.build_and_launch(program, stopOnEntry=True)
5454
progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py")
5555
self.dap_server.request_evaluate(
5656
f"`command script import {progress_emitter}", context="repl"

lldb/test/API/tools/lldb-dap/repl-mode/TestDAP_repl_mode_detection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def assertEvaluate(self, expression, regex):
2020

2121
def test_completions(self):
2222
program = self.getBuildArtifact("a.out")
23-
self.build_and_launch(program)
23+
self.build_and_launch(program, stopOnEntry=True)
2424

2525
source = "main.cpp"
2626
breakpoint1_line = line_number(source, "// breakpoint 1")

lldb/test/API/tools/lldb-dap/restart/TestDAP_restart.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ def test_basic_functionality(self):
2222
[bp_A, bp_B] = self.set_source_breakpoints("main.c", [line_A, line_B])
2323

2424
# Verify we hit A, then B.
25-
self.dap_server.request_configurationDone()
2625
self.verify_breakpoint_hit([bp_A])
2726
self.dap_server.request_continue()
2827
self.verify_breakpoint_hit([bp_B])

lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_runInTerminal.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ def test_stopOnEntry(self):
7474
program = self.getBuildArtifact("a.out")
7575
self.build_and_launch(program, runInTerminal=True, stopOnEntry=True)
7676
[bp_main] = self.set_function_breakpoints(["main"])
77-
self.dap_server.request_configurationDone()
7877

7978
# When using stopOnEntry, configurationDone doesn't result in a running
8079
# process, we should immediately get a stopped event instead.

lldb/test/API/tools/lldb-dap/stop-hooks/TestDAP_stop_hooks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def test_stop_hooks_before_run(self):
1919
self.build_and_launch(program, stopOnEntry=True, preRunCommands=preRunCommands)
2020

2121
# The first stop is on entry.
22-
self.continue_to_next_stop()
22+
self.dap_server.wait_for_stopped()
2323

2424
breakpoint_ids = self.set_function_breakpoints(["main"])
2525
# This request hangs if the race happens, because, in that case, the

0 commit comments

Comments
 (0)