Skip to content

Commit 9d6c721

Browse files
authored
[core] Correctly fail worker lease request if a task becomes infeasible after scheduling (#52295)
Signed-off-by: dayshah <dhyey2019@gmail.com>
1 parent ca48781 commit 9d6c721

3 files changed

Lines changed: 117 additions & 102 deletions

File tree

src/ray/raylet/local_task_manager.cc

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,11 @@ void LocalTaskManager::QueueAndScheduleTask(std::shared_ptr<internal::Work> work
8080
ScheduleAndDispatchTasks();
8181
}
8282

83-
bool LocalTaskManager::WaitForTaskArgsRequests(std::shared_ptr<internal::Work> work) {
83+
void LocalTaskManager::WaitForTaskArgsRequests(std::shared_ptr<internal::Work> work) {
8484
const auto &task = work->task;
8585
const auto &task_id = task.GetTaskSpecification().TaskId();
8686
const auto &scheduling_key = task.GetTaskSpecification().GetSchedulingClass();
8787
auto object_ids = task.GetTaskSpecification().GetDependencies();
88-
bool can_dispatch = true;
8988
if (!object_ids.empty()) {
9089
bool args_ready = task_dependency_manager_.RequestTaskDependencies(
9190
task_id,
@@ -97,7 +96,6 @@ bool LocalTaskManager::WaitForTaskArgsRequests(std::shared_ptr<internal::Work> w
9796
} else {
9897
RAY_LOG(DEBUG) << "Waiting for args for task: "
9998
<< task.GetTaskSpecification().TaskId();
100-
can_dispatch = false;
10199
auto it = waiting_task_queue_.insert(waiting_task_queue_.end(), std::move(work));
102100
RAY_CHECK(waiting_tasks_index_.emplace(task_id, it).second);
103101
}
@@ -106,7 +104,6 @@ bool LocalTaskManager::WaitForTaskArgsRequests(std::shared_ptr<internal::Work> w
106104
<< task.GetTaskSpecification().TaskId();
107105
tasks_to_dispatch_[scheduling_key].emplace_back(std::move(work));
108106
}
109-
return can_dispatch;
110107
}
111108

112109
void LocalTaskManager::ScheduleAndDispatchTasks() {
@@ -403,8 +400,18 @@ void LocalTaskManager::DispatchScheduledTasksToWorkers() {
403400
info_by_sched_cls_.erase(scheduling_class);
404401
}
405402
if (is_infeasible) {
406-
// TODO(scv119): fail the request.
407-
// Call CancelTask
403+
const auto &front_task = dispatch_queue.front()->task.GetTaskSpecification();
404+
RAY_LOG(ERROR) << "A task got scheduled to a node even though it was infeasible. "
405+
"Please report an issue on GitHub.\nTask: "
406+
<< front_task.DebugString();
407+
auto dispatch_queue_iter = dispatch_queue.begin();
408+
while (dispatch_queue_iter != dispatch_queue.end()) {
409+
CancelTaskToDispatch(
410+
*dispatch_queue_iter,
411+
rpc::RequestWorkerLeaseReply::SCHEDULING_CANCELLED_UNSCHEDULABLE,
412+
"Scheduling failed due to the task becoming infeasible.");
413+
dispatch_queue_iter = dispatch_queue.erase(dispatch_queue_iter);
414+
}
408415
tasks_to_dispatch_.erase(shapes_it++);
409416
} else if (dispatch_queue.empty()) {
410417
tasks_to_dispatch_.erase(shapes_it++);
@@ -604,8 +611,10 @@ bool LocalTaskManager::PoppedWorkerHandler(
604611
// directly and raise a `RuntimeEnvSetupError` exception to user
605612
// eventually. The task will be removed from dispatch queue in
606613
// `CancelTask`.
607-
CancelTask(
608-
task_id,
614+
CancelTasks(
615+
[task_id](const auto &work) {
616+
return task_id == work->task.GetTaskSpecification().TaskId();
617+
},
609618
rpc::RequestWorkerLeaseReply::SCHEDULING_CANCELLED_RUNTIME_ENV_SETUP_FAILED,
610619
/*scheduling_failure_message*/ runtime_env_setup_error_message);
611620
} else if (status == PopWorkerStatus::JobFinished) {
@@ -846,28 +855,12 @@ bool LocalTaskManager::CancelTasks(
846855

847856
ray::erase_if<SchedulingClass, std::shared_ptr<internal::Work>>(
848857
tasks_to_dispatch_, [&](const std::shared_ptr<internal::Work> &work) {
849-
if (predicate(work)) {
850-
const TaskID task_id = work->task.GetTaskSpecification().TaskId();
851-
RAY_LOG(DEBUG) << "Canceling task " << task_id << " from dispatch queue.";
852-
ReplyCancelled(work, failure_type, scheduling_failure_message);
853-
if (work->GetState() == internal::WorkStatus::WAITING_FOR_WORKER) {
854-
// We've already acquired resources so we need to release them.
855-
cluster_resource_scheduler_.GetLocalResourceManager().ReleaseWorkerResources(
856-
work->allocated_instances);
857-
// Release pinned task args.
858-
ReleaseTaskArgs(task_id);
859-
}
860-
if (!work->task.GetTaskSpecification().GetDependencies().empty()) {
861-
task_dependency_manager_.RemoveTaskDependencies(
862-
work->task.GetTaskSpecification().TaskId());
863-
}
864-
RemoveFromRunningTasksIfExists(work->task);
865-
work->SetStateCancelled();
866-
tasks_cancelled = true;
867-
return true;
868-
} else {
858+
if (!predicate(work)) {
869859
return false;
870860
}
861+
CancelTaskToDispatch(work, failure_type, scheduling_failure_message);
862+
tasks_cancelled = true;
863+
return true;
871864
});
872865

873866
ray::erase_if<std::shared_ptr<internal::Work>>(
@@ -889,16 +882,26 @@ bool LocalTaskManager::CancelTasks(
889882
return tasks_cancelled;
890883
}
891884

892-
bool LocalTaskManager::CancelTask(
893-
const TaskID &task_id,
885+
void LocalTaskManager::CancelTaskToDispatch(
886+
const std::shared_ptr<internal::Work> &work,
894887
rpc::RequestWorkerLeaseReply::SchedulingFailureType failure_type,
895888
const std::string &scheduling_failure_message) {
896-
return CancelTasks(
897-
[task_id](const std::shared_ptr<internal::Work> &work) {
898-
return work->task.GetTaskSpecification().TaskId() == task_id;
899-
},
900-
failure_type,
901-
scheduling_failure_message);
889+
const TaskID task_id = work->task.GetTaskSpecification().TaskId();
890+
RAY_LOG(DEBUG) << "Canceling task " << task_id << " from dispatch queue.";
891+
ReplyCancelled(work, failure_type, scheduling_failure_message);
892+
if (work->GetState() == internal::WorkStatus::WAITING_FOR_WORKER) {
893+
// We've already acquired resources so we need to release them.
894+
cluster_resource_scheduler_.GetLocalResourceManager().ReleaseWorkerResources(
895+
work->allocated_instances);
896+
// Release pinned task args.
897+
ReleaseTaskArgs(task_id);
898+
}
899+
if (!work->task.GetTaskSpecification().GetDependencies().empty()) {
900+
task_dependency_manager_.RemoveTaskDependencies(
901+
work->task.GetTaskSpecification().TaskId());
902+
}
903+
RemoveFromRunningTasksIfExists(work->task);
904+
work->SetStateCancelled();
902905
}
903906

904907
const RayTask *LocalTaskManager::AnyPendingTasksForResourceAcquisition(

src/ray/raylet/local_task_manager.h

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -202,17 +202,12 @@ class LocalTaskManager : public ILocalTaskManager {
202202
const rpc::Address &owner_address,
203203
const std::string &runtime_env_setup_error_message);
204204

205-
/// Attempt to cancel an already queued task.
206-
///
207-
/// \param task_id: The id of the task to remove.
208-
/// \param failure_type: The failure type.
209-
///
210-
/// \return True if task was successfully removed. This function will return
211-
/// false if the task is already running.
212-
bool CancelTask(const TaskID &task_id,
213-
rpc::RequestWorkerLeaseReply::SchedulingFailureType failure_type =
214-
rpc::RequestWorkerLeaseReply::SCHEDULING_CANCELLED_INTENDED,
215-
const std::string &scheduling_failure_message = "");
205+
/// Cancels a task in tasks_to_dispatch_. Does not remove it from tasks_to_dispatch_.
206+
void CancelTaskToDispatch(
207+
const std::shared_ptr<internal::Work> &work,
208+
rpc::RequestWorkerLeaseReply::SchedulingFailureType failure_type =
209+
rpc::RequestWorkerLeaseReply::SCHEDULING_CANCELLED_INTENDED,
210+
const std::string &scheduling_failure_message = "");
216211

217212
/// Attempts to dispatch all tasks which are ready to run. A task
218213
/// will be dispatched if it is on `tasks_to_dispatch_` and there are still
@@ -249,12 +244,6 @@ class LocalTaskManager : public ILocalTaskManager {
249244
/// data structure.
250245
void RecomputeDebugStats() const;
251246

252-
/// Determine whether a task should be immediately dispatched,
253-
/// or placed on a wait queue.
254-
///
255-
/// \return True if the work can be immediately dispatched.
256-
bool WaitForTaskArgsRequests(std::shared_ptr<internal::Work> work);
257-
258247
void Dispatch(
259248
std::shared_ptr<WorkerInterface> worker,
260249
absl::flat_hash_map<WorkerID, std::shared_ptr<WorkerInterface>> &leased_workers_,
@@ -277,6 +266,10 @@ class LocalTaskManager : public ILocalTaskManager {
277266
void ReleaseTaskArgs(const TaskID &task_id);
278267

279268
private:
269+
/// Determine whether a task should be immediately dispatched,
270+
/// or placed on a wait queue.
271+
void WaitForTaskArgsRequests(std::shared_ptr<internal::Work> work);
272+
280273
const NodeID &self_node_id_;
281274
const scheduling::NodeID self_scheduling_node_id_;
282275
/// Responsible for resource tracking/view of the cluster.
@@ -293,15 +286,13 @@ class LocalTaskManager : public ILocalTaskManager {
293286
/// running tasks per scheduling class.
294287
struct SchedulingClassInfo {
295288
explicit SchedulingClassInfo(int64_t cap)
296-
: running_tasks(),
297-
capacity(cap),
298-
next_update_time(std::numeric_limits<int64_t>::max()) {}
289+
: capacity(cap), next_update_time(std::numeric_limits<int64_t>::max()) {}
299290
/// Track the running task ids in this scheduling class.
300291
///
301292
/// TODO(hjiang): Store cgroup manager along with task id as the value for map.
302293
absl::flat_hash_set<TaskID> running_tasks;
303294
/// The total number of tasks that can run from this scheduling class.
304-
const uint64_t capacity;
295+
uint64_t capacity;
305296
/// The next time that a new task of this scheduling class may be dispatched.
306297
int64_t next_update_time;
307298
};

src/ray/raylet/local_task_manager_test.cc

Lines changed: 66 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ class MockWorkerPool : public WorkerPoolInterface {
134134
int num_pops;
135135
};
136136

137+
namespace {
138+
137139
std::shared_ptr<ClusterResourceScheduler> CreateSingleNodeScheduler(
138140
const std::string &id, double num_cpus, gcs::GcsClient &gcs_client) {
139141
absl::flat_hash_map<std::string, double> local_node_resources;
@@ -151,7 +153,8 @@ std::shared_ptr<ClusterResourceScheduler> CreateSingleNodeScheduler(
151153
}
152154

153155
RayTask CreateTask(const std::unordered_map<std::string, double> &required_resources,
154-
const std::string &task_name = "default") {
156+
const std::string &task_name = "default",
157+
const std::vector<std::unique_ptr<TaskArg>> &args = {}) {
155158
TaskSpecBuilder spec_builder;
156159
TaskID id = RandomTaskId();
157160
JobID job_id = RandomJobId();
@@ -181,9 +184,15 @@ RayTask CreateTask(const std::unordered_map<std::string, double> &required_resou
181184

182185
spec_builder.SetNormalTaskSpec(0, false, "", rpc::SchedulingStrategy(), ActorID::Nil());
183186

187+
for (const auto &arg : args) {
188+
spec_builder.AddArg(*arg);
189+
}
190+
184191
return RayTask(std::move(spec_builder).ConsumeAndBuild());
185192
}
186193

194+
} // namespace
195+
187196
class LocalTaskManagerTest : public ::testing::Test {
188197
public:
189198
explicit LocalTaskManagerTest(double num_cpus = 3.0)
@@ -253,8 +262,6 @@ class LocalTaskManagerTest : public ::testing::Test {
253262
};
254263

255264
TEST_F(LocalTaskManagerTest, TestTaskDispatchingOrder) {
256-
RAY_LOG(INFO) << "Starting TestTaskDispatchingOrder";
257-
258265
// Initial setup: 3 CPUs available.
259266
std::shared_ptr<MockWorker> worker1 =
260267
std::make_shared<MockWorker>(WorkerID::FromRandom(), 0);
@@ -270,28 +277,12 @@ TEST_F(LocalTaskManagerTest, TestTaskDispatchingOrder) {
270277
auto task_f1 = CreateTask({{ray::kCPU_ResourceLabel, 1}}, "f");
271278
auto task_f2 = CreateTask({{ray::kCPU_ResourceLabel, 1}}, "f");
272279
rpc::RequestWorkerLeaseReply reply;
273-
bool callback_occurred = false;
274-
bool *callback_occurred_ptr = &callback_occurred;
275-
auto callback = [callback_occurred_ptr](
276-
Status, std::function<void()>, std::function<void()>) {
277-
*callback_occurred_ptr = true;
278-
};
279280
local_task_manager_->WaitForTaskArgsRequests(std::make_shared<internal::Work>(
280-
task_f1,
281-
false,
282-
false,
283-
&reply,
284-
[callback] { callback(Status::OK(), nullptr, nullptr); },
285-
internal::WorkStatus::WAITING));
281+
task_f1, false, false, &reply, [] {}, internal::WorkStatus::WAITING));
286282
local_task_manager_->ScheduleAndDispatchTasks();
287283
pool_.TriggerCallbacks();
288284
local_task_manager_->WaitForTaskArgsRequests(std::make_shared<internal::Work>(
289-
task_f2,
290-
false,
291-
false,
292-
&reply,
293-
[callback] { callback(Status::OK(), nullptr, nullptr); },
294-
internal::WorkStatus::WAITING));
285+
task_f2, false, false, &reply, [] {}, internal::WorkStatus::WAITING));
295286
local_task_manager_->ScheduleAndDispatchTasks();
296287
pool_.TriggerCallbacks();
297288

@@ -301,40 +292,70 @@ TEST_F(LocalTaskManagerTest, TestTaskDispatchingOrder) {
301292
auto task_f5 = CreateTask({{ray::kCPU_ResourceLabel, 1}}, "f");
302293
auto task_g1 = CreateTask({{ray::kCPU_ResourceLabel, 1}}, "g");
303294
local_task_manager_->WaitForTaskArgsRequests(std::make_shared<internal::Work>(
304-
task_f3,
305-
false,
306-
false,
307-
&reply,
308-
[callback] { callback(Status::OK(), nullptr, nullptr); },
309-
internal::WorkStatus::WAITING));
295+
task_f3, false, false, &reply, [] {}, internal::WorkStatus::WAITING));
310296
local_task_manager_->WaitForTaskArgsRequests(std::make_shared<internal::Work>(
311-
task_f4,
312-
false,
313-
false,
314-
&reply,
315-
[callback] { callback(Status::OK(), nullptr, nullptr); },
316-
internal::WorkStatus::WAITING));
297+
task_f4, false, false, &reply, [] {}, internal::WorkStatus::WAITING));
317298
local_task_manager_->WaitForTaskArgsRequests(std::make_shared<internal::Work>(
318-
task_f5,
319-
false,
320-
false,
321-
&reply,
322-
[callback] { callback(Status::OK(), nullptr, nullptr); },
323-
internal::WorkStatus::WAITING));
299+
task_f5, false, false, &reply, [] {}, internal::WorkStatus::WAITING));
324300
local_task_manager_->WaitForTaskArgsRequests(std::make_shared<internal::Work>(
325-
task_g1,
326-
false,
327-
false,
328-
&reply,
329-
[callback] { callback(Status::OK(), nullptr, nullptr); },
330-
internal::WorkStatus::WAITING));
301+
task_g1, false, false, &reply, [] {}, internal::WorkStatus::WAITING));
331302
local_task_manager_->ScheduleAndDispatchTasks();
332303
pool_.TriggerCallbacks();
333304
auto tasks_to_dispatch_ = local_task_manager_->GetTaskToDispatch();
334305
// Only task f in queue now as g is dispatched.
335306
ASSERT_EQ(tasks_to_dispatch_.size(), 1);
336307
}
337308

309+
TEST_F(LocalTaskManagerTest, TestNoLeakOnImpossibleInfeasibleTask) {
310+
// Note that ideally it shouldn't be possible for an infeasible task to
311+
// be in the local task manager when ScheduleAndDispatchTasks happens.
312+
// See https://github.com/ray-project/ray/pull/52295 for reasons why added this.
313+
314+
std::shared_ptr<MockWorker> worker1 =
315+
std::make_shared<MockWorker>(WorkerID::FromRandom(), 0);
316+
std::shared_ptr<MockWorker> worker2 =
317+
std::make_shared<MockWorker>(WorkerID::FromRandom(), 0);
318+
pool_.PushWorker(std::static_pointer_cast<WorkerInterface>(worker1));
319+
320+
// Create 2 tasks that requires 3 CPU's each and are waiting on an arg.
321+
auto arg_id = ObjectID::FromRandom();
322+
std::vector<std::unique_ptr<TaskArg>> args;
323+
args.push_back(
324+
std::make_unique<TaskArgByReference>(arg_id, rpc::Address{}, "call_site"));
325+
auto task1 = CreateTask({{kCPU_ResourceLabel, 3}}, "f", args);
326+
auto task2 = CreateTask({{kCPU_ResourceLabel, 3}}, "f2", args);
327+
328+
EXPECT_CALL(object_manager_, Pull(_, _, _))
329+
.WillOnce(::testing::Return(1))
330+
.WillOnce(::testing::Return(2));
331+
332+
// Submit the tasks to the local task manager.
333+
int num_callbacks_called = 0;
334+
auto callback = [&num_callbacks_called]() { ++num_callbacks_called; };
335+
rpc::RequestWorkerLeaseReply reply1;
336+
local_task_manager_->QueueAndScheduleTask(std::make_shared<internal::Work>(
337+
task1, false, false, &reply1, callback, internal::WorkStatus::WAITING));
338+
rpc::RequestWorkerLeaseReply reply2;
339+
local_task_manager_->QueueAndScheduleTask(std::make_shared<internal::Work>(
340+
task2, false, false, &reply2, callback, internal::WorkStatus::WAITING));
341+
342+
// Node no longer has cpu.
343+
scheduler_->GetLocalResourceManager().DeleteLocalResource(
344+
scheduling::ResourceID::CPU());
345+
346+
// Simulate arg becoming local.
347+
local_task_manager_->TasksUnblocked(
348+
{task1.GetTaskSpecification().TaskId(), task2.GetTaskSpecification().TaskId()});
349+
350+
// Assert that the the correct rpc replies were sent back and the dispatch map is empty.
351+
ASSERT_EQ(reply1.failure_type(),
352+
rpc::RequestWorkerLeaseReply::SCHEDULING_CANCELLED_UNSCHEDULABLE);
353+
ASSERT_EQ(reply2.failure_type(),
354+
rpc::RequestWorkerLeaseReply::SCHEDULING_CANCELLED_UNSCHEDULABLE);
355+
ASSERT_EQ(num_callbacks_called, 2);
356+
ASSERT_EQ(local_task_manager_->GetTaskToDispatch().size(), 0);
357+
}
358+
338359
int main(int argc, char **argv) {
339360
::testing::InitGoogleTest(&argc, argv);
340361
return RUN_ALL_TESTS();

0 commit comments

Comments
 (0)