Merge task table and task log into a single table (#30)

* Merge task table and task log

* Fix test in db tests

* Address Robert's comments and some better error checking

* Add a LOG_FATAL that exits the program
This commit is contained in:
Stephanie Wang
2016-11-10 18:13:26 -08:00
committed by Philipp Moritz
parent 194bdb1d96
commit 9d1e750e8f
30 changed files with 1578 additions and 842 deletions
+21
View File
@@ -36,3 +36,24 @@ void object_table_subscribe(
init_table_callback(db_handle, object_id, sub_data, retry, done_callback,
redis_object_table_subscribe, user_context);
}
void result_table_add(db_handle *db_handle,
object_id object_id,
task_id task_id_arg,
retry_info *retry,
result_table_done_callback done_callback,
void *user_context) {
task_id *task_id_copy = malloc(sizeof(task_id));
memcpy(task_id_copy, task_id_arg.id, sizeof(task_id));
init_table_callback(db_handle, object_id, task_id_copy, retry, done_callback,
redis_result_table_add, user_context);
}
void result_table_lookup(db_handle *db_handle,
object_id object_id,
retry_info *retry,
result_table_lookup_callback done_callback,
void *user_context) {
init_table_callback(db_handle, object_id, NULL, retry, done_callback,
redis_result_table_lookup, user_context);
}
+52
View File
@@ -4,6 +4,7 @@
#include "common.h"
#include "table.h"
#include "db.h"
#include "task.h"
/*
* ==== Lookup call and callback ====
@@ -123,4 +124,55 @@ typedef struct {
void *subscribe_context;
} object_table_subscribe_data;
/*
* ==== Result table ====
*/
/**
* Callback called when the add/remove operation for a result table entry
* completes. */
typedef void (*result_table_done_callback)(object_id object_id,
void *user_context);
/**
* Add information about a new object to the object table. This
* is immutable information like the ID of the task that
* created the object.
*
* @param db_handle Handle to object_table database.
* @param object_id ID of the object to add.
* @param task_id ID of the task that creates this object.
* @param retry Information about retrying the request to the database.
* @param done_callback Function to be called when database returns result.
* @param user_context Context passed by the caller.
* @return Void.
*/
void result_table_add(db_handle *db_handle,
object_id object_id,
task_id task_id,
retry_info *retry,
result_table_done_callback done_callback,
void *user_context);
/** Callback called when the result table lookup completes. */
typedef void (*result_table_lookup_callback)(object_id object_id,
task *task,
void *user_context);
/**
* Lookup the task that created an object in the result table.
*
* @param db_handle Handle to object_table database.
* @param object_id ID of the object to lookup.
* @param retry Information about retrying the request to the database.
* @param done_callback Function to be called when database returns result.
* @param user_context Context passed by the caller.
* @return Void.
*/
void result_table_lookup(db_handle *db_handle,
object_id object_id,
retry_info *retry,
result_table_lookup_callback done_callback,
void *user_context);
#endif /* OBJECT_TABLE_H */
+296 -84
View File
@@ -11,21 +11,19 @@
#include "db.h"
#include "object_table.h"
#include "task.h"
#include "task_log.h"
#include "task_table.h"
#include "event_loop.h"
#include "redis.h"
#include "io.h"
#define LOG_REDIS_ERR(context, M, ...) \
fprintf(stderr, "[ERROR] (%s:%d: message: %s) " M "\n", __FILE__, __LINE__, \
context->errstr, ##__VA_ARGS__)
#define LOG_REDIS_ERR(context, M, ...) \
LOG_INFO("Redis error %d %s; %s", context->err, context->errstr, M)
#define CHECK_REDIS_CONNECT(CONTEXT_TYPE, context, M, ...) \
do { \
CONTEXT_TYPE *_context = (context); \
if (!_context) { \
LOG_ERR("could not allocate redis context"); \
exit(-1); \
LOG_FATAL("could not allocate redis context"); \
} \
if (_context->err) { \
LOG_REDIS_ERR(_context, M, ##__VA_ARGS__); \
@@ -123,6 +121,62 @@ void db_attach(db_handle *db, event_loop *loop) {
redisAeAttach(loop, db->sub_context);
}
/**
* An internal function to allocate a task object and parse a hashmap reply
* from Redis into the task object. If the Redis reply is malformed, an empty
* task with the given task ID is returned.
*
* @param id The ID of the task we're looking up. If the reply from Redis is
* well-formed, the reply's ID should match this ID. Else, the returned
* task will have its ID set to this ID.
* @param num_redis_replies The number of keys and values in the Redis hashmap.
* @param redis_replies A pointer to the Redis hashmap keys and values.
* @return A pointer to the parsed task.
*/
task *parse_redis_task_table_entry(task_id id,
int num_redis_replies,
redisReply **redis_replies) {
task *task_result;
if (num_redis_replies == 0) {
/* There was no information about this task. */
return NULL;
}
/* Exit immediately if there weren't 6 fields, one for each key-value pair.
* The keys are "node", "state", and "task_spec". */
DCHECK(num_redis_replies == 6);
/* Parse the task struct's fields. */
scheduling_state state = 0;
node_id node = NIL_ID;
task_spec *spec = NULL;
for (int i = 0; i < num_redis_replies; i = i + 2) {
char *key = redis_replies[i]->str;
redisReply *value = redis_replies[i + 1];
if (strcmp(key, "node") == 0) {
memcpy(&node, value->str, value->len);
} else if (strcmp(key, "state") == 0) {
int scanned = sscanf(value->str, "%d", (int *) &state);
if (scanned != 1) {
LOG_FATAL("Scheduling state for task is malformed");
state = 0;
}
} else if (strcmp(key, "task_spec") == 0) {
spec = malloc(value->len);
memcpy(spec, value->str, value->len);
} else {
LOG_FATAL("Found unexpected %s field in task log", key);
}
}
/* Exit immediately if we couldn't parse the task spec. */
if (spec == NULL) {
LOG_FATAL("Could not parse task spec from task log");
}
/* Build and return the task. */
DCHECK(task_ids_equal(task_spec_id(spec), id));
task_result = alloc_task(spec, state, node);
free_task_spec(spec);
return task_result;
}
/*
* ==== object_table callbacks ====
*/
@@ -133,7 +187,7 @@ void redis_object_table_add_callback(redisAsyncContext *c,
REDIS_CALLBACK_HEADER(db, callback_data, r)
if (callback_data->done_callback) {
task_log_done_callback done_callback = callback_data->done_callback;
task_table_done_callback done_callback = callback_data->done_callback;
done_callback(callback_data->id, callback_data->user_context);
}
destroy_timer_callback(db->loop, callback_data);
@@ -142,10 +196,12 @@ void redis_object_table_add_callback(redisAsyncContext *c,
void redis_object_table_add(table_callback_data *callback_data) {
CHECK(callback_data);
db_handle *db = callback_data->db_handle;
redisAsyncCommand(db->context, redis_object_table_add_callback,
(void *) callback_data->timer_id, "SADD obj:%b %d",
&callback_data->id.id[0], UNIQUE_ID_SIZE, db->client_id);
if (db->context->err) {
object_id id = callback_data->id;
int status =
redisAsyncCommand(db->context, redis_object_table_add_callback,
(void *) callback_data->timer_id, "SADD obj:%b %d",
id.id, sizeof(object_id), db->client_id);
if ((status == REDIS_ERR) || db->context->err) {
LOG_REDIS_ERR(db->context, "could not add object_table entry");
}
}
@@ -155,14 +211,117 @@ void redis_object_table_lookup(table_callback_data *callback_data) {
db_handle *db = callback_data->db_handle;
/* Call redis asynchronously */
redisAsyncCommand(db->context, redis_object_table_get_entry,
(void *) callback_data->timer_id, "SMEMBERS obj:%b",
&callback_data->id.id[0], UNIQUE_ID_SIZE);
if (db->context->err) {
object_id id = callback_data->id;
int status = redisAsyncCommand(db->context, redis_object_table_get_entry,
(void *) callback_data->timer_id,
"SMEMBERS obj:%b", id.id, sizeof(object_id));
if ((status == REDIS_ERR) || db->context->err) {
LOG_REDIS_ERR(db->context, "error in object_table lookup");
}
}
void redis_result_table_add_callback(redisAsyncContext *c,
void *r,
void *privdata) {
REDIS_CALLBACK_HEADER(db, callback_data, r)
redisReply *reply = r;
CHECK(reply->type == REDIS_REPLY_STATUS ||
reply->type == REDIS_REPLY_INTEGER);
if (callback_data->done_callback) {
result_table_done_callback done_callback = callback_data->done_callback;
done_callback(callback_data->id, callback_data->user_context);
}
task_id *task_id = callback_data->data;
free(task_id);
destroy_timer_callback(db->loop, callback_data);
}
void redis_result_table_add(table_callback_data *callback_data) {
CHECK(callback_data);
db_handle *db = callback_data->db_handle;
object_id id = callback_data->id;
task_id *result_task_id = (task_id *) callback_data->data;
/* Add the result entry to the result table. */
int status = redisAsyncCommand(db->context, redis_result_table_add_callback,
(void *) callback_data->timer_id,
"SET result:%b %b", id.id, sizeof(object_id),
(*result_task_id).id, sizeof(task_id));
if ((status == REDIS_ERR) || db->context->err) {
LOG_REDIS_ERR(db->context, "Error in result table add");
}
}
void redis_result_table_lookup_task_callback(redisAsyncContext *c,
void *r,
void *privdata) {
REDIS_CALLBACK_HEADER(db, callback_data, r)
redisReply *reply = r;
/* Check that we received a Redis hashmap. */
if (reply->type != REDIS_REPLY_ARRAY) {
LOG_FATAL("Expected Redis array, received type %d %s", reply->type,
reply->str);
}
/* If the user registered a success callback, construct the task object from
* the Redis reply and call the callback. */
result_table_lookup_callback done_callback = callback_data->done_callback;
task_id *result_task_id = callback_data->data;
if (done_callback) {
task *task_reply = parse_redis_task_table_entry(
*result_task_id, reply->elements, reply->element);
done_callback(callback_data->id, task_reply, callback_data->user_context);
free_task(task_reply);
}
free(result_task_id);
destroy_timer_callback(db->loop, callback_data);
}
void redis_result_table_lookup_object_callback(redisAsyncContext *c,
void *r,
void *privdata) {
REDIS_CALLBACK_HEADER(db, callback_data, r)
redisReply *reply = r;
if (reply->type == REDIS_REPLY_STRING) {
/* If we found the object, get the spec of the task that created it. */
DCHECK(reply->len == sizeof(task_id));
task_id *result_task_id = malloc(sizeof(task_id));
memcpy(result_task_id, reply->str, reply->len);
callback_data->data = (void *) result_task_id;
int status =
redisAsyncCommand(db->context, redis_result_table_lookup_task_callback,
(void *) callback_data->timer_id, "HGETALL task:%b",
(*result_task_id).id, sizeof(task_id));
if ((status == REDIS_ERR) || db->context->err) {
LOG_REDIS_ERR(db->context, "Could not look up result table entry");
}
} else if (reply->type == REDIS_REPLY_NIL) {
/* The object with the requested ID was not in the table. */
LOG_ERR("Object's result not in table.");
result_table_lookup_callback done_callback = callback_data->done_callback;
if (done_callback) {
done_callback(callback_data->id, NULL, callback_data->user_context);
}
destroy_timer_callback(db->loop, callback_data);
return;
} else {
LOG_FATAL("expected string or nil, received type %d", reply->type);
}
}
void redis_result_table_lookup(table_callback_data *callback_data) {
CHECK(callback_data);
db_handle *db = callback_data->db_handle;
/* First, lookup the ID of the task that created this object. */
object_id id = callback_data->id;
int status =
redisAsyncCommand(db->context, redis_result_table_lookup_object_callback,
(void *) callback_data->timer_id, "GET result:%b",
id.id, sizeof(object_id));
if ((status == REDIS_ERR) || db->context->err) {
LOG_REDIS_ERR(db->context, "Error in result table lookup");
}
}
/**
* Get an entry from the plasma manager table in redis.
*
@@ -213,8 +372,7 @@ void redis_object_table_get_entry(redisAsyncContext *c,
destroy_timer_callback(callback_data->db_handle->loop, callback_data);
free(managers);
} else {
LOG_ERR("expected integer or string, received type %d", reply->type);
exit(-1);
LOG_FATAL("expected integer or string, received type %d", reply->type);
}
}
@@ -249,28 +407,64 @@ void redis_object_table_subscribe(table_callback_data *callback_data) {
db_handle *db = callback_data->db_handle;
/* subscribe to key notification associated to object id */
redisAsyncCommand(db->sub_context, object_table_redis_callback,
(void *) callback_data->timer_id,
"SUBSCRIBE __keyspace@0__:%b add",
(char *) &callback_data->id.id[0], UNIQUE_ID_SIZE);
if (db->sub_context->err) {
object_id id = callback_data->id;
int status = redisAsyncCommand(db->sub_context, object_table_redis_callback,
(void *) callback_data->timer_id,
"SUBSCRIBE __keyspace@0__:%b add", id.id,
sizeof(object_id));
if ((status == REDIS_ERR) || db->sub_context->err) {
LOG_REDIS_ERR(db->sub_context,
"error in redis_object_table_subscribe_callback");
}
}
/*
* ==== task_log callbacks ====
* ==== task_table callbacks ====
*/
void redis_task_log_publish(table_callback_data *callback_data) {
void redis_task_table_get_task_callback(redisAsyncContext *c,
void *r,
void *privdata) {
REDIS_CALLBACK_HEADER(db, callback_data, r)
redisReply *reply = r;
/* Check that we received a Redis hashmap. */
if (reply->type != REDIS_REPLY_ARRAY) {
LOG_FATAL("Expected Redis array, received type %d %s", reply->type,
reply->str);
}
/* If the user registered a success callback, construct the task object from
* the Redis reply and call the callback. */
if (callback_data->done_callback) {
task_table_get_callback done_callback = callback_data->done_callback;
task *task_reply = parse_redis_task_table_entry(
callback_data->id, reply->elements, reply->element);
done_callback(task_reply, callback_data->user_context);
free_task(task_reply);
}
destroy_timer_callback(db->loop, callback_data);
}
void redis_task_table_get_task(table_callback_data *callback_data) {
CHECK(callback_data);
db_handle *db = callback_data->db_handle;
task_instance *task_instance = callback_data->data;
task_iid task_iid = *task_instance_id(task_instance);
node_id node = *task_instance_node(task_instance);
int32_t state = *task_instance_state(task_instance);
task_id id = callback_data->id;
int status =
redisAsyncCommand(db->context, redis_task_table_get_task_callback,
(void *) callback_data->timer_id, "HGETALL task:%b",
id.id, sizeof(task_id));
if ((status == REDIS_ERR) || db->sub_context->err) {
LOG_REDIS_ERR(db->sub_context, "Could not get task from task table");
}
}
void redis_task_table_publish(table_callback_data *callback_data,
bool task_added) {
db_handle *db = callback_data->db_handle;
task *task = callback_data->data;
task_id id = task_task_id(task);
node_id node = task_node(task);
scheduling_state state = task_state(task);
task_spec *spec = task_task_spec(task);
LOG_DEBUG("Called log_publish callback");
@@ -294,84 +488,98 @@ void redis_task_log_publish(table_callback_data *callback_data) {
}
if (((bool *) callback_data->requests_info)[PUSH_INDEX] == false) {
if (*task_instance_state(task_instance) == TASK_STATUS_WAITING) {
redisAsyncCommand(db->context, redis_task_log_publish_push_callback,
(void *) callback_data->timer_id, "RPUSH tasklog:%b %b",
(char *) &task_iid.id[0], UNIQUE_ID_SIZE,
(char *) task_instance,
task_instance_size(task_instance));
/* If the task has already been added to the task table, only update the
* scheduling information fields. */
int status = REDIS_OK;
if (task_added) {
status = redisAsyncCommand(
db->context, redis_task_table_publish_push_callback,
(void *) callback_data->timer_id, "HMSET task:%b state %d node %b",
(char *) id.id, sizeof(task_id), state, (char *) node.id,
sizeof(node_id));
} else {
task_update update = {.state = state, .node = node};
redisAsyncCommand(db->context, redis_task_log_publish_push_callback,
(void *) callback_data->timer_id, "RPUSH tasklog:%b %b",
(char *) &task_iid.id[0], UNIQUE_ID_SIZE,
(char *) &update, sizeof(update));
status = redisAsyncCommand(
db->context, redis_task_table_publish_push_callback,
(void *) callback_data->timer_id,
"HMSET task:%b state %d node %b task_spec %b", (char *) id.id,
sizeof(task_id), state, (char *) node.id, sizeof(node_id),
(char *) spec, task_spec_size(spec));
}
if (db->context->err) {
LOG_REDIS_ERR(db->context, "error setting task in task_log_add_task");
if ((status = REDIS_ERR) || db->context->err) {
LOG_REDIS_ERR(db->context, "error setting task in task_table_add_task");
}
}
if (((bool *) callback_data->requests_info)[PUBLISH_INDEX] == false) {
redisAsyncCommand(db->context, redis_task_log_publish_publish_callback,
(void *) callback_data->timer_id,
"PUBLISH task_log:%b:%d %b", (char *) &node.id[0],
UNIQUE_ID_SIZE, state, (char *) task_instance,
task_instance_size(task_instance));
int status = redisAsyncCommand(
db->context, redis_task_table_publish_publish_callback,
(void *) callback_data->timer_id, "PUBLISH task:%b:%d %b",
(char *) node.id, sizeof(node_id), state, (char *) task,
task_size(task));
if (db->context->err) {
LOG_REDIS_ERR(db->context, "error publishing task in task_log_add_task");
if ((status == REDIS_ERR) || db->context->err) {
LOG_REDIS_ERR(db->context,
"error publishing task in task_table_add_task");
}
}
}
void redis_task_log_publish_push_callback(redisAsyncContext *c,
void *r,
void *privdata) {
REDIS_CALLBACK_HEADER(db, callback_data, r)
void redis_task_table_add_task(table_callback_data *callback_data) {
redis_task_table_publish(callback_data, false);
}
void redis_task_table_update(table_callback_data *callback_data) {
redis_task_table_publish(callback_data, true);
}
void redis_task_table_publish_push_callback(redisAsyncContext *c,
void *r,
void *privdata) {
REDIS_CALLBACK_HEADER(db, callback_data, r)
CHECK(callback_data->requests_info != NULL);
((bool *) callback_data->requests_info)[PUSH_INDEX] = true;
if (((bool *) callback_data->requests_info)[PUBLISH_INDEX] == true) {
if (callback_data->done_callback) {
task_log_done_callback done_callback = callback_data->done_callback;
task_table_done_callback done_callback = callback_data->done_callback;
done_callback(callback_data->id, callback_data->user_context);
}
destroy_timer_callback(db->loop, callback_data);
}
}
void redis_task_log_publish_publish_callback(redisAsyncContext *c,
void *r,
void *privdata) {
void redis_task_table_publish_publish_callback(redisAsyncContext *c,
void *r,
void *privdata) {
REDIS_CALLBACK_HEADER(db, callback_data, r)
CHECK(callback_data->requests_info != NULL);
((bool *) callback_data->requests_info)[PUBLISH_INDEX] = true;
if (((bool *) callback_data->requests_info)[PUSH_INDEX] == true) {
if (callback_data->done_callback) {
task_log_done_callback done_callback = callback_data->done_callback;
task_table_done_callback done_callback = callback_data->done_callback;
done_callback(callback_data->id, callback_data->user_context);
}
destroy_timer_callback(db->loop, callback_data);
}
}
void task_log_redis_callback(redisAsyncContext *c, void *r, void *privdata) {
void redis_task_table_subscribe_callback(redisAsyncContext *c,
void *r,
void *privdata) {
REDIS_CALLBACK_HEADER(db, callback_data, r)
redisReply *reply = r;
CHECK(reply->type == REDIS_REPLY_ARRAY);
/* First entry is message type, second is topic, third is payload. */
CHECK(reply->elements > 2);
/* If this condition is true, we got the initial message that acknowledged the
* subscription. */
if (reply->element[2]->str == NULL) {
CHECK(reply->elements > 2);
/* First entry is message type, then possibly the regex we psubscribed to,
* then topic, then payload. */
redisReply *payload = reply->element[reply->elements - 1];
if (payload->str == NULL) {
if (callback_data->done_callback) {
task_log_done_callback done_callback = callback_data->done_callback;
task_table_done_callback done_callback = callback_data->done_callback;
done_callback(callback_data->id, callback_data->user_context);
}
/* Note that we do not destroy the callback data yet because the
@@ -380,32 +588,36 @@ void task_log_redis_callback(redisAsyncContext *c, void *r, void *privdata) {
return;
}
/* Otherwise, parse the task and call the callback. */
task_log_subscribe_data *data = callback_data->data;
task_table_subscribe_data *data = callback_data->data;
task_instance *instance = malloc(reply->element[2]->len);
memcpy(instance, reply->element[2]->str, reply->element[2]->len);
task *task = malloc(payload->len);
memcpy(task, payload->str, payload->len);
if (data->subscribe_callback) {
data->subscribe_callback(instance, data->subscribe_context);
data->subscribe_callback(task, data->subscribe_context);
}
task_instance_free(instance);
free_task(task);
}
void redis_task_log_subscribe(table_callback_data *callback_data) {
void redis_task_table_subscribe(table_callback_data *callback_data) {
db_handle *db = callback_data->db_handle;
task_log_subscribe_data *data = callback_data->data;
if (memcmp(&data->node.id[0], &NIL_ID.id[0], UNIQUE_ID_SIZE) == 0) {
redisAsyncCommand(db->sub_context, task_log_redis_callback,
(void *) callback_data->timer_id,
"PSUBSCRIBE task_log:*:%d", data->state_filter);
task_table_subscribe_data *data = callback_data->data;
int status = REDIS_OK;
if (IS_NIL_ID(data->node)) {
/* TODO(swang): Implement the state_filter by translating the bitmask into
* a Redis key-matching pattern. */
status =
redisAsyncCommand(db->sub_context, redis_task_table_subscribe_callback,
(void *) callback_data->timer_id,
"PSUBSCRIBE task:*:%d", data->state_filter);
} else {
redisAsyncCommand(db->sub_context, task_log_redis_callback,
(void *) callback_data->timer_id,
"SUBSCRIBE task_log:%b:%d", (char *) &data->node.id[0],
UNIQUE_ID_SIZE, data->state_filter);
node_id node = data->node;
status = redisAsyncCommand(
db->sub_context, redis_task_table_subscribe_callback,
(void *) callback_data->timer_id, "SUBSCRIBE task:%b:%d",
(char *) node.id, sizeof(node_id), data->state_filter);
}
if (db->sub_context->err) {
LOG_REDIS_ERR(db->sub_context, "error in task_log_register_callback");
if ((status == REDIS_ERR) || db->sub_context->err) {
LOG_REDIS_ERR(db->sub_context, "error in task_table_register_callback");
}
}
+54 -14
View File
@@ -3,7 +3,7 @@
#include "db.h"
#include "object_table.h"
#include "task_log.h"
#include "task_table.h"
#include "hiredis/hiredis.h"
#include "hiredis/async.h"
@@ -65,7 +65,7 @@ void object_table_lookup_callback(redisAsyncContext *c,
void redis_object_table_lookup(table_callback_data *callback_data);
/**
* Add an entry to the object table in redis.
* Add a location entry to the object table in redis.
*
* @param callback_data Data structure containing redis connection and timeout
* information.
@@ -82,50 +82,90 @@ void redis_object_table_add(table_callback_data *callback_data);
*/
void redis_object_table_subscribe(table_callback_data *callback_data);
/**
* Add a new object to the object table in redis.
*
* @param callback_data Data structure containing redis connection and timeout
* information.
* @return Void.
*/
void redis_result_table_add(table_callback_data *callback_data);
/**
* Lookup the object in the object table in redis. The entry in
* the object table contains metadata about the object.
*
* @param callback_data Data structure containing redis connection and timeout
* information.
* @return Void.
*/
void redis_result_table_lookup(table_callback_data *callback_data);
/*
* ==== Redis task table function =====
*/
/**
* Add or update task log entry with new scheduling information.
* Get a task table entry, including the task spec and the task's scheduling
* information.
*
* @param callback_data Data structure containing redis connection and timeout
* information.
* @return Void.
*/
void redis_task_log_publish(table_callback_data *callback_data);
void redis_task_table_get_task(table_callback_data *callback_data);
/**
* Callback invoked when the replya from the task push command is received.
* Add a task table entry with a new task spec and the task's scheduling
* information.
*
* @param callback_data Data structure containing redis connection and timeout
* information.
* @return Void.
*/
void redis_task_table_add_task(table_callback_data *callback_data);
/**
* Update a task table entry with the task's scheduling information.
*
* @param callback_data Data structure containing redis connection and timeout
* information.
* @return Void.
*/
void redis_task_table_update(table_callback_data *callback_data);
/**
* Callback invoked when the reply from the task push command is received.
*
* @param c Redis context.
* @param r Reply (not used).
* @param privdata Data associated to the callback.
* @return Void.
*/
void redis_task_log_publish_push_callback(redisAsyncContext *c,
void *r,
void *privdata);
void redis_task_table_publish_push_callback(redisAsyncContext *c,
void *r,
void *privdata);
/**
* Callback invoked when the replya from the task publish command is received.
* Callback invoked when the reply from the task publish command is received.
*
* @param c Redis context.
* @param r Reply (not used).
* @param privdata Data associated to the callback.
* @return Void.
*/
void redis_task_log_publish_publish_callback(redisAsyncContext *c,
void *r,
void *privdata);
void redis_task_table_publish_publish_callback(redisAsyncContext *c,
void *r,
void *privdata);
/**
* Subscribe to updates of the task log.
* Subscribe to updates of the task table.
*
* @param callback_data Data structure containing redis connection and timeout
* information.
* @return Void.
*/
void redis_task_log_subscribe(table_callback_data *callback_data);
void redis_task_table_subscribe(table_callback_data *callback_data);
#endif /* REDIS_H */
+3 -2
View File
@@ -67,9 +67,10 @@ int64_t table_timeout_handler(event_loop *loop,
if (callback_data->retry.num_retries == 0) {
/* We didn't get a response from the database after exhausting all retries;
* let user know, cleanup the state, and remove the timer. */
LOG_ERR("Table command with timer ID %ld failed", timer_id);
if (callback_data->retry.fail_callback) {
callback_data->retry.fail_callback(callback_data->id,
callback_data->user_context);
callback_data->retry.fail_callback(
callback_data->id, callback_data->user_context, callback_data->data);
}
destroy_table_callback(callback_data);
return EVENT_LOOP_TIMER_DONE;
+16 -3
View File
@@ -12,8 +12,19 @@ typedef struct table_callback_data table_callback_data;
typedef void *table_done_callback;
/* The callback called when the database operation hasn't completed after
* the number of retries specified for the operation. */
typedef void (*table_fail_callback)(unique_id id, void *user_context);
* the number of retries specified for the operation.
*
* @param id The unique ID that identifies this callback. Examples include an
* object ID or task ID.
* @param user_context The state context for the callback. This is equivalent
* to the user_context field in table_callback_data.
* @param user_data A data argument for the callback. This is equivalent to the
* data field in table_callback_data. The user is responsible for
* freeing user_data.
*/
typedef void (*table_fail_callback)(unique_id id,
void *user_context,
void *user_data);
typedef void (*table_retry_callback)(table_callback_data *callback_data);
@@ -41,7 +52,9 @@ struct table_callback_data {
* before the next retry, and a pointer to the failure callback.
*/
retry_info retry;
/** Pointer to the data that is entered into the table. */
/** Pointer to the data that is entered into the table. This can be used to
* pass the result of the call to the callback. The user is responsible for
* freeing data in both the fail_callback and done_callback. */
void *data;
/** Pointer to the data used internally to handle multiple database requests.
*/
-34
View File
@@ -1,34 +0,0 @@
#include "task_log.h"
#include "redis.h"
#define NUM_DB_REQUESTS 2
void task_log_publish(db_handle *db_handle,
task_instance *task_instance,
retry_info *retry,
task_log_done_callback done_callback,
void *user_context) {
init_table_callback(db_handle, *task_instance_id(task_instance),
task_instance, retry, done_callback,
redis_task_log_publish, user_context);
}
/* TODO(swang): A corresponding task_log_unsubscribe. */
void task_log_subscribe(db_handle *db_handle,
node_id node,
int32_t state_filter,
task_log_subscribe_callback subscribe_callback,
void *subscribe_context,
retry_info *retry,
task_log_done_callback done_callback,
void *user_context) {
task_log_subscribe_data *sub_data = malloc(sizeof(task_log_subscribe_data));
utarray_push_back(db_handle->callback_freelist, &sub_data);
sub_data->node = node;
sub_data->state_filter = state_filter;
sub_data->subscribe_callback = subscribe_callback;
sub_data->subscribe_context = subscribe_context;
init_table_callback(db_handle, node, sub_data, retry, done_callback,
redis_task_log_subscribe, user_context);
}
-88
View File
@@ -1,88 +0,0 @@
#ifndef TASK_LOG_H
#define TASK_LOG_H
#include "db.h"
#include "table.h"
#include "task.h"
/**
* The task log is a message bus that is used for all communication between
* local and global schedulers (and also persisted to the state database).
* Here are examples of events that are recorded by the task log:
*
* 1) local scheduler writes it when submits a task to the global scheduler;
* 2) global scheduler reads it to get the task submitted by local schedulers;
* 3) global scheduler writes it when assigning the task to a local scheduler;
* 4) local scheduler reads it to get its tasks assigned by global scheduler;
* 5) local scheduler writes it when a task finishes execution;
* 6) global scheduler reads it to get the tasks that have finished; */
/* Callback called when the task log operation completes. */
typedef void (*task_log_done_callback)(task_iid task_iid, void *user_context);
/*
* ==== Publish the task log ====
*/
/**
* Add or update a task instance to the task log.
*
* @param db_handle Database handle.
* @param retry Information about retrying the request to the database.
* @param done_callback Function to be called when database returns result.
* @param user_context Data that will be passed to done_callback and
* fail_callback.
* @return Void.
*/
void task_log_publish(db_handle *db_handle,
task_instance *task_instance,
retry_info *retry,
task_log_done_callback done_callback,
void *user_context);
/*
* ==== Subscribing to the task log ====
*/
/* Callback for subscribing to the task log. */
typedef void (*task_log_subscribe_callback)(task_instance *task_instance,
void *user_context);
/**
* Register callback for a certain event.
*
* @param db_handle Database handle.
* @param subscribe_callback Callback that will be called when the task log is
* updated.
* @param subscribe_context Context that will be passed into the
* subscribe_callback.
* @param node Node whose events we want to listen to. If you want to register
* to updates from all nodes, set node = NIL_ID.
* @param state_filter Flags for events we want to listen to. If you want
* to listen to all events, use state_filter = TASK_WAITING |
* TASK_SCHEDULED | TASK_RUNNING | TASK_DONE.
* @param retry Information about retrying the request to the database.
* @param done_callback Function to be called when database returns result.
* @param user_context Data that will be passed to done_callback and
* fail_callback.
* @return Void.
*/
void task_log_subscribe(db_handle *db_handle,
node_id node,
int32_t state_filter,
task_log_subscribe_callback subscribe_callback,
void *subscribe_context,
retry_info *retry,
task_log_done_callback done_callback,
void *user_context);
/* Data that is needed to register task log subscribe callbacks with the state
* database. */
typedef struct {
node_id node;
int32_t state_filter;
task_log_subscribe_callback subscribe_callback;
void *subscribe_context;
} task_log_subscribe_data;
#endif /* TASK_LOG_H */
+52
View File
@@ -0,0 +1,52 @@
#include "task_table.h"
#include "redis.h"
#define NUM_DB_REQUESTS 2
void task_table_get_task(db_handle *db_handle,
task_id task_id,
retry_info *retry,
task_table_get_callback done_callback,
void *user_context) {
init_table_callback(db_handle, task_id, NULL, retry, done_callback,
redis_task_table_get_task, user_context);
}
void task_table_add_task(db_handle *db_handle,
task *task,
retry_info *retry,
task_table_done_callback done_callback,
void *user_context) {
init_table_callback(db_handle, task_task_id(task), task, retry, done_callback,
redis_task_table_add_task, user_context);
}
void task_table_update(db_handle *db_handle,
task *task,
retry_info *retry,
task_table_done_callback done_callback,
void *user_context) {
init_table_callback(db_handle, task_task_id(task), task, retry, done_callback,
redis_task_table_update, user_context);
}
/* TODO(swang): A corresponding task_table_unsubscribe. */
void task_table_subscribe(db_handle *db_handle,
node_id node,
scheduling_state state_filter,
task_table_subscribe_callback subscribe_callback,
void *subscribe_context,
retry_info *retry,
task_table_done_callback done_callback,
void *user_context) {
task_table_subscribe_data *sub_data =
malloc(sizeof(task_table_subscribe_data));
utarray_push_back(db_handle->callback_freelist, &sub_data);
sub_data->node = node;
sub_data->state_filter = state_filter;
sub_data->subscribe_callback = subscribe_callback;
sub_data->subscribe_context = subscribe_context;
init_table_callback(db_handle, node, sub_data, retry, done_callback,
redis_task_table_subscribe, user_context);
}
+125 -13
View File
@@ -1,20 +1,132 @@
#ifndef TASK_TABLE_H
#define TASK_TABLE_H
#ifndef task_table_H
#define task_table_H
#include "db.h"
#include "table.h"
#include "task.h"
/* Add task to the task table, handle errors here. */
status task_table_add_task(db_handle *db, task_spec *task);
/**
* The task table is a message bus that is used for all communication between
* local and global schedulers (and also persisted to the state database).
* Here are examples of events that are recorded by the task table:
*
* 1) local scheduler writes when it submits a task to the global scheduler;
* 2) global scheduler reads it to get the task submitted by local schedulers;
* 3) global scheduler writes it when assigning the task to a local scheduler;
* 4) local scheduler reads it to get its tasks assigned by global scheduler;
* 5) local scheduler writes it when a task finishes execution;
* 6) global scheduler reads it to get the tasks that have finished; */
/* Callback for getting an entry from the task table. Task spec will be freed
* by the system after the callback */
typedef void (*task_table_callback)(task_spec *task, void *context);
/* Callback called when a task table write operation completes. */
typedef void (*task_table_done_callback)(task_id task_id, void *user_context);
/* Get specific task from the task table. */
status task_table_get_task(db_handle *db,
task_id task_id,
task_table_callback callback,
void *context);
/* Callback called when a task table read operation completes. */
typedef void (*task_table_get_callback)(task *task, void *user_context);
#endif /* TASK_TABLE_H */
/**
* Get a task's entry from the task table.
*
* @param db_handle Database handle.
* @param task_id The ID of the task we want to look up.
* @param retry Information about retrying the request to the database.
* @param done_callback Function to be called when database returns result.
* @param user_context Data that will be passed to done_callback and
* fail_callback.
* @return Void.
*/
void task_table_get_task(db_handle *db,
task_id task_id,
retry_info *retry,
task_table_get_callback done_callback,
void *user_context);
/**
* Add a task entry, including task spec and scheduling information, to the
* task table. This will overwrite any task already in the task table with the
* same task ID.
*
* @param db_handle Database handle.
* @param task The task entry to add to the table.
* @param retry Information about retrying the request to the database.
* @param done_callback Function to be called when database returns result.
* @param user_context Data that will be passed to done_callback and
* fail_callback.
* @return Void.
*/
void task_table_add_task(db_handle *db_handle,
task *task,
retry_info *retry,
task_table_done_callback done_callback,
void *user_context);
/*
* ==== Publish the task table ====
*/
/**
* Update a task's scheduling information in the task table. This assumes that
* the task spec already exists in the task table entry.
*
* @param db_handle Database handle.
* @param task The task entry to add to the table. The task spec in the entry is
* ignored.
* @param retry Information about retrying the request to the database.
* @param done_callback Function to be called when database returns result.
* @param user_context Data that will be passed to done_callback and
* fail_callback.
* @return Void.
*/
void task_table_update(db_handle *db_handle,
task *task,
retry_info *retry,
task_table_done_callback done_callback,
void *user_context);
/*
* ==== Subscribing to the task table ====
*/
/* Callback for subscribing to the task table. */
typedef void (*task_table_subscribe_callback)(task *task, void *user_context);
/**
* Register a callback for a task event. An event is any update of a task in
* the task table, produced by task_table_add_task or task_table_add_task.
* Events include changes to the task's scheduling state or changes to the
* task's node location.
*
* @param db_handle Database handle.
* @param subscribe_callback Callback that will be called when the task table is
* updated.
* @param subscribe_context Context that will be passed into the
* subscribe_callback.
* @param node Node whose events we want to listen to. If you want to register
* to updates from all nodes, set node = NIL_ID.
* @param state_filter Flags for events we want to listen to. If you want
* to listen to all events, use state_filter = TASK_WAITING |
* TASK_SCHEDULED | TASK_RUNNING | TASK_DONE.
* @param retry Information about retrying the request to the database.
* @param done_callback Function to be called when database returns result.
* @param user_context Data that will be passed to done_callback and
* fail_callback.
* @return Void.
*/
void task_table_subscribe(db_handle *db_handle,
node_id node,
scheduling_state state_filter,
task_table_subscribe_callback subscribe_callback,
void *subscribe_context,
retry_info *retry,
task_table_done_callback done_callback,
void *user_context);
/* Data that is needed to register task table subscribe callbacks with the state
* database. */
typedef struct {
node_id node;
scheduling_state state_filter;
task_table_subscribe_callback subscribe_callback;
void *subscribe_context;
} task_table_subscribe_data;
#endif /* task_table_H */