From 4f11f4117c7d2de86bc5c724bea3998d0b6ebabb Mon Sep 17 00:00:00 2001 From: Udit Kumar Agarwal Date: Tue, 3 Nov 2026 11:40:01 -0500 Subject: [PATCH 1/2] [SYCL] Re-enqueue waiting host accessor thread with a Blocking status --- sycl/source/detail/scheduler/scheduler.cpp | 6 ++- sycl/unittests/scheduler/BlockedCommands.cpp | 38 +++++++++++++++++++ .../scheduler/SchedulerTestUtils.hpp | 1 + 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/sycl/source/detail/scheduler/scheduler.cpp b/sycl/source/detail/scheduler/scheduler.cpp index 4f179919d1a7b..91a75a0e3f5d7 100644 --- a/sycl/source/detail/scheduler/scheduler.cpp +++ b/sycl/source/detail/scheduler/scheduler.cpp @@ -431,8 +431,10 @@ void Scheduler::enqueueUnblockedCommands(events_range ToEnqueue, if (!Cmd) continue; EnqueueResultT Res; - bool Enqueued = - GraphProcessor::enqueueCommand(Cmd, GraphReadLock, Res, ToCleanUp, Cmd); + // BLOCKING re-registers Cmd on any command that still blocks it, so that + // its waiter is not lost. + bool Enqueued = GraphProcessor::enqueueCommand(Cmd, GraphReadLock, Res, + ToCleanUp, Cmd, BLOCKING); if (!Enqueued && EnqueueResultT::SyclEnqueueFailed == Res.MResult) throw exception(make_error_code(errc::runtime), "Enqueue process failed."); diff --git a/sycl/unittests/scheduler/BlockedCommands.cpp b/sycl/unittests/scheduler/BlockedCommands.cpp index c7b86187104da..fbfb367112cda 100644 --- a/sycl/unittests/scheduler/BlockedCommands.cpp +++ b/sycl/unittests/scheduler/BlockedCommands.cpp @@ -204,3 +204,41 @@ TEST_F(SchedulerTest, EnqueueHostDependency) { ASSERT_EQ(detail::EnqueueResultT::SyclEnqueueSuccess, Res.MResult) << "Enqueue operation should return successfully.\n"; } + +// When a blocked user is unblocked by A but is still blocked by B, it must be +// re-registered on B, or its waiter is never woken up. +TEST_F(SchedulerTest, BlockedUserReRegisteredOnNextBlockedCommand) { + sycl::unittest::UrMock<> Mock; + sycl::queue Q{sycl::platform().get_devices()[0], MAsyncHandler}; + sycl::detail::queue_impl &QueueImpl = *detail::getSyclObjImpl(Q); + + // Root + // / \ + // A B (both blocked, e.g. by live read host accessors) + MockCommand A(&QueueImpl), B(&QueueImpl), Root(&QueueImpl); + std::vector ToCleanUp; + for (MockCommand *Dep : {&A, &B}) { + Dep->MEnqueueStatus = detail::EnqueueResultT::SyclEnqueueBlocked; + Dep->MIsBlockable = true; + auto DepEvent = detail::event_impl::create_device_event(QueueImpl); + DepEvent->setCommand(Dep); + (void)Root.addDep(DepEvent, ToCleanUp); + } + + MockScheduler MS; + auto Lock = MS.acquireGraphReadLock(); + detail::EnqueueResultT Res; + ASSERT_FALSE(MockScheduler::enqueueCommand(&Root, Res, detail::BLOCKING)); + ASSERT_EQ(1u, A.MBlockedUsers.size()); + ASSERT_EQ(0u, B.MBlockedUsers.size()); + + A.MEnqueueStatus = detail::EnqueueResultT::SyclEnqueueReady; + MockScheduler::enqueueUnblockedCommands(A.MBlockedUsers, Lock, ToCleanUp); + ASSERT_FALSE(Root.isSuccessfullyEnqueued()); + ASSERT_EQ(1u, B.MBlockedUsers.size()); + ASSERT_EQ(Root.getEvent(), B.MBlockedUsers[0]); + + B.MEnqueueStatus = detail::EnqueueResultT::SyclEnqueueReady; + MockScheduler::enqueueUnblockedCommands(B.MBlockedUsers, Lock, ToCleanUp); + ASSERT_TRUE(Root.isSuccessfullyEnqueued()); +} diff --git a/sycl/unittests/scheduler/SchedulerTestUtils.hpp b/sycl/unittests/scheduler/SchedulerTestUtils.hpp index 7c891aa28c6e0..2116c25c520cf 100644 --- a/sycl/unittests/scheduler/SchedulerTestUtils.hpp +++ b/sycl/unittests/scheduler/SchedulerTestUtils.hpp @@ -103,6 +103,7 @@ class MockScheduler : public sycl::detail::Scheduler { using sycl::detail::Scheduler::addCopyBack; using sycl::detail::Scheduler::checkLeavesCompletion; using sycl::detail::Scheduler::cleanupCommands; + using sycl::detail::Scheduler::enqueueUnblockedCommands; using sycl::detail::Scheduler::MDeferredMemObjRelease; sycl::detail::MemObjRecord * From 8dff231855dffbed03b68d0ab5d994f162e854b8 Mon Sep 17 00:00:00 2001 From: Udit Kumar Agarwal Date: Wed, 4 Nov 2026 13:00:59 -0500 Subject: [PATCH 2/2] [NFC] Rename enqueueBlockedCommands --- sycl/source/detail/scheduler/commands.hpp | 10 ++++++---- sycl/source/detail/scheduler/scheduler.cpp | 6 +++--- sycl/source/detail/scheduler/scheduler.hpp | 2 +- sycl/unittests/scheduler/BlockedCommands.cpp | 4 ++-- sycl/unittests/scheduler/FailedCommands.cpp | 2 +- sycl/unittests/scheduler/SchedulerTestUtils.hpp | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/sycl/source/detail/scheduler/commands.hpp b/sycl/source/detail/scheduler/commands.hpp index c45d2a789bb80..695ffefe9b26f 100644 --- a/sycl/source/detail/scheduler/commands.hpp +++ b/sycl/source/detail/scheduler/commands.hpp @@ -388,10 +388,12 @@ class Command { /// removal). bool MMarkedForCleanup = false; - /// Contains list of commands that depends on the host command explicitly (by - /// depends_on). Not involved in the cleanup process since it is one-way link - /// and does not hold resources. - /// Using EventImplPtr since enqueueUnblockedCommands and event.wait may + /// Events of root commands whose enqueue was deferred because this command + /// is blocking: a host task that is not complete yet, or a command blocked + /// by a live host accessor. They are re-enqueued by retryEnqueueBlockedUsers + /// when this command stops blocking. Not involved in the cleanup process + /// since it is one-way link and does not hold resources. + /// Using EventImplPtr since retryEnqueueBlockedUsers and event.wait may /// intersect with command enqueue. std::vector MBlockedUsers; std::mutex MBlockedUsersMutex; diff --git a/sycl/source/detail/scheduler/scheduler.cpp b/sycl/source/detail/scheduler/scheduler.cpp index 91a75a0e3f5d7..1993113e91d95 100644 --- a/sycl/source/detail/scheduler/scheduler.cpp +++ b/sycl/source/detail/scheduler/scheduler.cpp @@ -397,7 +397,7 @@ void Scheduler::releaseHostAccessor(Requirement *Req) { BlockedCmd->MEnqueueStatus = EnqueueResultT::SyclEnqueueReady; } // Commands whose blocking waiters deferred on BlockedCmd. - enqueueUnblockedCommands(BlockedCmd->MBlockedUsers, Lock, ToCleanUp); + retryEnqueueBlockedUsers(BlockedCmd->MBlockedUsers, Lock, ToCleanUp); enqueueLeavesOfReqUnlocked(Req, Lock, ToCleanUp); } @@ -423,7 +423,7 @@ void Scheduler::enqueueLeavesOfReqUnlocked(const Requirement *const Req, EnqueueLeaves(Record->MWriteLeaves); } -void Scheduler::enqueueUnblockedCommands(events_range ToEnqueue, +void Scheduler::retryEnqueueBlockedUsers(events_range ToEnqueue, ReadLockT &GraphReadLock, std::vector &ToCleanUp) { for (event_impl &Event : ToEnqueue) { @@ -540,7 +540,7 @@ void Scheduler::NotifyHostTaskCompletion(Command *Cmd) { // update self-event status CmdEvent->setComplete(); } - Scheduler::enqueueUnblockedCommands(Cmd->MBlockedUsers, Lock, ToCleanUp); + Scheduler::retryEnqueueBlockedUsers(Cmd->MBlockedUsers, Lock, ToCleanUp); } QueueImpl->revisitUnenqueuedCommandsState(CmdEvent); diff --git a/sycl/source/detail/scheduler/scheduler.hpp b/sycl/source/detail/scheduler/scheduler.hpp index 06a063b4e3cc8..f01f1499b6964 100644 --- a/sycl/source/detail/scheduler/scheduler.hpp +++ b/sycl/source/detail/scheduler/scheduler.hpp @@ -549,7 +549,7 @@ class Scheduler { ReadLockT &GraphReadLock, std::vector &ToCleanUp); - static void enqueueUnblockedCommands(events_range ToEnqueue, + static void retryEnqueueBlockedUsers(events_range ToEnqueue, ReadLockT &GraphReadLock, std::vector &ToCleanUp); diff --git a/sycl/unittests/scheduler/BlockedCommands.cpp b/sycl/unittests/scheduler/BlockedCommands.cpp index fbfb367112cda..b0719e1932303 100644 --- a/sycl/unittests/scheduler/BlockedCommands.cpp +++ b/sycl/unittests/scheduler/BlockedCommands.cpp @@ -233,12 +233,12 @@ TEST_F(SchedulerTest, BlockedUserReRegisteredOnNextBlockedCommand) { ASSERT_EQ(0u, B.MBlockedUsers.size()); A.MEnqueueStatus = detail::EnqueueResultT::SyclEnqueueReady; - MockScheduler::enqueueUnblockedCommands(A.MBlockedUsers, Lock, ToCleanUp); + MockScheduler::retryEnqueueBlockedUsers(A.MBlockedUsers, Lock, ToCleanUp); ASSERT_FALSE(Root.isSuccessfullyEnqueued()); ASSERT_EQ(1u, B.MBlockedUsers.size()); ASSERT_EQ(Root.getEvent(), B.MBlockedUsers[0]); B.MEnqueueStatus = detail::EnqueueResultT::SyclEnqueueReady; - MockScheduler::enqueueUnblockedCommands(B.MBlockedUsers, Lock, ToCleanUp); + MockScheduler::retryEnqueueBlockedUsers(B.MBlockedUsers, Lock, ToCleanUp); ASSERT_TRUE(Root.isSuccessfullyEnqueued()); } diff --git a/sycl/unittests/scheduler/FailedCommands.cpp b/sycl/unittests/scheduler/FailedCommands.cpp index 7bcefa5ba4fa3..e7dadd157372f 100644 --- a/sycl/unittests/scheduler/FailedCommands.cpp +++ b/sycl/unittests/scheduler/FailedCommands.cpp @@ -169,7 +169,7 @@ TEST(FailedCommandsTest, CheckUREventReleaseWithBarrier) { // Regression for the deferred-enqueue-failure hang identified in PR #22993 // review. When Command::enqueue's re-attempt on a ThreadPool worker (via -// NotifyHostTaskCompletion -> enqueueUnblockedCommands) fails, a caller +// NotifyHostTaskCompletion -> retryEnqueueBlockedUsers) fails, a caller // that deferred via MBlockedUsers and parked in event_impl::waitInternal's // cv.wait must still wake. The failure itself surfaces via // reportAsyncException on the queue's async handler. diff --git a/sycl/unittests/scheduler/SchedulerTestUtils.hpp b/sycl/unittests/scheduler/SchedulerTestUtils.hpp index 2116c25c520cf..da9818193d35b 100644 --- a/sycl/unittests/scheduler/SchedulerTestUtils.hpp +++ b/sycl/unittests/scheduler/SchedulerTestUtils.hpp @@ -103,8 +103,8 @@ class MockScheduler : public sycl::detail::Scheduler { using sycl::detail::Scheduler::addCopyBack; using sycl::detail::Scheduler::checkLeavesCompletion; using sycl::detail::Scheduler::cleanupCommands; - using sycl::detail::Scheduler::enqueueUnblockedCommands; using sycl::detail::Scheduler::MDeferredMemObjRelease; + using sycl::detail::Scheduler::retryEnqueueBlockedUsers; sycl::detail::MemObjRecord * getOrInsertMemObjRecord(sycl::detail::queue_impl *Queue,