Introduce a log interface for the new GCS (#1771)

* TABLE_APPEND call

* Convert callbacks back to taking in a string...

* GCS returns flatbuffers, define Log class

* Cleanups

* Modify client table to use the Log interface

* Fix bug where we replied twice from redis

* Fixes

* lint
This commit is contained in:
Stephanie Wang
2018-03-26 16:00:43 -07:00
committed by Philipp Moritz
parent 7c4afa4b04
commit 0fd4112354
7 changed files with 407 additions and 237 deletions
+2 -1
View File
@@ -420,9 +420,10 @@ TEST_F(TestGcsWithAsio, TestSubscribeCancel) {
TestSubscribeCancel(job_id_, client_);
}
void ClientTableNotification(gcs::AsyncGcsClient *client, const UniqueID &id,
void ClientTableNotification(gcs::AsyncGcsClient *client, const ClientID &client_id,
const ClientTableDataT &data, bool is_insertion) {
ClientID added_id = client->client_table().GetLocalClientId();
ASSERT_EQ(client_id, added_id);
ASSERT_EQ(ClientID::from_binary(data.client_id), added_id);
ASSERT_EQ(data.is_insertion, is_insertion);
+2 -2
View File
@@ -23,9 +23,9 @@ enum TablePubsub:int {
ACTOR
}
table GcsNotification {
table GcsTableEntry {
id: string;
data: string;
entries: [string];
}
table FunctionTableData {
+5 -6
View File
@@ -15,7 +15,7 @@ namespace {
/// A helper function to call the callback and delete it from the callback
/// manager if necessary.
void ProcessCallback(int64_t callback_index, const std::vector<std::string> &data) {
void ProcessCallback(int64_t callback_index, const std::string &data) {
if (callback_index >= 0) {
bool delete_callback =
ray::gcs::RedisCallbackManager::instance().get(callback_index)(data);
@@ -40,14 +40,14 @@ void GlobalRedisCallback(void *c, void *r, void *privdata) {
}
int64_t callback_index = reinterpret_cast<int64_t>(privdata);
redisReply *reply = reinterpret_cast<redisReply *>(r);
std::vector<std::string> data;
std::string data = "";
// Parse the response.
switch (reply->type) {
case (REDIS_REPLY_NIL): {
// Do not add any data for a nil response.
} break;
case (REDIS_REPLY_STRING): {
data.push_back(std::string(reply->str, reply->len));
data = std::string(reply->str, reply->len);
} break;
case (REDIS_REPLY_STATUS): {
} break;
@@ -67,7 +67,7 @@ void SubscribeRedisCallback(void *c, void *r, void *privdata) {
}
int64_t callback_index = reinterpret_cast<int64_t>(privdata);
redisReply *reply = reinterpret_cast<redisReply *>(r);
std::vector<std::string> data;
std::string data = "";
// Parse the response.
switch (reply->type) {
case (REDIS_REPLY_ARRAY): {
@@ -76,13 +76,12 @@ void SubscribeRedisCallback(void *c, void *r, void *privdata) {
if (strcmp(message_type->str, "subscribe") == 0) {
// If the message is for the initial subscription call, return the empty
// string as a response to signify that subscription was successful.
data.push_back("");
} else if (strcmp(message_type->str, "message") == 0) {
// If the message is from a PUBLISH, make sure the data is nonempty.
redisReply *message = reply->element[reply->elements - 1];
auto notification = std::string(message->str, message->len);
RAY_CHECK(!notification.empty()) << "Empty message received on subscribe channel";
data.push_back(notification);
data = notification;
} else {
RAY_LOG(FATAL) << "Fatal redis error during subscribe" << message_type->str;
}
+1 -1
View File
@@ -24,7 +24,7 @@ class RedisCallbackManager {
/// Every callback should take in a vector of the results from the Redis
/// operation and return a bool indicating whether the callback should be
/// deleted once called.
using RedisCallback = std::function<bool(const std::vector<std::string> &)>;
using RedisCallback = std::function<bool(const std::string &)>;
static RedisCallbackManager &instance() {
static RedisCallbackManager instance;
+162 -95
View File
@@ -7,13 +7,124 @@ namespace ray {
namespace gcs {
template <typename ID, typename Data>
Status Log<ID, Data>::Append(const JobID &job_id, const ID &id,
std::shared_ptr<DataT> data, const Callback &done) {
auto d = std::shared_ptr<CallbackData>(
new CallbackData({id, data, done, nullptr, this, client_}));
int64_t callback_index =
RedisCallbackManager::instance().add([d](const std::string &data) {
if (d->callback != nullptr) {
(d->callback)(d->client, d->id, {*d->data});
}
return true;
});
flatbuffers::FlatBufferBuilder fbb;
fbb.ForceDefaults(true);
fbb.Finish(Data::Pack(fbb, data.get()));
return context_->RunAsync("RAY.TABLE_APPEND", id, fbb.GetBufferPointer(), fbb.GetSize(),
prefix_, pubsub_channel_, callback_index);
}
template <typename ID, typename Data>
Status Log<ID, Data>::Lookup(const JobID &job_id, const ID &id, const Callback &lookup) {
auto d = std::shared_ptr<CallbackData>(
new CallbackData({id, nullptr, lookup, nullptr, this, client_}));
int64_t callback_index =
RedisCallbackManager::instance().add([d](const std::string &data) {
if (d->callback != nullptr) {
std::vector<DataT> results;
if (!data.empty()) {
auto root = flatbuffers::GetRoot<GcsTableEntry>(data.data());
RAY_CHECK(from_flatbuf(*root->id()) == d->id);
for (size_t i = 0; i < root->entries()->size(); i++) {
DataT result;
auto data_root =
flatbuffers::GetRoot<Data>(root->entries()->Get(i)->data());
data_root->UnPackTo(&result);
results.emplace_back(std::move(result));
}
}
(d->callback)(d->client, d->id, results);
}
return true;
});
std::vector<uint8_t> nil;
return context_->RunAsync("RAY.TABLE_LOOKUP", id, nil.data(), nil.size(), prefix_,
pubsub_channel_, callback_index);
}
template <typename ID, typename Data>
Status Log<ID, Data>::Subscribe(const JobID &job_id, const ClientID &client_id,
const Callback &subscribe,
const SubscriptionCallback &done) {
RAY_CHECK(subscribe_callback_index_ == -1)
<< "Client called Subscribe twice on the same table";
auto d = std::shared_ptr<CallbackData>(
new CallbackData({client_id, nullptr, subscribe, done, this, client_}));
int64_t callback_index = RedisCallbackManager::instance().add(
[this, d](const std::string &data) {
if (data.empty()) {
// No notification data is provided. This is the callback for the
// initial subscription request.
if (d->subscription_callback != nullptr) {
(d->subscription_callback)(d->client);
}
} else {
// Data is provided. This is the callback for a message.
if (d->callback != nullptr) {
// Parse the notification.
auto root = flatbuffers::GetRoot<GcsTableEntry>(data.data());
ID id = UniqueID::nil();
if (root->id()->size() > 0) {
id = from_flatbuf(*root->id());
}
std::vector<DataT> results;
for (size_t i = 0; i < root->entries()->size(); i++) {
DataT result;
auto data_root =
flatbuffers::GetRoot<Data>(root->entries()->Get(i)->data());
data_root->UnPackTo(&result);
results.emplace_back(std::move(result));
}
(d->callback)(d->client, id, results);
}
}
// We do not delete the callback after calling it since there may be
// more subscription messages.
return false;
});
subscribe_callback_index_ = callback_index;
return context_->SubscribeAsync(client_id, pubsub_channel_, callback_index);
}
template <typename ID, typename Data>
Status Log<ID, Data>::RequestNotifications(const JobID &job_id, const ID &id,
const ClientID &client_id) {
RAY_CHECK(subscribe_callback_index_ >= 0)
<< "Client requested notifications on a key before Subscribe completed";
return context_->RunAsync("RAY.TABLE_REQUEST_NOTIFICATIONS", id, client_id.data(),
client_id.size(), prefix_, pubsub_channel_,
/*callback_index=*/-1);
}
template <typename ID, typename Data>
Status Log<ID, Data>::CancelNotifications(const JobID &job_id, const ID &id,
const ClientID &client_id) {
RAY_CHECK(subscribe_callback_index_ >= 0)
<< "Client canceled notifications on a key before Subscribe completed";
return context_->RunAsync("RAY.TABLE_CANCEL_NOTIFICATIONS", id, client_id.data(),
client_id.size(), prefix_, pubsub_channel_,
/*callback_index=*/-1);
}
template <typename ID, typename Data>
Status Table<ID, Data>::Add(const JobID &job_id, const ID &id,
std::shared_ptr<DataT> data, const Callback &done) {
auto d = std::shared_ptr<CallbackData>(
new CallbackData({id, data, done, nullptr, nullptr, this, client_}));
new CallbackData({id, data, done, nullptr, this, client_}));
int64_t callback_index =
RedisCallbackManager::instance().add([d](const std::vector<std::string> &data) {
RedisCallbackManager::instance().add([d](const std::string &data) {
if (d->callback != nullptr) {
(d->callback)(d->client, d->id, *d->data);
}
@@ -29,88 +140,33 @@ Status Table<ID, Data>::Add(const JobID &job_id, const ID &id,
template <typename ID, typename Data>
Status Table<ID, Data>::Lookup(const JobID &job_id, const ID &id, const Callback &lookup,
const FailureCallback &failure) {
auto d = std::shared_ptr<CallbackData>(
new CallbackData({id, nullptr, lookup, failure, nullptr, this, client_}));
int64_t callback_index =
RedisCallbackManager::instance().add([d](const std::vector<std::string> &data) {
if (data.empty()) {
if (d->failure != nullptr) {
(d->failure)(d->client, d->id);
}
} else {
RAY_CHECK(data.size() == 1);
if (d->callback != nullptr) {
DataT result;
auto root = flatbuffers::GetRoot<Data>(data[0].data());
root->UnPackTo(&result);
(d->callback)(d->client, d->id, result);
}
}
return true;
});
std::vector<uint8_t> nil;
return context_->RunAsync("RAY.TABLE_LOOKUP", id, nil.data(), nil.size(), prefix_,
pubsub_channel_, callback_index);
return Log<ID, Data>::Lookup(job_id, id,
[lookup, failure](AsyncGcsClient *client, const ID &id,
const std::vector<DataT> &data) {
if (data.empty()) {
if (failure != nullptr) {
(failure)(client, id);
}
} else {
RAY_CHECK(data.size() == 1);
if (lookup != nullptr) {
(lookup)(client, id, data[0]);
}
}
});
}
template <typename ID, typename Data>
Status Table<ID, Data>::Subscribe(const JobID &job_id, const ClientID &client_id,
const Callback &subscribe,
const SubscriptionCallback &done) {
RAY_CHECK(subscribe_callback_index_ == -1)
<< "Client called Subscribe twice on the same table";
auto d = std::shared_ptr<CallbackData>(
new CallbackData({client_id, nullptr, subscribe, nullptr, done, this, client_}));
int64_t callback_index = RedisCallbackManager::instance().add(
[this, d](const std::vector<std::string> &data) {
if (data.size() == 1 && data[0] == "") {
// No notification data is provided. This is the callback for the
// initial subscription request.
if (d->subscription_callback != nullptr) {
(d->subscription_callback)(d->client);
}
} else {
// Data is provided. This is the callback for a message.
RAY_CHECK(data.size() == 1);
if (d->callback != nullptr) {
// Parse the notification.
auto notification = flatbuffers::GetRoot<GcsNotification>(data[0].data());
ID id = UniqueID::nil();
if (notification->id()->size() > 0) {
id = from_flatbuf(*notification->id());
}
DataT result;
auto root = flatbuffers::GetRoot<Data>(notification->data()->data());
root->UnPackTo(&result);
(d->callback)(d->client, id, result);
}
}
// We do not delete the callback after calling it since there may be
// more subscription messages.
return false;
});
subscribe_callback_index_ = callback_index;
return context_->SubscribeAsync(client_id, pubsub_channel_, callback_index);
}
template <typename ID, typename Data>
Status Table<ID, Data>::RequestNotifications(const JobID &job_id, const ID &id,
const ClientID &client_id) {
RAY_CHECK(subscribe_callback_index_ >= 0)
<< "Client requested notifications on a key before Subscribe completed";
return context_->RunAsync("RAY.TABLE_REQUEST_NOTIFICATIONS", id, client_id.data(),
client_id.size(), prefix_, pubsub_channel_,
subscribe_callback_index_);
}
template <typename ID, typename Data>
Status Table<ID, Data>::CancelNotifications(const JobID &job_id, const ID &id,
const ClientID &client_id) {
RAY_CHECK(subscribe_callback_index_ >= 0)
<< "Client canceled notifications on a key before Subscribe completed";
return context_->RunAsync("RAY.TABLE_CANCEL_NOTIFICATIONS", id, client_id.data(),
client_id.size(), prefix_, pubsub_channel_,
/*callback_index=*/-1);
return Log<ID, Data>::Subscribe(
job_id, client_id,
[subscribe](AsyncGcsClient *client, const ID &id, const std::vector<DataT> &data) {
RAY_CHECK(data.size() == 1);
subscribe(client, id, data[0]);
},
done);
}
void ClientTable::RegisterClientAddedCallback(const ClientTableCallback &callback) {
@@ -118,7 +174,7 @@ void ClientTable::RegisterClientAddedCallback(const ClientTableCallback &callbac
// Call the callback for any added clients that are cached.
for (const auto &entry : client_cache_) {
if (!entry.first.is_nil() && entry.second.is_insertion) {
client_added_callback_(client_, ClientID::nil(), entry.second);
client_added_callback_(client_, entry.first, entry.second);
}
}
}
@@ -128,12 +184,12 @@ void ClientTable::RegisterClientRemovedCallback(const ClientTableCallback &callb
// Call the callback for any removed clients that are cached.
for (const auto &entry : client_cache_) {
if (!entry.first.is_nil() && !entry.second.is_insertion) {
client_removed_callback_(client_, ClientID::nil(), entry.second);
client_removed_callback_(client_, entry.first, entry.second);
}
}
}
void ClientTable::HandleNotification(AsyncGcsClient *client, const ClientID &channel_id,
void ClientTable::HandleNotification(AsyncGcsClient *client,
const ClientTableDataT &data) {
ClientID client_id = ClientID::from_binary(data.client_id);
// It's possible to get duplicate notifications from the client table, so
@@ -176,9 +232,10 @@ void ClientTable::HandleNotification(AsyncGcsClient *client, const ClientID &cha
}
}
void ClientTable::HandleConnected(AsyncGcsClient *client, const ClientID &client_id,
const ClientTableDataT &data) {
RAY_CHECK(client_id == client_id_) << client_id.hex() << " " << client_id_.hex();
void ClientTable::HandleConnected(AsyncGcsClient *client, const ClientTableDataT &data) {
auto connected_client_id = ClientID::from_binary(data.client_id);
RAY_CHECK(client_id_ == connected_client_id) << connected_client_id.hex() << " "
<< client_id_.hex();
}
const ClientID &ClientTable::GetLocalClientId() { return client_id_; }
@@ -191,15 +248,21 @@ Status ClientTable::Connect() {
auto data = std::make_shared<ClientTableDataT>(local_client_);
data->is_insertion = true;
// Callback for a notification from the client table.
auto notification_callback = [this](AsyncGcsClient *client, const ClientID &channel_id,
const ClientTableDataT &data) {
return HandleNotification(client, channel_id, data);
auto notification_callback = [this](
AsyncGcsClient *client, const UniqueID &log_key,
const std::vector<ClientTableDataT> &notifications) {
RAY_CHECK(log_key == client_log_key_);
for (auto &notification : notifications) {
HandleNotification(client, notification);
}
};
// Callback to handle our own successful connection once we've added
// ourselves.
auto add_callback = [this](AsyncGcsClient *client, const ClientID &id,
const ClientTableDataT &data) {
HandleConnected(client, id, data);
auto add_callback = [this](AsyncGcsClient *client, const UniqueID &log_key,
const std::vector<ClientTableDataT> &data) {
RAY_CHECK(log_key == client_log_key_);
RAY_CHECK(data.size() == 1);
HandleConnected(client, data[0]);
};
// Callback to add ourselves once we've successfully subscribed.
auto subscription_callback = [this, data, add_callback](AsyncGcsClient *c) {
@@ -208,9 +271,10 @@ Status ClientTable::Connect() {
if (disconnected_) {
data->is_insertion = false;
}
return Add(JobID::nil(), client_id_, data, add_callback);
RAY_CHECK_OK(RequestNotifications(JobID::nil(), client_log_key_, client_id_));
RAY_CHECK_OK(Append(JobID::nil(), client_log_key_, data, add_callback));
};
return Subscribe(JobID::nil(), ClientID::nil(), notification_callback,
return Subscribe(JobID::nil(), client_id_, notification_callback,
subscription_callback);
}
@@ -218,10 +282,12 @@ Status ClientTable::Disconnect() {
auto data = std::make_shared<ClientTableDataT>(local_client_);
data->is_insertion = true;
auto add_callback = [this](AsyncGcsClient *client, const ClientID &id,
const ClientTableDataT &data) {
HandleConnected(client, id, data);
const std::vector<ClientTableDataT> &data) {
RAY_CHECK(data.size() == 1);
HandleConnected(client, data[0]);
RAY_CHECK_OK(CancelNotifications(JobID::nil(), client_log_key_, id));
};
RAY_RETURN_NOT_OK(Add(JobID::nil(), client_id_, data, add_callback));
RAY_RETURN_NOT_OK(Append(JobID::nil(), client_log_key_, data, add_callback));
// We successfully added the deletion entry. Mark ourselves as disconnected.
disconnected_ = true;
return Status::OK();
@@ -239,6 +305,7 @@ const ClientTableDataT &ClientTable::GetClient(const ClientID &client_id) {
}
}
template class Log<ObjectID, ObjectTableData>;
template class Table<TaskID, ray::protocol::Task>;
template class Table<TaskID, TaskTableData>;
template class Table<ObjectID, ObjectTableData>;
+114 -30
View File
@@ -27,15 +27,20 @@ class RedisContext;
class AsyncGcsClient;
/// \class Log
///
/// A GCS table where every entry is an append-only log.
/// Example tables backed by Log:
/// ObjectTable: Stores a log of which clients have added or evicted an
/// object.
/// ClientTable: Stores a log of which GCS clients have been added or deleted
/// from the system.
template <typename ID, typename Data>
class Table {
class Log {
public:
using DataT = typename Data::NativeTableType;
using Callback =
std::function<void(AsyncGcsClient *client, const ID &id, const DataT &data)>;
/// The callback to call when a lookup fails because there is no entry at the
/// key.
using FailureCallback = std::function<void(AsyncGcsClient *client, const ID &id)>;
using Callback = std::function<void(AsyncGcsClient *client, const ID &id,
const std::vector<DataT> &data)>;
/// The callback to call when a SUBSCRIBE call completes and we are ready to
/// request and receive notifications.
using SubscriptionCallback = std::function<void(AsyncGcsClient *client)>;
@@ -44,45 +49,44 @@ class Table {
ID id;
std::shared_ptr<DataT> data;
Callback callback;
FailureCallback failure;
// An optional callback to call for subscription operations, where the
// first message is a notification of subscription success.
SubscriptionCallback subscription_callback;
Table<ID, Data> *table;
Log<ID, Data> *log;
AsyncGcsClient *client;
};
Table(const std::shared_ptr<RedisContext> &context, AsyncGcsClient *client)
Log(const std::shared_ptr<RedisContext> &context, AsyncGcsClient *client)
: context_(context),
client_(client),
pubsub_channel_(TablePubsub_NO_PUBLISH),
prefix_(TablePrefix_UNUSED),
subscribe_callback_index_(-1){};
/// Add an entry to the table.
/// Append a log entry to a key.
///
/// \param job_id The ID of the job (= driver).
/// \param id The ID of the data that is added to the GCS.
/// \param data Data that is added to the GCS.
/// \param data Data to append to the log.
/// \param done Callback that is called once the data has been written to the
/// GCS.
/// \return Status
Status Add(const JobID &job_id, const ID &id, std::shared_ptr<DataT> data,
const Callback &done);
Status Append(const JobID &job_id, const ID &id, std::shared_ptr<DataT> data,
const Callback &done);
/// Lookup an entry asynchronously.
/// Lookup the log values at a key asynchronously.
///
/// \param job_id The ID of the job (= driver).
/// \param id The ID of the data that is looked up in the GCS.
/// \param lookup Callback that is called after lookup. If the callback is
/// called with an empty vector, then there was no data at the key.
/// \return Status
Status Lookup(const JobID &job_id, const ID &id, const Callback &lookup,
const FailureCallback &failure);
Status Lookup(const JobID &job_id, const ID &id, const Callback &lookup);
/// Subscribe to any Add operations to this table. The caller may choose to
/// subscribe to all Adds, or to subscribe only to keys that it requests
/// notifications for. This may only be called once per Table instance.
/// Subscribe to any Append operations to this table. The caller may choose
/// to subscribe to all Appends, or to subscribe only to keys that it
/// requests notifications for. This may only be called once per Log
/// instance.
///
/// \param job_id The ID of the job (= driver).
/// \param client_id The type of update to listen to. If this is nil, then a
@@ -103,8 +107,8 @@ class Table {
///
/// The notifications will be returned via the subscribe callback that was
/// registered by `Subscribe`. An initial notification will be returned for
/// the current value(s) at the key, if any, and a subsequent notification
/// will be published for every following `Add` to the key. Before
/// the current values at the key, if any, and a subsequent notification will
/// be published for every following `Append` to the key. Before
/// notifications can be requested, the caller must first call `Subscribe`,
/// with the same `client_id`.
///
@@ -143,6 +147,73 @@ class Table {
int64_t subscribe_callback_index_;
};
/// \class Table
///
/// A GCS table where every entry is a single data item.
/// Example tables backed by Log:
/// TaskTable: Stores Task metadata needed for executing the task.
template <typename ID, typename Data>
class Table : private Log<ID, Data> {
public:
using DataT = typename Log<ID, Data>::DataT;
using Callback =
std::function<void(AsyncGcsClient *client, const ID &id, const DataT &data)>;
/// The callback to call when a Lookup call returns an empty entry.
using FailureCallback = std::function<void(AsyncGcsClient *client, const ID &id)>;
/// The callback to call when a Subscribe call completes and we are ready to
/// request and receive notifications.
using SubscriptionCallback = typename Log<ID, Data>::SubscriptionCallback;
struct CallbackData {
ID id;
std::shared_ptr<DataT> data;
Callback callback;
// An optional callback to call for subscription operations, where the
// first message is a notification of subscription success.
SubscriptionCallback subscription_callback;
Log<ID, Data> *log;
AsyncGcsClient *client;
};
Table(const std::shared_ptr<RedisContext> &context, AsyncGcsClient *client)
: Log<ID, Data>(context, client) {}
using Log<ID, Data>::RequestNotifications;
using Log<ID, Data>::CancelNotifications;
/// Add an entry to the table. This overwrites any existing data at the key.
///
/// \param job_id The ID of the job (= driver).
/// \param id The ID of the data that is added to the GCS.
/// \param data Data that is added to the GCS.
/// \param done Callback that is called once the data has been written to the
/// GCS.
/// \return Status
Status Add(const JobID &job_id, const ID &id, std::shared_ptr<DataT> data,
const Callback &done);
/// Lookup an entry asynchronously.
///
/// \param job_id The ID of the job (= driver).
/// \param id The ID of the data that is looked up in the GCS.
/// \param lookup Callback that is called after lookup if there was data the
/// key.
/// \param failure Callback that is called after lookup if there was no data
/// at the key.
/// \return Status
Status Lookup(const JobID &job_id, const ID &id, const Callback &lookup,
const FailureCallback &failure);
Status Subscribe(const JobID &job_id, const ClientID &client_id,
const Callback &subscribe, const SubscriptionCallback &done);
protected:
using Log<ID, Data>::context_;
using Log<ID, Data>::client_;
using Log<ID, Data>::pubsub_channel_;
using Log<ID, Data>::prefix_;
};
class ObjectTable : public Table<ObjectID, ObjectTableData> {
public:
ObjectTable(const std::shared_ptr<RedisContext> &context, AsyncGcsClient *client)
@@ -210,10 +281,9 @@ class TaskTable : public Table<TaskID, TaskTableData> {
std::shared_ptr<TaskTableTestAndUpdateT> data,
const TestAndUpdateCallback &callback) {
int64_t callback_index = RedisCallbackManager::instance().add(
[this, callback, id](const std::vector<std::string> &data) {
RAY_CHECK(data.size() == 1);
[this, callback, id](const std::string &data) {
auto result = std::make_shared<TaskTableDataT>();
auto root = flatbuffers::GetRoot<TaskTableData>(data[0].data());
auto root = flatbuffers::GetRoot<TaskTableData>(data.data());
root->UnPackTo(result.get());
callback(client_, id, *result, root->updated());
return true;
@@ -263,13 +333,25 @@ Status TaskTableTestAndUpdate(AsyncGcsClient *gcs_client, const TaskID &task_id,
SchedulingState update_state,
const TaskTable::TestAndUpdateCallback &callback);
class ClientTable : private Table<ClientID, ClientTableData> {
/// \class ClientTable
///
/// The ClientTable stores information about active and inactive clients. It is
/// structured as a single log stored at a key known to all clients. When a
/// client connects, it appends an entry to the log indicating that it is
/// alive. When a client disconnects, or if another client detects its failure,
/// it should append an entry to the log indicating that it is dead. A client
/// that is marked as dead should never again be marked as alive; if it needs
/// to reconnect, it must connect with a different ClientID.
class ClientTable : private Log<UniqueID, ClientTableData> {
public:
using ClientTableCallback = std::function<void(
AsyncGcsClient *client, const ClientID &id, const ClientTableDataT &data)>;
ClientTable(const std::shared_ptr<RedisContext> &context, AsyncGcsClient *client,
const ClientTableDataT &local_client)
: Table(context, client),
: Log(context, client),
// We set the client log's key equal to nil so that all instances of
// ClientTable have the same key.
client_log_key_(UniqueID::nil()),
disconnected_(false),
client_id_(ClientID::from_binary(local_client.client_id)),
local_client_(local_client) {
@@ -325,12 +407,14 @@ class ClientTable : private Table<ClientID, ClientTableData> {
private:
/// Handle a client table notification.
void HandleNotification(AsyncGcsClient *client, const ClientID &channel_id,
const ClientTableDataT &notifications);
void HandleNotification(AsyncGcsClient *client, const ClientTableDataT &notifications);
/// Handle this client's successful connection to the GCS.
void HandleConnected(AsyncGcsClient *client, const ClientID &client_id,
const ClientTableDataT &notifications);
void HandleConnected(AsyncGcsClient *client, const ClientTableDataT &notifications);
/// The key at which the log of client information is stored. This key must
/// be kept the same across all instances of the ClientTable, so that all
/// clients append and read from the same key.
UniqueID client_log_key_;
/// Whether this client has called Disconnect().
bool disconnected_;
/// This client's ID.