Implement actor checkpointing (#3839)

* Implement Actor checkpointing

* docs

* fix

* fix

* fix

* move restore-from-checkpoint to HandleActorStateTransition

* Revert "move restore-from-checkpoint to HandleActorStateTransition"

This reverts commit 9aa4447c1e3e321f42a1d895d72f17098b72de12.

* resubmit waiting tasks when actor frontier restored

* add doc about num_actor_checkpoints_to_keep=1

* add num_actor_checkpoints_to_keep to Cython

* add checkpoint_expired api

* check if actor class is abstract

* change checkpoint_ids to long string

* implement java

* Refactor to delay actor creation publish until checkpoint is resumed

* debug, lint

* Erase from checkpoints to restore if task fails

* fix lint

* update comments

* avoid duplicated actor notification log

* fix unintended change

* add actor_id to checkpoint_expired

* small java updates

* make checkpoint info per actor

* lint

* Remove logging

* Remove old actor checkpointing Python code, move new checkpointing code to FunctionActionManager

* Replace old actor checkpointing tests

* Fix test and lint

* address comments

* consolidate kill_actor

* Remove __ray_checkpoint__

* fix non-ascii char

* Loosen test checks

* fix java

* fix sphinx-build
This commit is contained in:
Hao Chen
2019-02-13 19:39:02 +08:00
committed by GitHub
parent 57dcd3033e
commit f31a79f3f7
41 changed files with 1708 additions and 490 deletions
+10
View File
@@ -118,6 +118,8 @@ AsyncGcsClient::AsyncGcsClient(const std::string &address, int port,
task_lease_table_.reset(new TaskLeaseTable(shard_contexts_, this));
heartbeat_table_.reset(new HeartbeatTable(shard_contexts_, this));
profile_table_.reset(new ProfileTable(shard_contexts_, this));
actor_checkpoint_table_.reset(new ActorCheckpointTable(shard_contexts_, this));
actor_checkpoint_id_table_.reset(new ActorCheckpointIdTable(shard_contexts_, this));
command_type_ = command_type;
// TODO(swang): Call the client table's Connect() method here. To do this,
@@ -219,6 +221,14 @@ DriverTable &AsyncGcsClient::driver_table() { return *driver_table_; }
ProfileTable &AsyncGcsClient::profile_table() { return *profile_table_; }
ActorCheckpointTable &AsyncGcsClient::actor_checkpoint_table() {
return *actor_checkpoint_table_;
}
ActorCheckpointIdTable &AsyncGcsClient::actor_checkpoint_id_table() {
return *actor_checkpoint_id_table_;
}
} // namespace gcs
} // namespace ray
+4
View File
@@ -60,6 +60,8 @@ class RAY_EXPORT AsyncGcsClient {
ErrorTable &error_table();
DriverTable &driver_table();
ProfileTable &profile_table();
ActorCheckpointTable &actor_checkpoint_table();
ActorCheckpointIdTable &actor_checkpoint_id_table();
// We also need something to export generic code to run on workers from the
// driver (to set the PYTHONPATH)
@@ -90,6 +92,8 @@ class RAY_EXPORT AsyncGcsClient {
std::unique_ptr<ErrorTable> error_table_;
std::unique_ptr<ProfileTable> profile_table_;
std::unique_ptr<ClientTable> client_table_;
std::unique_ptr<ActorCheckpointTable> actor_checkpoint_table_;
std::unique_ptr<ActorCheckpointIdTable> actor_checkpoint_id_table_;
// The following contexts write to the data shard
std::vector<std::shared_ptr<RedisContext>> shard_contexts_;
std::vector<std::unique_ptr<RedisAsioClient>> shard_asio_async_clients_;
+33 -2
View File
@@ -20,6 +20,8 @@ enum TablePrefix:int {
DRIVER,
PROFILE,
TASK_LEASE,
ACTOR_CHECKPOINT,
ACTOR_CHECKPOINT_ID,
}
// The channel that Add operations to the Table should be published on, if any.
@@ -72,8 +74,6 @@ table TaskInfo {
actor_handle_id: string;
// Number of tasks that have been submitted to this actor so far.
actor_counter: int;
// True if this task is an actor checkpoint task and false otherwise.
is_actor_checkpoint_method: bool;
// If this is an actor task, then this will be populated with all of the new
// actor handles that were forked from this handle since the last task on
// this handle was submitted.
@@ -318,3 +318,34 @@ table DriverTableData {
// Whether it's dead.
is_dead: bool;
}
// This table stores the actor checkpoint data. An actor checkpoint
// is the snapshot of an actor's state in the actor registration.
// See `actor_registration.h` for more detailed explanation of these fields.
table ActorCheckpointData {
// ID of this actor.
actor_id: string;
// The dummy object ID of actor's most recently executed task.
execution_dependency: string;
// A list of IDs of this actor's handles.
handle_ids: [string];
// The task counters of the above handles.
task_counters: [long];
// The frontier dependencies of the above handles.
frontier_dependencies: [string];
// A list of unreleased dummy objects from this actor.
unreleased_dummy_objects: [string];
// The numbers of dependencies for the above unreleased dummy objects.
num_dummy_object_dependencies: [int];
}
// This table stores the actor-to-available-checkpoint-ids mapping.
table ActorCheckpointIdData {
// ID of this actor.
actor_id: string;
// IDs of this actor's available checkpoints.
// Note, this is a long string that concatenates all the IDs.
checkpoint_ids: string;
// A list of the timestamps for each of the above `checkpoint_ids`.
timestamps: [long];
}
+39
View File
@@ -2,6 +2,8 @@
#include "ray/common/common_protocol.h"
#include "ray/gcs/client.h"
#include "ray/ray_config.h"
#include "ray/util/util.h"
namespace {
@@ -438,6 +440,41 @@ std::string ClientTable::DebugString() const {
return result.str();
}
Status ActorCheckpointIdTable::AddCheckpointId(const JobID &job_id,
const ActorID &actor_id,
const UniqueID &checkpoint_id) {
auto lookup_callback = [this, checkpoint_id, job_id, actor_id](
ray::gcs::AsyncGcsClient *client, const UniqueID &id,
const ActorCheckpointIdDataT &data) {
std::shared_ptr<ActorCheckpointIdDataT> copy =
std::make_shared<ActorCheckpointIdDataT>(data);
copy->timestamps.push_back(current_sys_time_ms());
copy->checkpoint_ids += checkpoint_id.binary();
auto num_to_keep = RayConfig::instance().num_actor_checkpoints_to_keep();
while (copy->timestamps.size() > num_to_keep) {
// Delete the checkpoint from actor checkpoint table.
const auto &checkpoint_id =
UniqueID::from_binary(copy->checkpoint_ids.substr(0, kUniqueIDSize));
RAY_LOG(DEBUG) << "Deleting checkpoint " << checkpoint_id << " for actor "
<< actor_id;
copy->timestamps.erase(copy->timestamps.begin());
copy->checkpoint_ids.erase(0, kUniqueIDSize);
// TODO(hchen): also delete checkpoint data from GCS.
}
RAY_CHECK_OK(Add(job_id, actor_id, copy, nullptr));
};
auto failure_callback = [this, checkpoint_id, job_id, actor_id](
ray::gcs::AsyncGcsClient *client, const UniqueID &id) {
std::shared_ptr<ActorCheckpointIdDataT> data =
std::make_shared<ActorCheckpointIdDataT>();
data->actor_id = id.binary();
data->timestamps.push_back(current_sys_time_ms());
data->checkpoint_ids = checkpoint_id.binary();
RAY_CHECK_OK(Add(job_id, actor_id, data, nullptr));
};
return Lookup(job_id, actor_id, lookup_callback, failure_callback);
}
template class Log<ObjectID, ObjectTableData>;
template class Log<TaskID, ray::protocol::Task>;
template class Table<TaskID, ray::protocol::Task>;
@@ -451,6 +488,8 @@ template class Log<JobID, ErrorTableData>;
template class Log<UniqueID, ClientTableData>;
template class Log<JobID, DriverTableData>;
template class Log<UniqueID, ProfileTableData>;
template class Table<ActorCheckpointID, ActorCheckpointData>;
template class Table<ActorID, ActorCheckpointIdData>;
} // namespace gcs
+28
View File
@@ -443,6 +443,34 @@ class TaskLeaseTable : public Table<TaskID, TaskLeaseData> {
}
};
class ActorCheckpointTable : public Table<ActorCheckpointID, ActorCheckpointData> {
public:
ActorCheckpointTable(const std::vector<std::shared_ptr<RedisContext>> &contexts,
AsyncGcsClient *client)
: Table(contexts, client) {
prefix_ = TablePrefix::ACTOR_CHECKPOINT;
};
};
class ActorCheckpointIdTable : public Table<ActorID, ActorCheckpointIdData> {
public:
ActorCheckpointIdTable(const std::vector<std::shared_ptr<RedisContext>> &contexts,
AsyncGcsClient *client)
: Table(contexts, client) {
prefix_ = TablePrefix::ACTOR_CHECKPOINT_ID;
};
/// Add a checkpoint id to an actor, and remove a previous checkpoint if the
/// total number of checkpoints in GCS exceeds the max allowed value.
///
/// \param job_id The ID of the job (= driver).
/// \param actor_id ID of the actor.
/// \param checkpoint_id ID of the checkpoint.
/// \return Status.
Status AddCheckpointId(const JobID &job_id, const ActorID &actor_id,
const UniqueID &checkpoint_id);
};
namespace raylet {
class TaskTable : public Table<TaskID, ray::protocol::Task> {
+1
View File
@@ -45,6 +45,7 @@ typedef UniqueID FunctionID;
typedef UniqueID ActorClassID;
typedef UniqueID ActorID;
typedef UniqueID ActorHandleID;
typedef UniqueID ActorCheckpointID;
typedef UniqueID WorkerID;
typedef UniqueID DriverID;
typedef UniqueID ConfigID;
+6
View File
@@ -144,3 +144,9 @@ RAY_CONFIG(int, num_workers_per_process, 1);
/// Maximum timeout in milliseconds within which a task lease must be renewed.
RAY_CONFIG(int64_t, max_task_lease_timeout_ms, 60000);
/// Maximum number of checkpoints to keep in GCS for an actor.
/// Note: this number should be set to at least 2. Because saving a application
/// checkpoint isn't atomic with saving the backend checkpoint, and it will break
/// if this number is set to 1 and users save application checkpoints in place.
RAY_CONFIG(uint32_t, num_actor_checkpoints_to_keep, 20);
+48
View File
@@ -11,6 +11,25 @@ namespace raylet {
ActorRegistration::ActorRegistration(const ActorTableDataT &actor_table_data)
: actor_table_data_(actor_table_data) {}
ActorRegistration::ActorRegistration(const ActorTableDataT &actor_table_data,
const ActorCheckpointDataT &checkpoint_data)
: actor_table_data_(actor_table_data),
execution_dependency_(ObjectID::from_binary(checkpoint_data.execution_dependency)) {
// Restore `frontier_`.
for (size_t i = 0; i < checkpoint_data.handle_ids.size(); i++) {
auto handle_id = ActorHandleID::from_binary(checkpoint_data.handle_ids[i]);
auto &frontier_entry = frontier_[handle_id];
frontier_entry.task_counter = checkpoint_data.task_counters[i];
frontier_entry.execution_dependency =
ObjectID::from_binary(checkpoint_data.frontier_dependencies[i]);
}
// Restore `dummy_objects_`.
for (size_t i = 0; i < checkpoint_data.unreleased_dummy_objects.size(); i++) {
auto dummy = ObjectID::from_binary(checkpoint_data.unreleased_dummy_objects[i]);
dummy_objects_[dummy] = checkpoint_data.num_dummy_object_dependencies[i];
}
}
const ClientID ActorRegistration::GetNodeManagerId() const {
return ClientID::from_binary(actor_table_data_.node_manager_id);
}
@@ -77,6 +96,35 @@ void ActorRegistration::AddHandle(const ActorHandleID &handle_id,
int ActorRegistration::NumHandles() const { return frontier_.size(); }
std::shared_ptr<ActorCheckpointDataT> ActorRegistration::GenerateCheckpointData(
const ActorID &actor_id, const Task &task) {
const auto actor_handle_id = task.GetTaskSpecification().ActorHandleId();
const auto dummy_object = task.GetTaskSpecification().ActorDummyObject();
// Make a copy of the actor registration, and extend its frontier to include
// the most recent task.
// Note(hchen): this is needed because this method is called before
// `FinishAssignedTask`, which will be called when the worker tries to fetch
// the next task.
ActorRegistration copy = *this;
copy.ExtendFrontier(actor_handle_id, dummy_object);
// Use actor's current state to generate checkpoint data.
auto checkpoint_data = std::make_shared<ActorCheckpointDataT>();
checkpoint_data->actor_id = actor_id.binary();
checkpoint_data->execution_dependency = copy.GetExecutionDependency().binary();
for (const auto &frontier : copy.GetFrontier()) {
checkpoint_data->handle_ids.push_back(frontier.first.binary());
checkpoint_data->task_counters.push_back(frontier.second.task_counter);
checkpoint_data->frontier_dependencies.push_back(
frontier.second.execution_dependency.binary());
}
for (const auto &entry : copy.GetDummyObjects()) {
checkpoint_data->unreleased_dummy_objects.push_back(entry.first.binary());
checkpoint_data->num_dummy_object_dependencies.push_back(entry.second);
}
return checkpoint_data;
}
} // namespace raylet
} // namespace ray
+15
View File
@@ -5,6 +5,7 @@
#include "ray/gcs/format/gcs_generated.h"
#include "ray/id.h"
#include "ray/raylet/task.h"
namespace ray {
@@ -24,6 +25,12 @@ class ActorRegistration {
/// this actor. This includes the actor's node manager location.
ActorRegistration(const ActorTableDataT &actor_table_data);
/// Recreate an actor's registration from a checkpoint.
///
/// \param checkpoint_data The checkpoint used to restore the actor.
ActorRegistration(const ActorTableDataT &actor_table_data,
const ActorCheckpointDataT &checkpoint_data);
/// Each actor may have multiple callers, or "handles". A frontier leaf
/// represents the execution state of the actor with respect to a single
/// handle.
@@ -119,6 +126,14 @@ class ActorRegistration {
/// \return int.
int NumHandles() const;
/// Generate checkpoint data based on actor's current state.
///
/// \param actor_id ID of this actor.
/// \param task The task that just finished on the actor.
/// \return A shared pointer to the generated checkpoint data.
std::shared_ptr<ActorCheckpointDataT> GenerateCheckpointData(const ActorID &actor_id,
const Task &task);
private:
/// Information from the global actor table about this actor, including the
/// node manager location.
+23
View File
@@ -71,6 +71,12 @@ enum MessageType:int {
PushProfileEventsRequest,
// Free the objects in objects store.
FreeObjectsInObjectStoreRequest,
// Request raylet backend to prepare a checkpoint for an actor.
PrepareActorCheckpointRequest,
// Reply of `PrepareActorCheckpointRequest`.
PrepareActorCheckpointReply,
// Notify raylet backend that an actor was resumed from a checkpoint.
NotifyActorResumedFromCheckpoint,
// A node manager requests to connect to another node manager.
ConnectClient,
}
@@ -207,6 +213,23 @@ table FreeObjectsRequest {
object_ids: [string];
}
table PrepareActorCheckpointRequest {
// ID of the actor.
actor_id: string;
}
table PrepareActorCheckpointReply {
// ID of the checkpoint.
checkpoint_id: string;
}
table NotifyActorResumedFromCheckpoint {
// ID of the actor.
actor_id: string;
// ID of the checkpoint from which the actor was resumed.
checkpoint_id: string;
}
table ConnectClient {
// ID of the connecting client.
client_id: string;
@@ -268,6 +268,40 @@ Java_org_ray_runtime_raylet_RayletClientImpl_nativeFreePlasmaObjects(
ThrowRayExceptionIfNotOK(env, status, "[RayletClient] Failed to free objects.");
}
/*
* Class: org_ray_runtime_raylet_RayletClientImpl
* Method: nativePrepareCheckpoint
* Signature: (J[B)[B
*/
JNIEXPORT jbyteArray JNICALL
Java_org_ray_runtime_raylet_RayletClientImpl_nativePrepareCheckpoint(JNIEnv *env, jclass,
jlong client,
jbyteArray actorId) {
auto raylet_client = reinterpret_cast<RayletClient *>(client);
UniqueIdFromJByteArray actor_id(env, actorId);
ActorCheckpointID checkpoint_id;
RAY_CHECK_OK(raylet_client->PrepareActorCheckpoint(*actor_id.PID, checkpoint_id));
jbyteArray result = env->NewByteArray(sizeof(ActorCheckpointID));
env->SetByteArrayRegion(result, 0, sizeof(ActorCheckpointID),
reinterpret_cast<jbyte *>(&checkpoint_id));
return result;
}
/*
* Class: org_ray_runtime_raylet_RayletClientImpl
* Method: nativeNotifyActorResumedFromCheckpoint
* Signature: (J[B[B)V
*/
JNIEXPORT void JNICALL
Java_org_ray_runtime_raylet_RayletClientImpl_nativeNotifyActorResumedFromCheckpoint(
JNIEnv *env, jclass, jlong client, jbyteArray actorId, jbyteArray checkpointId) {
auto raylet_client = reinterpret_cast<RayletClient *>(client);
UniqueIdFromJByteArray actor_id(env, actorId);
UniqueIdFromJByteArray checkpoint_id(env, checkpointId);
RAY_CHECK_OK(
raylet_client->NotifyActorResumedFromCheckpoint(*actor_id.PID, *checkpoint_id.PID));
}
#ifdef __cplusplus
}
#endif
@@ -7,6 +7,8 @@
#ifdef __cplusplus
extern "C" {
#endif
#undef org_ray_runtime_raylet_RayletClientImpl_TASK_SPEC_BUFFER_SIZE
#define org_ray_runtime_raylet_RayletClientImpl_TASK_SPEC_BUFFER_SIZE 2097152L
/*
* Class: org_ray_runtime_raylet_RayletClientImpl
* Method: nativeInit
@@ -58,6 +60,14 @@ Java_org_ray_runtime_raylet_RayletClientImpl_nativeFetchOrReconstruct(JNIEnv *,
JNIEXPORT void JNICALL Java_org_ray_runtime_raylet_RayletClientImpl_nativeNotifyUnblocked(
JNIEnv *, jclass, jlong, jbyteArray);
/*
* Class: org_ray_runtime_raylet_RayletClientImpl
* Method: nativePutObject
* Signature: (J[B[B)V
*/
JNIEXPORT void JNICALL Java_org_ray_runtime_raylet_RayletClientImpl_nativePutObject(
JNIEnv *, jclass, jlong, jbyteArray, jbyteArray);
/*
* Class: org_ray_runtime_raylet_RayletClientImpl
* Method: nativeWaitObject
@@ -88,6 +98,24 @@ Java_org_ray_runtime_raylet_RayletClientImpl_nativeFreePlasmaObjects(JNIEnv *, j
jlong, jobjectArray,
jboolean);
/*
* Class: org_ray_runtime_raylet_RayletClientImpl
* Method: nativePrepareCheckpoint
* Signature: (J[B)[B
*/
JNIEXPORT jbyteArray JNICALL
Java_org_ray_runtime_raylet_RayletClientImpl_nativePrepareCheckpoint(JNIEnv *, jclass,
jlong, jbyteArray);
/*
* Class: org_ray_runtime_raylet_RayletClientImpl
* Method: nativeNotifyActorResumedFromCheckpoint
* Signature: (J[B[B)V
*/
JNIEXPORT void JNICALL
Java_org_ray_runtime_raylet_RayletClientImpl_nativeNotifyActorResumedFromCheckpoint(
JNIEnv *, jclass, jlong, jbyteArray, jbyteArray);
#ifdef __cplusplus
}
#endif
+202 -78
View File
@@ -140,7 +140,7 @@ ray::Status NodeManager::RegisterGcs() {
if (!data.empty()) {
// We only need the last entry, because it represents the latest state of
// this actor.
HandleActorStateTransition(actor_id, data.back());
HandleActorStateTransition(actor_id, ActorRegistration(data.back()));
}
};
@@ -507,13 +507,7 @@ void NodeManager::PublishActorStateTransition(
}
void NodeManager::HandleActorStateTransition(const ActorID &actor_id,
const ActorTableDataT &data) {
ActorRegistration actor_registration(data);
RAY_LOG(DEBUG) << "Actor notification received: actor_id = " << actor_id
<< ", node_manager_id = " << actor_registration.GetNodeManagerId()
<< ", state = " << EnumNameActorState(actor_registration.GetState())
<< ", remaining_reconstructions = "
<< actor_registration.GetRemainingReconstructions();
ActorRegistration &&actor_registration) {
// Update local registry.
auto it = actor_registry_.find(actor_id);
if (it == actor_registry_.end()) {
@@ -536,6 +530,11 @@ void NodeManager::HandleActorStateTransition(const ActorID &actor_id,
return;
}
}
RAY_LOG(DEBUG) << "Actor notification received: actor_id = " << actor_id
<< ", node_manager_id = " << actor_registration.GetNodeManagerId()
<< ", state = " << EnumNameActorState(actor_registration.GetState())
<< ", remaining_reconstructions = "
<< actor_registration.GetRemainingReconstructions();
if (actor_registration.GetState() == ActorState::ALIVE) {
// The actor's location is now known. Dequeue any methods that were
@@ -700,6 +699,12 @@ void NodeManager::ProcessClientMessage(
std::vector<ObjectID> object_ids = from_flatbuf(*message->object_ids());
object_manager_.FreeObjects(object_ids, message->local_only());
} break;
case protocol::MessageType::PrepareActorCheckpointRequest: {
ProcessPrepareActorCheckpointRequest(client, message_data);
} break;
case protocol::MessageType::NotifyActorResumedFromCheckpoint: {
ProcessNotifyActorResumedFromCheckpoint(message_data);
} break;
default:
RAY_LOG(FATAL) << "Received unexpected message type " << message_type;
@@ -762,7 +767,7 @@ void NodeManager::HandleDisconnectedActor(const ActorID &actor_id, bool was_loca
// So if we receive any actor tasks before we receive GCS notification,
// these tasks can be correctly routed to the `MethodsWaitingForActorCreation` queue,
// instead of being assigned to the dead actor.
HandleActorStateTransition(actor_id, new_actor_data);
HandleActorStateTransition(actor_id, ActorRegistration(new_actor_data));
}
ray::gcs::ActorTable::WriteCallback failure_callback = nullptr;
if (was_local) {
@@ -1014,6 +1019,64 @@ void NodeManager::ProcessPushErrorRequestMessage(const uint8_t *message_data) {
timestamp));
}
void NodeManager::ProcessPrepareActorCheckpointRequest(
const std::shared_ptr<LocalClientConnection> &client, const uint8_t *message_data) {
auto message =
flatbuffers::GetRoot<protocol::PrepareActorCheckpointRequest>(message_data);
ActorID actor_id = from_flatbuf(*message->actor_id());
RAY_LOG(DEBUG) << "Preparing checkpoint for actor " << actor_id;
const auto &actor_entry = actor_registry_.find(actor_id);
RAY_CHECK(actor_entry != actor_registry_.end());
std::shared_ptr<Worker> worker = worker_pool_.GetRegisteredWorker(client);
RAY_CHECK(worker && worker->GetActorId() == actor_id);
// Find the task that is running on this actor.
const auto task_id = worker->GetAssignedTaskId();
const Task &task = local_queues_.GetTaskOfState(task_id, TaskState::RUNNING);
// Generate checkpoint id and data.
ActorCheckpointID checkpoint_id = UniqueID::from_random();
auto checkpoint_data =
actor_entry->second.GenerateCheckpointData(actor_entry->first, task);
// Write checkpoint data to GCS.
RAY_CHECK_OK(gcs_client_->actor_checkpoint_table().Add(
UniqueID::nil(), checkpoint_id, checkpoint_data,
[worker, actor_id, this](ray::gcs::AsyncGcsClient *client,
const UniqueID &checkpoint_id,
const ActorCheckpointDataT &data) {
RAY_LOG(DEBUG) << "Checkpoint " << checkpoint_id << " saved for actor "
<< worker->GetActorId();
// Save this actor-to-checkpoint mapping, and remove old checkpoints associated
// with this actor.
RAY_CHECK_OK(gcs_client_->actor_checkpoint_id_table().AddCheckpointId(
JobID::nil(), actor_id, checkpoint_id));
// Send reply to worker.
flatbuffers::FlatBufferBuilder fbb;
auto reply = ray::protocol::CreatePrepareActorCheckpointReply(
fbb, to_flatbuf(fbb, checkpoint_id));
fbb.Finish(reply);
worker->Connection()->WriteMessageAsync(
static_cast<int64_t>(protocol::MessageType::PrepareActorCheckpointReply),
fbb.GetSize(), fbb.GetBufferPointer(), [](const ray::Status &status) {
if (!status.ok()) {
RAY_LOG(WARNING)
<< "Failed to send PrepareActorCheckpointReply to client";
}
});
}));
}
void NodeManager::ProcessNotifyActorResumedFromCheckpoint(const uint8_t *message_data) {
auto message =
flatbuffers::GetRoot<protocol::NotifyActorResumedFromCheckpoint>(message_data);
ActorID actor_id = from_flatbuf(*message->actor_id());
ActorCheckpointID checkpoint_id = from_flatbuf(*message->checkpoint_id());
RAY_LOG(DEBUG) << "Actor " << actor_id << " was resumed from checkpoint "
<< checkpoint_id;
checkpoint_id_to_restore_.emplace(actor_id, checkpoint_id);
}
void NodeManager::ProcessNewNodeManager(TcpClientConnection &node_manager_client) {
node_manager_client.ProcessMessages();
}
@@ -1154,6 +1217,12 @@ bool NodeManager::CheckDependencyManagerInvariant() const {
void NodeManager::TreatTaskAsFailed(const Task &task) {
const TaskSpecification &spec = task.GetTaskSpecification();
RAY_LOG(DEBUG) << "Treating task " << spec.TaskId() << " as failed.";
// If this was an actor creation task that tried to resume from a checkpoint,
// then erase it here since the task did not finish.
if (spec.IsActorCreationTask()) {
ActorID actor_id = spec.ActorCreationId();
checkpoint_id_to_restore_.erase(actor_id);
}
// Loop over the return IDs (except the dummy ID) and store a fake object in
// the object store.
int64_t num_returns = spec.NumReturns();
@@ -1320,7 +1389,7 @@ void NodeManager::SubmitTask(const Task &task, const Lineage &uncommitted_lineag
if (!data.empty()) {
// The actor has been created. We only need the last entry, because
// it represents the latest state of this actor.
HandleActorStateTransition(actor_id, data.back());
HandleActorStateTransition(actor_id, ActorRegistration(data.back()));
}
};
RAY_CHECK_OK(gcs_client_->actor_table().Lookup(JobID::nil(), spec.ActorId(),
@@ -1672,86 +1741,141 @@ void NodeManager::FinishAssignedTask(Worker &worker) {
}
}
void NodeManager::FinishAssignedActorTask(Worker &worker, const Task &task) {
// If this was an actor creation task, then convert the worker to an actor
// and notify the other node managers.
if (task.GetTaskSpecification().IsActorCreationTask()) {
// Convert the worker to an actor.
auto actor_id = task.GetTaskSpecification().ActorCreationId();
worker.AssignActorId(actor_id);
// Publish the actor creation event to all other nodes so that methods for
// the actor will be forwarded directly to this node.
auto actor_entry = actor_registry_.find(actor_id);
ActorTableDataT new_actor_data;
if (actor_entry == actor_registry_.end()) {
// Set all of the static fields for the actor. These fields will not
// change even if the actor fails or is reconstructed.
new_actor_data.actor_id = actor_id.binary();
new_actor_data.actor_creation_dummy_object_id =
task.GetTaskSpecification().ActorDummyObject().binary();
new_actor_data.driver_id = task.GetTaskSpecification().DriverId().binary();
new_actor_data.max_reconstructions =
task.GetTaskSpecification().MaxActorReconstructions();
// This is the first time that the actor has been created, so the number
// of remaining reconstructions is the max.
new_actor_data.remaining_reconstructions =
task.GetTaskSpecification().MaxActorReconstructions();
} else {
// If we've already seen this actor, it means that this actor was reconstructed.
// Thus, its previous state must be RECONSTRUCTING.
RAY_CHECK(actor_entry->second.GetState() == ActorState::RECONSTRUCTING);
// Copy the static fields from the current actor entry.
new_actor_data = actor_entry->second.GetTableData();
// We are reconstructing the actor, so subtract its
// remaining_reconstructions by 1.
new_actor_data.remaining_reconstructions--;
}
// Set the new fields for the actor's state to indicate that the actor is
// now alive on this node manager.
new_actor_data.node_manager_id =
gcs_client_->client_table().GetLocalClientId().binary();
new_actor_data.state = ActorState::ALIVE;
HandleActorStateTransition(actor_id, new_actor_data);
PublishActorStateTransition(
actor_id, new_actor_data,
/*failure_callback=*/
[](gcs::AsyncGcsClient *client, const ActorID &id, const ActorTableDataT &data) {
// Only one node at a time should succeed at creating the actor.
RAY_LOG(FATAL) << "Failed to update state to ALIVE for actor " << id;
});
ActorTableDataT NodeManager::CreateActorTableDataFromCreationTask(const Task &task) {
RAY_CHECK(task.GetTaskSpecification().IsActorCreationTask());
auto actor_id = task.GetTaskSpecification().ActorCreationId();
auto actor_entry = actor_registry_.find(actor_id);
ActorTableDataT new_actor_data;
// TODO(swang): If this is an actor that was reconstructed, and previous
// actor notifications were delayed, then this node may not have an entry for
// the actor in actor_regisry_. Then, the fields for the number of
// reconstructions will be wrong.
if (actor_entry == actor_registry_.end()) {
// Set all of the static fields for the actor. These fields will not
// change even if the actor fails or is reconstructed.
new_actor_data.actor_id = actor_id.binary();
new_actor_data.actor_creation_dummy_object_id =
task.GetTaskSpecification().ActorDummyObject().binary();
new_actor_data.driver_id = task.GetTaskSpecification().DriverId().binary();
new_actor_data.max_reconstructions =
task.GetTaskSpecification().MaxActorReconstructions();
// This is the first time that the actor has been created, so the number
// of remaining reconstructions is the max.
new_actor_data.remaining_reconstructions =
task.GetTaskSpecification().MaxActorReconstructions();
} else {
// If we've already seen this actor, it means that this actor was reconstructed.
// Thus, its previous state must be RECONSTRUCTING.
RAY_CHECK(actor_entry->second.GetState() == ActorState::RECONSTRUCTING);
// Copy the static fields from the current actor entry.
new_actor_data = actor_entry->second.GetTableData();
// We are reconstructing the actor, so subtract its
// remaining_reconstructions by 1.
new_actor_data.remaining_reconstructions--;
}
// Update the actor's frontier.
// Set the new fields for the actor's state to indicate that the actor is
// now alive on this node manager.
new_actor_data.node_manager_id =
gcs_client_->client_table().GetLocalClientId().binary();
new_actor_data.state = ActorState::ALIVE;
return new_actor_data;
}
void NodeManager::FinishAssignedActorTask(Worker &worker, const Task &task) {
ActorID actor_id;
ActorHandleID actor_handle_id;
bool resumed_from_checkpoint = false;
if (task.GetTaskSpecification().IsActorCreationTask()) {
actor_id = task.GetTaskSpecification().ActorCreationId();
actor_handle_id = ActorHandleID::nil();
if (checkpoint_id_to_restore_.count(actor_id) > 0) {
resumed_from_checkpoint = true;
}
} else {
actor_id = task.GetTaskSpecification().ActorId();
actor_handle_id = task.GetTaskSpecification().ActorHandleId();
}
auto actor_entry = actor_registry_.find(actor_id);
RAY_CHECK(actor_entry != actor_registry_.end());
// Extend the actor's frontier to include the executed task.
const auto dummy_object = task.GetTaskSpecification().ActorDummyObject();
const ObjectID object_to_release =
actor_entry->second.ExtendFrontier(actor_handle_id, dummy_object);
if (!object_to_release.is_nil()) {
// If there were no new actor handles created, then no other actor task
// will depend on this execution dependency, so it safe to release.
HandleObjectMissing(object_to_release);
if (task.GetTaskSpecification().IsActorCreationTask()) {
// This was an actor creation task. Convert the worker to an actor.
worker.AssignActorId(actor_id);
// Notify the other node managers that the actor has been created.
const auto new_actor_data = CreateActorTableDataFromCreationTask(task);
if (resumed_from_checkpoint) {
// This actor was resumed from a checkpoint. In this case, we first look
// up the checkpoint in GCS and use it to restore the actor registration
// and frontier.
const auto checkpoint_id = checkpoint_id_to_restore_[actor_id];
checkpoint_id_to_restore_.erase(actor_id);
RAY_LOG(DEBUG) << "Looking up checkpoint " << checkpoint_id << " for actor "
<< actor_id;
RAY_CHECK_OK(gcs_client_->actor_checkpoint_table().Lookup(
JobID::nil(), checkpoint_id,
[this, actor_id, new_actor_data](ray::gcs::AsyncGcsClient *client,
const UniqueID &checkpoint_id,
const ActorCheckpointDataT &checkpoint_data) {
RAY_LOG(INFO) << "Restoring registration for actor " << actor_id
<< " from checkpoint " << checkpoint_id;
ActorRegistration actor_registration =
ActorRegistration(new_actor_data, checkpoint_data);
// Mark the unreleased dummy objects in the checkpoint frontier as local.
for (const auto &entry : actor_registration.GetDummyObjects()) {
HandleObjectLocal(entry.first);
}
HandleActorStateTransition(actor_id, std::move(actor_registration));
PublishActorStateTransition(
actor_id, new_actor_data,
/*failure_callback=*/
[](gcs::AsyncGcsClient *client, const ActorID &id,
const ActorTableDataT &data) {
// Only one node at a time should succeed at creating the actor.
RAY_LOG(FATAL) << "Failed to update state to ALIVE for actor " << id;
});
},
[actor_id](ray::gcs::AsyncGcsClient *client, const UniqueID &checkpoint_id) {
RAY_LOG(FATAL) << "Couldn't find checkpoint " << checkpoint_id
<< " for actor " << actor_id << " in GCS.";
}));
} else {
// The actor did not resume from a checkpoint. Immediately notify the
// other node managers that the actor has been created.
HandleActorStateTransition(actor_id, ActorRegistration(new_actor_data));
PublishActorStateTransition(
actor_id, new_actor_data,
/*failure_callback=*/
[](gcs::AsyncGcsClient *client, const ActorID &id,
const ActorTableDataT &data) {
// Only one node at a time should succeed at creating the actor.
RAY_LOG(FATAL) << "Failed to update state to ALIVE for actor " << id;
});
}
}
if (!resumed_from_checkpoint) {
// The actor was not resumed from a checkpoint. We extend the actor's
// frontier as usual since there is no frontier to restore.
auto actor_entry = actor_registry_.find(actor_id);
RAY_CHECK(actor_entry != actor_registry_.end());
// Extend the actor's frontier to include the executed task.
const auto dummy_object = task.GetTaskSpecification().ActorDummyObject();
const ObjectID object_to_release =
actor_entry->second.ExtendFrontier(actor_handle_id, dummy_object);
if (!object_to_release.is_nil()) {
// If there were no new actor handles created, then no other actor task
// will depend on this execution dependency, so it safe to release.
HandleObjectMissing(object_to_release);
}
// Mark the dummy object as locally available to indicate that the actor's
// state has changed and the next method can run. This is not added to the
// object table, so the update will be invisible to both the local object
// manager and the other nodes.
// NOTE(swang): The dummy objects must be marked as local whenever
// ExtendFrontier is called, and vice versa, so that we can clean up the
// dummy objects properly in case the actor fails and needs to be
// reconstructed.
HandleObjectLocal(dummy_object);
}
// Mark the dummy object as locally available to indicate that the actor's
// state has changed and the next method can run. This is not added to the
// object table, so the update will be invisible to both the local object
// manager and the other nodes.
// NOTE(swang): The dummy objects must be marked as local whenever
// ExtendFrontier is called, and vice versa, so that we can clean up the
// dummy objects properly in case the actor fails and needs to be
// reconstructed.
HandleObjectLocal(dummy_object);
}
void NodeManager::HandleTaskReconstruction(const TaskID &task_id) {
+31 -2
View File
@@ -186,6 +186,10 @@ class NodeManager {
/// \param worker The worker that finished the task.
/// \return Void.
void FinishAssignedTask(Worker &worker);
/// Helper function to produce actor table data for a newly created actor.
///
/// \param task The actor creation task that created the actor.
ActorTableDataT CreateActorTableDataFromCreationTask(const Task &task);
/// Handle a worker finishing an assigned actor task or actor creation task.
/// \param worker The worker that finished the task.
/// \param task The actor task or actor creationt ask.
@@ -282,9 +286,11 @@ class NodeManager {
/// old state transition.
///
/// \param actor_id The actor ID of the actor whose state was updated.
/// \param data Data associated with this notification.
/// \param actor_registration The ActorRegistration object that represents actor's
/// new state.
/// \return Void.
void HandleActorStateTransition(const ActorID &actor_id, const ActorTableDataT &data);
void HandleActorStateTransition(const ActorID &actor_id,
ActorRegistration &&actor_registration);
/// Publish an actor's state transition to all other nodes.
///
@@ -385,6 +391,25 @@ class NodeManager {
/// \return Void.
void ProcessPushErrorRequestMessage(const uint8_t *message_data);
/// Process client message of PrepareActorCheckpointRequest.
///
/// \param client The client that sent the message.
/// \param message_data A pointer to the message data.
void ProcessPrepareActorCheckpointRequest(
const std::shared_ptr<LocalClientConnection> &client, const uint8_t *message_data);
/// Process client message of NotifyActorResumedFromCheckpoint.
///
/// \param message_data A pointer to the message data.
void ProcessNotifyActorResumedFromCheckpoint(const uint8_t *message_data);
/// Update actor frontier when a task finishes.
/// If the task is an actor creation task and the actor was resumed from a checkpoint,
/// restore the frontier from the checkpoint. Otherwise, just extend actor frontier.
///
/// \param task The task that just finished.
void UpdateActorFrontier(const Task &task);
/// Handle the case where an actor is disconnected, determine whether this
/// actor needs to be reconstructed and then update actor table.
/// This function needs to be called either when actor process dies or when
@@ -458,6 +483,10 @@ class NodeManager {
/// A mapping from actor ID to registration information about that actor
/// (including which node manager owns it).
std::unordered_map<ActorID, ActorRegistration> actor_registry_;
/// This map stores actor ID to the ID of the checkpoint that will be used to
/// restore the actor.
std::unordered_map<ActorID, ActorCheckpointID> checkpoint_id_to_restore_;
};
} // namespace raylet
+28
View File
@@ -358,3 +358,31 @@ ray::Status RayletClient::FreeObjects(const std::vector<ray::ObjectID> &object_i
auto status = conn_->WriteMessage(MessageType::FreeObjectsInObjectStoreRequest, &fbb);
return status;
}
ray::Status RayletClient::PrepareActorCheckpoint(const ActorID &actor_id,
ActorCheckpointID &checkpoint_id) {
flatbuffers::FlatBufferBuilder fbb;
auto message =
ray::protocol::CreatePrepareActorCheckpointRequest(fbb, to_flatbuf(fbb, actor_id));
fbb.Finish(message);
std::unique_ptr<uint8_t[]> reply;
auto status =
conn_->AtomicRequestReply(MessageType::PrepareActorCheckpointRequest,
MessageType::PrepareActorCheckpointReply, reply, &fbb);
if (!status.ok()) return status;
auto reply_message =
flatbuffers::GetRoot<ray::protocol::PrepareActorCheckpointReply>(reply.get());
checkpoint_id = ObjectID::from_binary(reply_message->checkpoint_id()->str());
return ray::Status::OK();
}
ray::Status RayletClient::NotifyActorResumedFromCheckpoint(
const ActorID &actor_id, const ActorCheckpointID &checkpoint_id) {
flatbuffers::FlatBufferBuilder fbb;
auto message = ray::protocol::CreateNotifyActorResumedFromCheckpoint(
fbb, to_flatbuf(fbb, actor_id), to_flatbuf(fbb, checkpoint_id));
fbb.Finish(message);
return conn_->WriteMessage(MessageType::NotifyActorResumedFromCheckpoint, &fbb);
}
+17
View File
@@ -10,6 +10,7 @@
#include "ray/status.h"
using ray::ActorID;
using ray::ActorCheckpointID;
using ray::JobID;
using ray::ObjectID;
using ray::TaskID;
@@ -146,6 +147,22 @@ class RayletClient {
/// \return ray::Status.
ray::Status FreeObjects(const std::vector<ray::ObjectID> &object_ids, bool local_only);
/// Request raylet backend to prepare a checkpoint for an actor.
///
/// \param actor_id ID of the actor.
/// \param checkpoint_id ID of the new checkpoint (output parameter).
/// \return ray::Status.
ray::Status PrepareActorCheckpoint(const ActorID &actor_id,
ActorCheckpointID &checkpoint_id);
/// Notify raylet backend that an actor was resumed from a checkpoint.
///
/// \param actor_id ID of the actor.
/// \param checkpoint_id ID of the checkpoint from which the actor was resumed.
/// \return ray::Status.
ray::Status NotifyActorResumedFromCheckpoint(const ActorID &actor_id,
const ActorCheckpointID &checkpoint_id);
Language GetLanguage() const { return language_; }
ClientID GetClientID() const { return client_id_; }
+1 -1
View File
@@ -99,7 +99,7 @@ TaskSpecification::TaskSpecification(
fbb, to_flatbuf(fbb, driver_id), to_flatbuf(fbb, task_id),
to_flatbuf(fbb, parent_task_id), parent_counter, to_flatbuf(fbb, actor_creation_id),
to_flatbuf(fbb, actor_creation_dummy_object_id), max_actor_reconstructions,
to_flatbuf(fbb, actor_id), to_flatbuf(fbb, actor_handle_id), actor_counter, false,
to_flatbuf(fbb, actor_id), to_flatbuf(fbb, actor_handle_id), actor_counter,
object_ids_to_flatbuf(fbb, new_actor_handles), fbb.CreateVector(arguments),
object_ids_to_flatbuf(fbb, returns), map_to_flatbuf(fbb, required_resources),
map_to_flatbuf(fbb, required_placement_resources), language,