From 0335696ffdcdb824df00cb18d406720fc3a639a2 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Tue, 15 Apr 2025 14:05:58 -0700 Subject: [PATCH 1/8] [lldb] Make TestSwiftActorUnprioritisedJobs deterministic --- .../TestSwiftActorUnprioritisedJobs.py | 9 ++------- .../swift/async/actors/unprioritised_jobs/main.swift | 4 +--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py index 92f1f726bb111..5294ff237c9f5 100644 --- a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py +++ b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py @@ -7,10 +7,6 @@ class TestCase(TestBase): @swiftTest - @skipUnlessFoundation - @skipIfWindows # temporarily skip test until fails can be investigated - @skipIfLinux # temporarily skip test until fails can be investigated - @skipIfDarwin # temporarily skip test until fails can be investigated def test_actor_unprioritised_jobs(self): """Verify that an actor exposes its unprioritised jobs (queue).""" self.build() @@ -19,12 +15,11 @@ def test_actor_unprioritised_jobs(self): ) frame = thread.GetSelectedFrame() defaultActor = frame.var("a.$defaultActor") - self.assertEqual(defaultActor.summary, "running") unprioritised_jobs = defaultActor.GetChildMemberWithName("unprioritised_jobs") # There are 4 child tasks (async let), the first one occupies the actor # with a sleep, the next 3 go on to the queue. - # TODO: rdar://148377173 - # self.assertEqual(unprioritised_jobs.num_children, 3) + self.assertEqual(unprioritised_jobs.num_children, 3) + self.assertEqual(defaultActor.summary, "running") for job in unprioritised_jobs: self.assertRegex(job.name, r"^\d+") self.assertRegex(job.summary, r"^id:\d+ flags:\S+") diff --git a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift index 0236fbb41628a..1a6ec0c355ba8 100644 --- a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift +++ b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift @@ -1,10 +1,8 @@ -import Foundation - actor Actor { var data: Int = 15 func occupy() async { - Thread.sleep(forTimeInterval: 100) + _ = readLine() } func work() async -> Int { From 88ecc1464ccae89f5816ee189e3fc378d7083cc7 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Tue, 15 Apr 2025 15:58:07 -0700 Subject: [PATCH 2/8] Run and show `bt all` --- .../unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py index 5294ff237c9f5..58dbe8d18fd9e 100644 --- a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py +++ b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py @@ -18,6 +18,8 @@ def test_actor_unprioritised_jobs(self): unprioritised_jobs = defaultActor.GetChildMemberWithName("unprioritised_jobs") # There are 4 child tasks (async let), the first one occupies the actor # with a sleep, the next 3 go on to the queue. + if unprioritised_jobs.num_children != 3: + self.expect("bt all", substrs=["abcdefghijklmnopqrstuvwxyz"]) self.assertEqual(unprioritised_jobs.num_children, 3) self.assertEqual(defaultActor.summary, "running") for job in unprioritised_jobs: From 1bb5bc77415cf559db703c32b86eb02ca5c927d4 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Wed, 16 Apr 2025 09:57:27 -0700 Subject: [PATCH 3/8] Attempt forward progress of other threads --- .../TestSwiftActorUnprioritisedJobs.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py index 58dbe8d18fd9e..dd96dad6b0f9e 100644 --- a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py +++ b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py @@ -1,16 +1,27 @@ +import time +from contextlib import contextmanager import lldb from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil +@contextmanager +def _managed_async(dbg): + async_state = dbg.GetAsync() + try: + yield + finally: + dbg.SetAsync(async_state) + + class TestCase(TestBase): @swiftTest def test_actor_unprioritised_jobs(self): """Verify that an actor exposes its unprioritised jobs (queue).""" self.build() - _, _, thread, _ = lldbutil.run_to_source_breakpoint( + _, process, thread, _ = lldbutil.run_to_source_breakpoint( self, "break here", lldb.SBFileSpec("main.swift") ) frame = thread.GetSelectedFrame() @@ -19,6 +30,20 @@ def test_actor_unprioritised_jobs(self): # There are 4 child tasks (async let), the first one occupies the actor # with a sleep, the next 3 go on to the queue. if unprioritised_jobs.num_children != 3: + with _managed_async(self.dbg): + # Continue - other threads only. + self.dbg.SetAsync(True) + process.Continue() + # Wait - allow the other threads to work. + time.sleep(2) + # Stop the threads. + # Notes: After a single interrupt, lldb reports the process as + # running, but two interrupt calls results in a stopped process. + # Also, using `process.Stop()` instead of `"process interrupt"` + # did not work. + self.dbg.SetAsync(False) + self.dbg.HandleCommand("process interrupt") + self.dbg.HandleCommand("process interrupt") self.expect("bt all", substrs=["abcdefghijklmnopqrstuvwxyz"]) self.assertEqual(unprioritised_jobs.num_children, 3) self.assertEqual(defaultActor.summary, "running") From e8537a7ef0bc3d8527a13e0960ec0ad4ac0bc577 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Wed, 16 Apr 2025 10:16:49 -0700 Subject: [PATCH 4/8] Add missing thread.Suspend() --- .../unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py index dd96dad6b0f9e..66aa762a0f4e8 100644 --- a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py +++ b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py @@ -31,6 +31,8 @@ def test_actor_unprioritised_jobs(self): # with a sleep, the next 3 go on to the queue. if unprioritised_jobs.num_children != 3: with _managed_async(self.dbg): + # Suspend the current thread. + thread.Suspend() # Continue - other threads only. self.dbg.SetAsync(True) process.Continue() From f7f225f77243034653c5533b2fc418e72d12370d Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Wed, 16 Apr 2025 11:19:46 -0700 Subject: [PATCH 5/8] Unconditionally run other threads --- .../TestSwiftActorUnprioritisedJobs.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py index 66aa762a0f4e8..9755142bce72e 100644 --- a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py +++ b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py @@ -25,28 +25,28 @@ def test_actor_unprioritised_jobs(self): self, "break here", lldb.SBFileSpec("main.swift") ) frame = thread.GetSelectedFrame() + + with _managed_async(self.dbg): + # Suspend the current thread. + thread.Suspend() + # Continue - other threads only. + self.dbg.SetAsync(True) + process.Continue() + # Wait - allowing other threads to run. + time.sleep(2) + # Stop the threads. + self.dbg.SetAsync(False) + self.dbg.HandleCommand("process interrupt") + # Note: After a single interrupt, lldb reports the process as + # running, but two interrupt calls results in a stopped process. + # Also, using `process.Stop()` instead of `"process interrupt"` + # did not work. + self.dbg.HandleCommand("process interrupt") + defaultActor = frame.var("a.$defaultActor") unprioritised_jobs = defaultActor.GetChildMemberWithName("unprioritised_jobs") # There are 4 child tasks (async let), the first one occupies the actor # with a sleep, the next 3 go on to the queue. - if unprioritised_jobs.num_children != 3: - with _managed_async(self.dbg): - # Suspend the current thread. - thread.Suspend() - # Continue - other threads only. - self.dbg.SetAsync(True) - process.Continue() - # Wait - allow the other threads to work. - time.sleep(2) - # Stop the threads. - # Notes: After a single interrupt, lldb reports the process as - # running, but two interrupt calls results in a stopped process. - # Also, using `process.Stop()` instead of `"process interrupt"` - # did not work. - self.dbg.SetAsync(False) - self.dbg.HandleCommand("process interrupt") - self.dbg.HandleCommand("process interrupt") - self.expect("bt all", substrs=["abcdefghijklmnopqrstuvwxyz"]) self.assertEqual(unprioritised_jobs.num_children, 3) self.assertEqual(defaultActor.summary, "running") for job in unprioritised_jobs: From 56e53072b8b76772bef33194cc2f380bea7267c3 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Thu, 17 Apr 2025 14:21:46 -0700 Subject: [PATCH 6/8] Increase sleep wait --- .../unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py index 9755142bce72e..22eee9efac483 100644 --- a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py +++ b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py @@ -33,7 +33,7 @@ def test_actor_unprioritised_jobs(self): self.dbg.SetAsync(True) process.Continue() # Wait - allowing other threads to run. - time.sleep(2) + time.sleep(5) # Stop the threads. self.dbg.SetAsync(False) self.dbg.HandleCommand("process interrupt") From f9b00d3b971d0b41f38a5240270534db47f0db7e Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Tue, 22 Apr 2025 11:00:23 -0700 Subject: [PATCH 7/8] Add actor call to hopefully initialize swift concurrency threads --- .../API/lang/swift/async/actors/unprioritised_jobs/main.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift index 1a6ec0c355ba8..dcf9cf588acce 100644 --- a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift +++ b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift @@ -15,6 +15,10 @@ actor Actor { @main struct Entry { static func main() async { let a = Actor() + + // Execute through Swift Concurrency threads + await a.work() + async let _ = a.occupy() async let _ = a.work() async let _ = a.work() From 4e5a1a39e0704f457bee890c3e72a0c5fb64c793 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Tue, 22 Apr 2025 12:59:46 -0700 Subject: [PATCH 8/8] New algorithm to ensure actor is sufficiently setup Credit Adrian for the idea. --- .../TestSwiftActorUnprioritisedJobs.py | 48 +++++++++++-------- .../actors/unprioritised_jobs/main.swift | 4 +- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py index 22eee9efac483..7a480dae1182d 100644 --- a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py +++ b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/TestSwiftActorUnprioritisedJobs.py @@ -22,33 +22,41 @@ def test_actor_unprioritised_jobs(self): """Verify that an actor exposes its unprioritised jobs (queue).""" self.build() _, process, thread, _ = lldbutil.run_to_source_breakpoint( - self, "break here", lldb.SBFileSpec("main.swift") + self, + "break here", + lldb.SBFileSpec("main.swift"), + only_one_thread=False, ) - frame = thread.GetSelectedFrame() - - with _managed_async(self.dbg): - # Suspend the current thread. - thread.Suspend() - # Continue - other threads only. - self.dbg.SetAsync(True) - process.Continue() - # Wait - allowing other threads to run. - time.sleep(5) - # Stop the threads. - self.dbg.SetAsync(False) - self.dbg.HandleCommand("process interrupt") - # Note: After a single interrupt, lldb reports the process as - # running, but two interrupt calls results in a stopped process. - # Also, using `process.Stop()` instead of `"process interrupt"` - # did not work. - self.dbg.HandleCommand("process interrupt") + + stopped_threads = [ + t for t in process.threads if t.stop_reason == lldb.eStopReasonBreakpoint + ] + + # If only one breakpoint has hit, run the other threads to reach the + # state where both breakpoints have hit. + if len(stopped_threads) == 1: + with _managed_async(self.dbg): + # Suspend the current thread. + thread.Suspend() + # Run the other threads until the second breakpoint hits. + self.dbg.SetAsync(False) + process.Continue() + + # Get the frame for the the breakpoint hit in `main()`. + frame = lldb.SBFrame() + for t in process.threads: + if t.stop_reason == lldb.eStopReasonBreakpoint: + if "Entry.main()" in t.frame[0].name: + frame = t.frame[0] + break defaultActor = frame.var("a.$defaultActor") unprioritised_jobs = defaultActor.GetChildMemberWithName("unprioritised_jobs") + # There are 4 child tasks (async let), the first one occupies the actor # with a sleep, the next 3 go on to the queue. self.assertEqual(unprioritised_jobs.num_children, 3) self.assertEqual(defaultActor.summary, "running") for job in unprioritised_jobs: self.assertRegex(job.name, r"^\d+") - self.assertRegex(job.summary, r"^id:\d+ flags:\S+") + self.assertRegex(job.summary, r"^id:[1-9]\d* flags:\S+") diff --git a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift index dcf9cf588acce..4b01cbd36d446 100644 --- a/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift +++ b/lldb/test/API/lang/swift/async/actors/unprioritised_jobs/main.swift @@ -2,7 +2,7 @@ actor Actor { var data: Int = 15 func occupy() async { - _ = readLine() + print("break here") } func work() async -> Int { @@ -16,7 +16,7 @@ actor Actor { static func main() async { let a = Actor() - // Execute through Swift Concurrency threads + // Cause execution to pass through Swift Concurrency's threads. await a.work() async let _ = a.occupy()