mirror of
https://github.com/wassname/ray.git
synced 2026-08-02 13:01:01 +08:00
* Treat actor creation like a regular task. * Small cleanups. * Change semantics of actor resource handling. * Bug fix. * Minor linting * Bug fix * Fix jenkins test. * Fix actor tests * Some cleanups * Bug fix * Fix bug. * Remove cached actor tasks when a driver is removed. * Add more info to taskspec in global state API. * Fix cyclic import bug in tune. * Fix * Fix linting. * Fix linting. * Don't schedule any tasks (especially actor creaiton tasks) on local schedulers with 0 CPUs. * Bug fix. * Add test for 0 CPU case * Fix linting * Address comments. * Fix typos and add comment. * Add assertion and fix test.
258 lines
9.9 KiB
C++
258 lines
9.9 KiB
C++
#include <limits.h>
|
|
|
|
#include "task.h"
|
|
#include "state/task_table.h"
|
|
|
|
#include "global_scheduler_algorithm.h"
|
|
|
|
GlobalSchedulerPolicyState *GlobalSchedulerPolicyState_init(void) {
|
|
GlobalSchedulerPolicyState *policy_state = new GlobalSchedulerPolicyState();
|
|
return policy_state;
|
|
}
|
|
|
|
void GlobalSchedulerPolicyState_free(GlobalSchedulerPolicyState *policy_state) {
|
|
delete policy_state;
|
|
}
|
|
|
|
/**
|
|
* Checks if the given local scheduler satisfies the task's hard constraints.
|
|
*
|
|
* @param scheduler Local scheduler.
|
|
* @param spec Task specification.
|
|
* @return True if all tasks's resource constraints are satisfied. False
|
|
* otherwise.
|
|
*/
|
|
bool constraints_satisfied_hard(const LocalScheduler *scheduler,
|
|
const TaskSpec *spec) {
|
|
if (scheduler->info.static_resources.count("CPU") == 1 &&
|
|
scheduler->info.static_resources.at("CPU") == 0) {
|
|
// Don't give tasks to local schedulers that have 0 CPUs. This can be an
|
|
// issue for actor creation tasks that require 0 CPUs (but the subsequent
|
|
// actor methods require some CPUs).
|
|
return false;
|
|
}
|
|
|
|
for (auto const &resource_pair : TaskSpec_get_required_resources(spec)) {
|
|
std::string resource_name = resource_pair.first;
|
|
double resource_quantity = resource_pair.second;
|
|
|
|
// Continue on if the task doesn't actually require this resource.
|
|
if (resource_quantity == 0) {
|
|
continue;
|
|
}
|
|
|
|
// Check if the local scheduler has this resource.
|
|
if (scheduler->info.static_resources.count(resource_name) == 0) {
|
|
return false;
|
|
}
|
|
|
|
// Check if the local scheduler has enough of the resource.
|
|
if (scheduler->info.static_resources.at(resource_name) <
|
|
resource_quantity) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int64_t locally_available_data_size(const GlobalSchedulerState *state,
|
|
DBClientID local_scheduler_id,
|
|
TaskSpec *task_spec) {
|
|
/* This function will compute the total size of all the object dependencies
|
|
* for the given task that are already locally available to the specified
|
|
* local scheduler. */
|
|
int64_t task_data_size = 0;
|
|
|
|
RAY_CHECK(state->local_scheduler_plasma_map.count(local_scheduler_id) == 1);
|
|
|
|
const std::string &plasma_manager =
|
|
state->local_scheduler_plasma_map.at(local_scheduler_id);
|
|
|
|
/* TODO(rkn): Note that if the same object ID appears as multiple arguments,
|
|
* then it will be overcounted. */
|
|
for (int64_t i = 0; i < TaskSpec_num_args(task_spec); ++i) {
|
|
int count = TaskSpec_arg_id_count(task_spec, i);
|
|
for (int j = 0; j < count; ++j) {
|
|
ObjectID object_id = TaskSpec_arg_id(task_spec, i, j);
|
|
|
|
if (state->scheduler_object_info_table.count(object_id) == 0) {
|
|
/* If this global scheduler is not aware of this object ID, then ignore
|
|
* it. */
|
|
continue;
|
|
}
|
|
|
|
const SchedulerObjectInfo &object_size_info =
|
|
state->scheduler_object_info_table.at(object_id);
|
|
|
|
if (std::find(object_size_info.object_locations.begin(),
|
|
object_size_info.object_locations.end(), plasma_manager) ==
|
|
object_size_info.object_locations.end()) {
|
|
/* This local scheduler does not have access to this object, so don't
|
|
* count this object. */
|
|
continue;
|
|
}
|
|
|
|
/* Look at the size of the object. */
|
|
int64_t object_size = object_size_info.data_size;
|
|
if (object_size == -1) {
|
|
/* This means that this global scheduler does not know the object size
|
|
* yet, so assume that the object is one megabyte. TODO(rkn): Maybe we
|
|
* should instead use the average object size. */
|
|
object_size = 1000000;
|
|
}
|
|
|
|
/* If we get here, then this local scheduler has access to this object, so
|
|
* count the contribution of this object. */
|
|
task_data_size += object_size;
|
|
}
|
|
}
|
|
|
|
return task_data_size;
|
|
}
|
|
|
|
double calculate_cost_pending(const GlobalSchedulerState *state,
|
|
const LocalScheduler *scheduler,
|
|
TaskSpec *task_spec) {
|
|
/* Calculate how much data is already present on this machine. TODO(rkn): Note
|
|
* that this information is not being used yet. Fix this. */
|
|
locally_available_data_size(state, scheduler->id, task_spec);
|
|
/* TODO(rkn): This logic does not load balance properly when the different
|
|
* machines have different sizes. Fix this. */
|
|
double cost_pending = scheduler->num_recent_tasks_sent +
|
|
scheduler->info.task_queue_length -
|
|
scheduler->info.available_workers;
|
|
return cost_pending;
|
|
}
|
|
|
|
bool handle_task_waiting_random(GlobalSchedulerState *state,
|
|
GlobalSchedulerPolicyState *policy_state,
|
|
Task *task) {
|
|
TaskSpec *task_spec = Task_task_execution_spec(task)->Spec();
|
|
RAY_CHECK(task_spec != NULL)
|
|
<< "task wait handler encounted a task with NULL spec";
|
|
|
|
std::vector<DBClientID> feasible_nodes;
|
|
|
|
for (const auto &it : state->local_schedulers) {
|
|
// Local scheduler map iterator yields <DBClientID, LocalScheduler> pairs.
|
|
const LocalScheduler &local_scheduler = it.second;
|
|
if (!constraints_satisfied_hard(&local_scheduler, task_spec)) {
|
|
continue;
|
|
}
|
|
// Add this local scheduler as a candidate for random selection.
|
|
feasible_nodes.push_back(it.first);
|
|
}
|
|
|
|
if (feasible_nodes.size() == 0) {
|
|
RAY_LOG(ERROR) << "Infeasible task. No nodes satisfy hard constraints for "
|
|
<< "task = " << Task_task_id(task);
|
|
return false;
|
|
}
|
|
|
|
// Randomly select the local scheduler. TODO(atumanov): replace with
|
|
// std::discrete_distribution<int>.
|
|
std::uniform_int_distribution<> dis(0, feasible_nodes.size() - 1);
|
|
DBClientID local_scheduler_id =
|
|
feasible_nodes[dis(policy_state->getRandomGenerator())];
|
|
RAY_CHECK(!local_scheduler_id.is_nil())
|
|
<< "Task is feasible, but doesn't have a local scheduler assigned.";
|
|
// A local scheduler ID was found, so assign the task.
|
|
assign_task_to_local_scheduler(state, task, local_scheduler_id);
|
|
return true;
|
|
}
|
|
|
|
bool handle_task_waiting_cost(GlobalSchedulerState *state,
|
|
GlobalSchedulerPolicyState *policy_state,
|
|
Task *task) {
|
|
TaskSpec *task_spec = Task_task_execution_spec(task)->Spec();
|
|
int64_t curtime = current_time_ms();
|
|
|
|
RAY_CHECK(task_spec != NULL)
|
|
<< "task wait handler encounted a task with NULL spec";
|
|
|
|
// For tasks already seen by the global scheduler (spillback > 1),
|
|
// adjust scheduled task counts for the source local scheduler.
|
|
if (task->execution_spec->SpillbackCount() > 1) {
|
|
auto it = state->local_schedulers.find(task->local_scheduler_id);
|
|
// Task's previous local scheduler must be present and known.
|
|
RAY_CHECK(it != state->local_schedulers.end());
|
|
LocalScheduler &src_local_scheduler = it->second;
|
|
src_local_scheduler.num_recent_tasks_sent -= 1;
|
|
}
|
|
|
|
bool task_feasible = false;
|
|
|
|
// Go through all the nodes, calculate the score for each, pick max score.
|
|
double best_local_scheduler_score = INT32_MIN;
|
|
RAY_CHECK(best_local_scheduler_score < 0)
|
|
<< "We might have a floating point underflow";
|
|
RAY_LOG(INFO) << "ct[" << curtime << "] task from "
|
|
<< task->local_scheduler_id << " spillback "
|
|
<< task->execution_spec->SpillbackCount();
|
|
|
|
// The best node to send this task.
|
|
DBClientID best_local_scheduler_id = DBClientID::nil();
|
|
|
|
for (auto it = state->local_schedulers.begin();
|
|
it != state->local_schedulers.end(); it++) {
|
|
// For each local scheduler, calculate its score. Check hard constraints
|
|
// first.
|
|
LocalScheduler *scheduler = &(it->second);
|
|
if (!constraints_satisfied_hard(scheduler, task_spec)) {
|
|
continue;
|
|
}
|
|
// Skip the local scheduler the task came from.
|
|
if (task->local_scheduler_id == scheduler->id) {
|
|
continue;
|
|
}
|
|
task_feasible = true;
|
|
// This node satisfies the hard capacity constraint. Calculate its score.
|
|
double score = -1 * calculate_cost_pending(state, scheduler, task_spec);
|
|
RAY_LOG(INFO) << "ct[" << curtime << "][" << scheduler->id << "][q"
|
|
<< scheduler->info.task_queue_length << "][w"
|
|
<< scheduler->info.available_workers << "]: score " << score
|
|
<< " bestscore " << best_local_scheduler_score;
|
|
if (score >= best_local_scheduler_score) {
|
|
best_local_scheduler_score = score;
|
|
best_local_scheduler_id = scheduler->id;
|
|
}
|
|
}
|
|
|
|
if (!task_feasible) {
|
|
RAY_LOG(ERROR) << "Infeasible task. No nodes satisfy hard constraints for "
|
|
<< "task = " << Task_task_id(task);
|
|
// TODO(atumanov): propagate this error to the task's driver and/or
|
|
// cache the task in case new local schedulers satisfy it in the future.
|
|
return false;
|
|
}
|
|
RAY_CHECK(!best_local_scheduler_id.is_nil())
|
|
<< "Task is feasible, but doesn't have a local scheduler assigned.";
|
|
// A local scheduler ID was found, so assign the task.
|
|
assign_task_to_local_scheduler(state, task, best_local_scheduler_id);
|
|
return true;
|
|
}
|
|
|
|
bool handle_task_waiting(GlobalSchedulerState *state,
|
|
GlobalSchedulerPolicyState *policy_state,
|
|
Task *task) {
|
|
return handle_task_waiting_random(state, policy_state, task);
|
|
}
|
|
|
|
void handle_object_available(GlobalSchedulerState *state,
|
|
GlobalSchedulerPolicyState *policy_state,
|
|
ObjectID object_id) {
|
|
/* Do nothing for now. */
|
|
}
|
|
|
|
void handle_new_local_scheduler(GlobalSchedulerState *state,
|
|
GlobalSchedulerPolicyState *policy_state,
|
|
DBClientID db_client_id) {
|
|
/* Do nothing for now. */
|
|
}
|
|
|
|
void handle_local_scheduler_removed(GlobalSchedulerState *state,
|
|
GlobalSchedulerPolicyState *policy_state,
|
|
DBClientID db_client_id) {
|
|
/* Do nothing for now. */
|
|
}
|