1638 lines
53 KiB
C
1638 lines
53 KiB
C
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
|
#define QUAKEY_ENABLE_MOCKS
|
|
#endif
|
|
|
|
#include <quakey.h>
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "server.h"
|
|
|
|
typedef enum {
|
|
HR_OK,
|
|
HR_OUT_OF_MEMORY,
|
|
HR_INVALID_MESSAGE,
|
|
HR_DEFERRED, // Message not consumed; handler will reply and consume later
|
|
} HandlerResult;
|
|
|
|
static int self_idx(Server *state)
|
|
{
|
|
for (int i = 0; i < state->num_nodes; i++)
|
|
if (addr_eql(state->node_addrs[i], state->self_addr))
|
|
return i;
|
|
UNREACHABLE;
|
|
}
|
|
|
|
static int primary_idx(Server *state)
|
|
{
|
|
return state->view_number % state->num_nodes;
|
|
}
|
|
|
|
static bool is_primary(Server *state)
|
|
{
|
|
if (state->status == STATUS_RECOVERY)
|
|
return false;
|
|
return self_idx(state) == primary_idx(state);
|
|
}
|
|
|
|
#define TIME_FMT "%7.3fs"
|
|
#define TIME_VAL(t) ((double)(t) / 1000000000.0)
|
|
|
|
static const char *status_name(Status s)
|
|
{
|
|
switch (s) {
|
|
case STATUS_NORMAL: return "NR";
|
|
case STATUS_CHANGE_VIEW: return "CV";
|
|
case STATUS_RECOVERY: return "RC";
|
|
}
|
|
return "??";
|
|
}
|
|
|
|
static void node_log_impl(Server *state, const char *event, const char *detail)
|
|
{
|
|
printf("[" TIME_FMT "] NODE %d (%s) %s | V%-3llu C%-3d L%-3d | %-20s %s\n",
|
|
TIME_VAL(state->now),
|
|
self_idx(state),
|
|
is_primary(state) ? "PR" : "RE",
|
|
status_name(state->status),
|
|
(unsigned long long)state->view_number,
|
|
state->commit_index,
|
|
state->log.count,
|
|
event,
|
|
detail ? detail : "");
|
|
}
|
|
|
|
#define node_log(state, event, fmt, ...) do { \
|
|
char _detail[1024]; \
|
|
snprintf(_detail, sizeof(_detail), fmt, ##__VA_ARGS__); \
|
|
node_log_impl(state, event, _detail); \
|
|
} while (0)
|
|
|
|
#define node_log_simple(state, event) \
|
|
node_log_impl(state, event, NULL)
|
|
|
|
static int count_set(uint32_t word)
|
|
{
|
|
int n = 0;
|
|
for (int i = 0; i < (int) sizeof(word) * 8; i++)
|
|
if (word & (1 << i))
|
|
n++;
|
|
return n;
|
|
}
|
|
|
|
static bool reached_quorum(Server *state, uint32_t votes)
|
|
{
|
|
return count_set(votes) > state->num_nodes/2;
|
|
}
|
|
|
|
static bool already_voted(uint32_t votes, int idx)
|
|
{
|
|
uint32_t mask = 1 << idx;
|
|
return (votes & mask) == mask;
|
|
}
|
|
|
|
static void add_vote(uint32_t *votes, int idx)
|
|
{
|
|
*votes |= 1 << idx;
|
|
}
|
|
|
|
// Helper: read a message from raw_message into string-like usage
|
|
// raw_message is a pointer to the message bytes in the TCP read buffer
|
|
static uint64_t msg_len(void *raw_message)
|
|
{
|
|
Message base;
|
|
memcpy(&base, raw_message, sizeof(base));
|
|
return base.length;
|
|
}
|
|
|
|
static void begin_state_transfer(Server *state, int sender_idx)
|
|
{
|
|
if (state->state_transfer_pending)
|
|
return;
|
|
state->state_transfer_pending = true;
|
|
state->state_transfer_time = state->now;
|
|
|
|
GetStateMessage message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_GET_STATE,
|
|
.length = sizeof(GetStateMessage),
|
|
},
|
|
.view_number = state->view_number,
|
|
.op_number = state->log.count,
|
|
.sender_idx = self_idx(state),
|
|
};
|
|
send_message(state->msys,sender_idx, &message.base);
|
|
node_log(state, "SEND GET_STATE", "to=%d op=%d", sender_idx, state->log.count);
|
|
}
|
|
|
|
static HandlerResult
|
|
process_request(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
RequestMessage request_message;
|
|
if (len != sizeof(request_message))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&request_message, raw_message, sizeof(request_message));
|
|
|
|
{
|
|
char oper_buf[128];
|
|
meta_snprint_oper(oper_buf, sizeof(oper_buf), &request_message.oper);
|
|
node_log(state, "RECV REQUEST", "client=%llu req=%llu %s",
|
|
(unsigned long long)request_message.client_id, (unsigned long long)request_message.request_id,
|
|
oper_buf);
|
|
}
|
|
|
|
{
|
|
ClientTableEntry *entry = client_table_find(&state->client_table, request_message.client_id);
|
|
if (entry == NULL) {
|
|
|
|
int ret = client_table_add(
|
|
&state->client_table,
|
|
request_message.client_id,
|
|
request_message.request_id,
|
|
raw_message);
|
|
|
|
if (ret < 0) {
|
|
VsrReplyMessage reply_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_REPLY,
|
|
.length = sizeof(VsrReplyMessage),
|
|
},
|
|
.rejected = true,
|
|
.request_id = request_message.request_id,
|
|
};
|
|
reply_to_message(state->msys, raw_message, &reply_message.base);
|
|
node_log(state, "SEND REPLY", "client=%llu REJECTED (table full)", (unsigned long long)request_message.client_id);
|
|
return HR_OK;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (entry->pending)
|
|
return HR_OK;
|
|
|
|
if (entry->last_request_id > request_message.request_id)
|
|
return HR_OK;
|
|
|
|
if (entry->last_request_id == request_message.request_id) {
|
|
|
|
VsrReplyMessage reply_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_REPLY,
|
|
.length = sizeof(VsrReplyMessage),
|
|
},
|
|
.rejected = false,
|
|
.result = entry->last_result,
|
|
.request_id = request_message.request_id,
|
|
};
|
|
reply_to_message(state->msys, raw_message, &reply_message.base);
|
|
|
|
{
|
|
char result_buf[64];
|
|
meta_snprint_result(result_buf, sizeof(result_buf), entry->last_result);
|
|
node_log(state, "SEND REPLY", "client=%llu cached %s", (unsigned long long)request_message.client_id, result_buf);
|
|
}
|
|
return HR_OK;
|
|
}
|
|
|
|
entry->last_request_id = request_message.request_id;
|
|
entry->pending_message = raw_message;
|
|
entry->pending = true;
|
|
}
|
|
}
|
|
|
|
LogEntry log_entry = {
|
|
.oper = request_message.oper,
|
|
.votes = 1 << self_idx(state),
|
|
.view_number = state->view_number,
|
|
.client_id = request_message.client_id,
|
|
.request_id = request_message.request_id,
|
|
};
|
|
if (log_append(&state->log, log_entry) < 0)
|
|
return HR_OUT_OF_MEMORY;
|
|
|
|
PrepareMessage prepare_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_PREPARE,
|
|
.length = sizeof(PrepareMessage),
|
|
},
|
|
.oper = request_message.oper,
|
|
.sender_idx = self_idx(state),
|
|
.log_index = state->log.count-1,
|
|
.commit_index = state->commit_index,
|
|
.view_number = state->view_number,
|
|
.client_id = request_message.client_id,
|
|
.request_id = request_message.request_id,
|
|
};
|
|
broadcast_message(state->msys, self_idx(state), &prepare_message.base);
|
|
|
|
{
|
|
char oper_buf[128];
|
|
meta_snprint_oper(oper_buf, sizeof(oper_buf), &request_message.oper);
|
|
node_log(state, "SEND PREPARE", "to=* idx=%d %s", state->log.count-1, oper_buf);
|
|
}
|
|
return HR_DEFERRED;
|
|
}
|
|
|
|
static void reply_to_client(Server *state, ClientTableEntry *table_entry,
|
|
uint64_t request_id, MetaOper *oper, MetaResult result)
|
|
{
|
|
{
|
|
char oper_buf[128], result_buf[64];
|
|
meta_snprint_oper(oper_buf, sizeof(oper_buf), oper);
|
|
meta_snprint_result(result_buf, sizeof(result_buf), result);
|
|
node_log(state, "SEND REPLY", "client=%llu req=%llu %s -> %s",
|
|
(unsigned long long)table_entry->client_id, (unsigned long long)request_id, oper_buf, result_buf);
|
|
}
|
|
|
|
if (table_entry->pending_message) {
|
|
VsrReplyMessage message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_REPLY,
|
|
.length = sizeof(VsrReplyMessage),
|
|
},
|
|
.result = result,
|
|
.request_id = request_id,
|
|
};
|
|
reply_to_message(state->msys, table_entry->pending_message, &message.base);
|
|
consume_message(state->msys, table_entry->pending_message);
|
|
table_entry->pending_message = NULL;
|
|
}
|
|
}
|
|
|
|
static void advance_commit_index(Server *state, int target_index, bool send_replies)
|
|
{
|
|
target_index = MIN(target_index, state->log.count);
|
|
|
|
while (state->commit_index < target_index) {
|
|
|
|
LogEntry *entry = &state->log.entries[state->commit_index++];
|
|
|
|
MetaResult result = meta_store_update(&state->metastore, &entry->oper);
|
|
{
|
|
char oper_buf[128], result_buf[64];
|
|
meta_snprint_oper(oper_buf, sizeof(oper_buf), &entry->oper);
|
|
meta_snprint_result(result_buf, sizeof(result_buf), result);
|
|
node_log(state, "APPLY", "idx=%d %s -> %s", state->commit_index-1, oper_buf, result_buf);
|
|
}
|
|
|
|
ClientTableEntry *table_entry = client_table_find(&state->client_table, entry->client_id);
|
|
if (table_entry == NULL)
|
|
continue;
|
|
|
|
if (!table_entry->pending)
|
|
continue;
|
|
|
|
if (table_entry->last_request_id != entry->request_id)
|
|
continue;
|
|
|
|
table_entry->pending = false;
|
|
table_entry->last_result = result;
|
|
|
|
if (send_replies)
|
|
reply_to_client(state, table_entry, entry->request_id, &entry->oper, result);
|
|
}
|
|
}
|
|
|
|
static HandlerResult
|
|
process_prepare_ok(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
PrepareOKMessage message;
|
|
if (len != sizeof(message))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&message, raw_message, sizeof(message));
|
|
|
|
node_log(state, "RECV PREPARE_OK", "from=%d idx=%d view=%llu",
|
|
message.sender_idx, message.log_index, (unsigned long long)message.view_number);
|
|
|
|
if (message.view_number < state->view_number)
|
|
return HR_OK;
|
|
|
|
if (message.view_number > state->view_number) {
|
|
state->view_number = message.view_number;
|
|
begin_state_transfer(state, message.sender_idx);
|
|
return HR_OK;
|
|
}
|
|
|
|
assert(message.log_index > -1);
|
|
assert(message.log_index < state->log.count);
|
|
|
|
if (message.log_index < state->commit_index)
|
|
return HR_OK;
|
|
|
|
LogEntry *entry = &state->log.entries[message.log_index];
|
|
add_vote(&entry->votes, message.sender_idx);
|
|
if (reached_quorum(state, entry->votes)) {
|
|
node_log(state, "QUORUM", "idx=%d %s/%s", message.log_index, entry->oper.bucket, entry->oper.key);
|
|
advance_commit_index(state, message.log_index+1, true);
|
|
}
|
|
return HR_OK;
|
|
}
|
|
|
|
static bool
|
|
should_store_view_change_log(Server *state, DoViewChangeMessage message)
|
|
{
|
|
if (message.old_view_number > state->view_change_old_view)
|
|
return true;
|
|
|
|
if (message.old_view_number < state->view_change_old_view)
|
|
return false;
|
|
|
|
if (message.op_number <= state->view_change_log.count)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
clear_view_change_fields(Server *state)
|
|
{
|
|
state->view_change_begin_votes = 0;
|
|
state->view_change_apply_votes = 0;
|
|
state->view_change_old_view = 0;
|
|
state->view_change_commit = 0;
|
|
log_free(&state->view_change_log);
|
|
log_init(&state->view_change_log);
|
|
}
|
|
|
|
static HandlerResult
|
|
complete_view_change_and_become_primary(Server *state)
|
|
{
|
|
assert(state->commit_index <= state->view_change_commit);
|
|
|
|
log_move(&state->log, &state->view_change_log);
|
|
|
|
state->status = STATUS_NORMAL;
|
|
state->last_normal_view = state->view_number;
|
|
node_log(state, "STATUS NORMAL", "became primary (view change complete)");
|
|
|
|
advance_commit_index(state, state->view_change_commit, false);
|
|
|
|
for (int i = state->commit_index; i < state->log.count; i++) {
|
|
LogEntry *entry = &state->log.entries[i];
|
|
entry->votes = 0;
|
|
add_vote(&entry->votes, self_idx(state));
|
|
}
|
|
|
|
BeginViewMessage begin_view_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_BEGIN_VIEW,
|
|
.length = sizeof(BeginViewMessage) + state->log.count * sizeof(LogEntry),
|
|
},
|
|
.view_number = state->view_number,
|
|
.commit_index = state->commit_index,
|
|
.op_number = state->log.count,
|
|
};
|
|
broadcast_message_ex(state->msys, self_idx(state),&begin_view_message.base, state->log.entries, state->log.count * sizeof(LogEntry));
|
|
|
|
node_log(state, "SEND BEGIN_VIEW", "to=* view=%llu log=%d commit=%d",
|
|
(unsigned long long)state->view_number, state->log.count, state->commit_index);
|
|
|
|
clear_view_change_fields(state);
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_do_view_change(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
DoViewChangeMessage message;
|
|
if (len < sizeof(message))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&message, raw_message, sizeof(message));
|
|
|
|
node_log(state, "RECV DO_VIEW_CHANGE", "from=%d view=%llu old_view=%llu ops=%d commit=%d",
|
|
message.sender_idx, (unsigned long long)message.view_number, (unsigned long long)message.old_view_number,
|
|
message.op_number, message.commit_index);
|
|
|
|
if (message.view_number != state->view_number)
|
|
return HR_OK;
|
|
|
|
if (!already_voted(state->view_change_apply_votes, message.sender_idx)) {
|
|
|
|
if (should_store_view_change_log(state, message)) {
|
|
|
|
state->view_change_old_view = message.old_view_number;
|
|
|
|
LogEntry *entries = (LogEntry*) ((uint8_t*)raw_message + sizeof(DoViewChangeMessage));
|
|
|
|
int num_entries = (len - sizeof(DoViewChangeMessage)) / sizeof(LogEntry);
|
|
if (num_entries != message.op_number)
|
|
return HR_INVALID_MESSAGE;
|
|
|
|
log_free(&state->view_change_log);
|
|
if (log_init_from_network(&state->view_change_log, entries, num_entries) < 0)
|
|
return HR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
state->view_change_commit = MAX(state->view_change_commit, message.commit_index);
|
|
add_vote(&state->view_change_apply_votes, message.sender_idx);
|
|
}
|
|
|
|
if (reached_quorum(state, state->view_change_apply_votes)) {
|
|
HandlerResult ret = complete_view_change_and_become_primary(state);
|
|
if (ret != HR_OK)
|
|
return ret;
|
|
}
|
|
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_recovery(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
RecoveryMessage recovery_message;
|
|
if (len != sizeof(RecoveryMessage))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&recovery_message, raw_message, sizeof(recovery_message));
|
|
|
|
node_log(state, "RECV RECOVERY", "from=%d nonce=%llu", recovery_message.sender_idx, (unsigned long long)recovery_message.nonce);
|
|
|
|
node_log(state, "SEND RECOVERY_RESP", "to=%d view=%llu is_primary=%s",
|
|
recovery_message.sender_idx, (unsigned long long)state->view_number, is_primary(state) ? "yes" : "no");
|
|
|
|
RecoveryResponseMessage recovery_response_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_RECOVERY_RESPONSE,
|
|
.length = sizeof(RecoveryResponseMessage),
|
|
},
|
|
.view_number = state->view_number,
|
|
.op_number = state->log.count-1,
|
|
.nonce = recovery_message.nonce,
|
|
.commit_index = state->commit_index,
|
|
.sender_idx = self_idx(state),
|
|
};
|
|
if (is_primary(state)) {
|
|
recovery_response_message.base.length += state->log.count * sizeof(LogEntry);
|
|
send_message_ex(state->msys,recovery_message.sender_idx, &recovery_response_message.base,
|
|
state->log.entries, state->log.count * sizeof(LogEntry));
|
|
} else {
|
|
send_message(state->msys,recovery_message.sender_idx, &recovery_response_message.base);
|
|
}
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
perform_log_transfer_for_view_change(Server *state)
|
|
{
|
|
if (is_primary(state)) {
|
|
add_vote(&state->view_change_apply_votes, self_idx(state));
|
|
|
|
state->view_change_old_view = state->last_normal_view;
|
|
state->view_change_commit = state->commit_index;
|
|
|
|
if (log_init_from_network(&state->view_change_log, state->log.entries, state->log.count) < 0)
|
|
return HR_OUT_OF_MEMORY;
|
|
|
|
} else {
|
|
DoViewChangeMessage do_view_change_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_DO_VIEW_CHANGE,
|
|
.length = sizeof(DoViewChangeMessage) + state->log.count * sizeof(LogEntry),
|
|
},
|
|
.view_number = state->view_number,
|
|
.old_view_number = state->last_normal_view,
|
|
.op_number = state->log.count,
|
|
.commit_index = state->commit_index,
|
|
.sender_idx = self_idx(state),
|
|
};
|
|
send_message_ex(state->msys,primary_idx(state), &do_view_change_message.base, state->log.entries, state->log.count * sizeof(LogEntry));
|
|
node_log(state, "SEND DO_VIEW_CHANGE", "to=%d view=%llu old_view=%llu log=%d commit=%d",
|
|
primary_idx(state), (unsigned long long)state->view_number, (unsigned long long)state->last_normal_view,
|
|
state->log.count, state->commit_index);
|
|
}
|
|
|
|
state->num_future = 0;
|
|
state->state_transfer_pending = false;
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_begin_view_change(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
BeginViewChangeMessage message;
|
|
if (len != sizeof(BeginViewChangeMessage))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&message, raw_message, sizeof(message));
|
|
|
|
node_log(state, "RECV BEGIN_VIEW_CHG", "from=%d view=%llu", message.sender_idx, (unsigned long long)message.view_number);
|
|
|
|
if (message.view_number < state->view_number)
|
|
return HR_OK;
|
|
|
|
if (state->status == STATUS_NORMAL) {
|
|
if (state->view_number == message.view_number)
|
|
return HR_OK;
|
|
}
|
|
|
|
if (message.view_number > state->view_number) {
|
|
|
|
BeginViewChangeMessage message_2 = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_BEGIN_VIEW_CHANGE,
|
|
.length = sizeof(BeginViewChangeMessage),
|
|
},
|
|
.view_number = message.view_number,
|
|
.sender_idx = self_idx(state),
|
|
};
|
|
broadcast_message(state->msys, self_idx(state),&message_2.base);
|
|
node_log(state, "SEND BEGIN_VIEW_CHG", "to=* view=%llu", (unsigned long long)message.view_number);
|
|
|
|
clear_view_change_fields(state);
|
|
state->view_number = message.view_number;
|
|
state->heartbeat = state->now;
|
|
state->status = STATUS_CHANGE_VIEW;
|
|
node_log(state, "STATUS CHANGE_VIEW", "view=%llu", (unsigned long long)state->view_number);
|
|
}
|
|
|
|
bool before = reached_quorum(state, state->view_change_begin_votes);
|
|
|
|
add_vote(&state->view_change_begin_votes, self_idx(state));
|
|
add_vote(&state->view_change_begin_votes, message.sender_idx);
|
|
|
|
if (!before && reached_quorum(state, state->view_change_begin_votes)) {
|
|
HandlerResult ret = perform_log_transfer_for_view_change(state);
|
|
if (ret != HR_OK)
|
|
return ret;
|
|
}
|
|
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_begin_view(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
BeginViewMessage message;
|
|
if (len < sizeof(message))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&message, raw_message, sizeof(message));
|
|
|
|
node_log(state, "RECV BEGIN_VIEW", "view=%llu commit=%d ops=%d",
|
|
(unsigned long long)message.view_number, message.commit_index, message.op_number);
|
|
|
|
if (message.view_number < state->view_number)
|
|
return HR_OK;
|
|
|
|
state->view_number = message.view_number;
|
|
|
|
state->status = STATUS_NORMAL;
|
|
state->last_normal_view = state->view_number;
|
|
node_log(state, "STATUS NORMAL", "new view=%llu (follower)", (unsigned long long)state->view_number);
|
|
|
|
int num_entries = (len - sizeof(BeginViewMessage)) / sizeof(LogEntry);
|
|
assert(num_entries >= state->commit_index);
|
|
|
|
LogEntry *entries = (LogEntry *)((uint8_t *)raw_message + sizeof(BeginViewMessage));
|
|
log_free(&state->log);
|
|
if (log_init_from_network(&state->log, entries, num_entries) < 0)
|
|
return HR_OUT_OF_MEMORY;
|
|
|
|
state->num_future = 0;
|
|
state->state_transfer_pending = false;
|
|
|
|
if (state->log.count > message.commit_index) {
|
|
PrepareOKMessage ok_msg = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_PREPARE_OK,
|
|
.length = sizeof(PrepareOKMessage),
|
|
},
|
|
.sender_idx = self_idx(state),
|
|
.log_index = state->log.count - 1,
|
|
.view_number = state->view_number,
|
|
};
|
|
send_message(state->msys,primary_idx(state), &ok_msg.base);
|
|
node_log(state, "SEND PREPARE_OK", "to=%d idx=%d %s/%s", primary_idx(state), state->log.count - 1,
|
|
state->log.entries[state->log.count - 1].oper.bucket, state->log.entries[state->log.count - 1].oper.key);
|
|
}
|
|
|
|
advance_commit_index(state, message.commit_index, false);
|
|
|
|
clear_view_change_fields(state);
|
|
state->heartbeat = state->now;
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_get_state(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
GetStateMessage get_state_message;
|
|
if (len != sizeof(GetStateMessage))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&get_state_message, raw_message, sizeof(get_state_message));
|
|
|
|
node_log(state, "RECV GET_STATE", "from=%d op=%d view=%llu",
|
|
get_state_message.sender_idx, get_state_message.op_number, (unsigned long long)get_state_message.view_number);
|
|
|
|
if (state->status != STATUS_NORMAL)
|
|
return HR_OK;
|
|
|
|
if (get_state_message.view_number != state->view_number)
|
|
return HR_OK;
|
|
|
|
int start = get_state_message.op_number;
|
|
if (start < 0 || start >= state->log.count)
|
|
return HR_OK;
|
|
|
|
int num_entries = state->log.count - start;
|
|
|
|
NewStateMessage new_state_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_NEW_STATE,
|
|
.length = sizeof(NewStateMessage) + num_entries * sizeof(LogEntry),
|
|
},
|
|
.view_number = state->view_number,
|
|
.op_number = num_entries,
|
|
.commit_index = state->commit_index,
|
|
.start_index = start,
|
|
};
|
|
send_message_ex(state->msys,get_state_message.sender_idx, &new_state_message.base,
|
|
state->log.entries + start, num_entries * sizeof(LogEntry));
|
|
node_log(state, "SEND NEW_STATE", "to=%d entries=%d commit=%d",
|
|
get_state_message.sender_idx, num_entries, state->commit_index);
|
|
return HR_OK;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
// Chunk and Blob message handlers (bypass VSR log)
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
static HandlerResult
|
|
process_store_chunk(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
StoreChunkMessage message;
|
|
if (len < sizeof(message))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&message, raw_message, sizeof(message));
|
|
|
|
uint32_t data_size = len - sizeof(StoreChunkMessage);
|
|
if (data_size != message.size)
|
|
return HR_INVALID_MESSAGE;
|
|
|
|
char *data = (char *)((uint8_t *)raw_message + sizeof(StoreChunkMessage));
|
|
|
|
int ret = chunk_store_write(&state->chunk_store, message.hash, data, message.size);
|
|
|
|
StoreChunkAckMessage ack = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_STORE_CHUNK_ACK,
|
|
.length = sizeof(StoreChunkAckMessage),
|
|
},
|
|
.hash = message.hash,
|
|
.success = (ret == 0),
|
|
};
|
|
reply_to_message(state->msys, raw_message, &ack.base);
|
|
|
|
node_log(state, "RECV STORE_CHUNK", "size=%u ok=%d", message.size, ret == 0);
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_fetch_chunk(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
FetchChunkMessage message;
|
|
if (len != sizeof(message))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&message, raw_message, sizeof(message));
|
|
|
|
bool exists = chunk_store_exists(&state->chunk_store, message.hash);
|
|
|
|
if (!exists) {
|
|
FetchChunkResponseMessage response = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_FETCH_CHUNK_RESPONSE,
|
|
.length = sizeof(FetchChunkResponseMessage),
|
|
},
|
|
.hash = message.hash,
|
|
.size = 0,
|
|
};
|
|
reply_to_message(state->msys, raw_message, &response.base);
|
|
|
|
node_log(state, "RECV FETCH_CHUNK", "NOT_FOUND");
|
|
return HR_OK;
|
|
}
|
|
|
|
uint32_t chunk_size = 0;
|
|
for (int i = 0; i < state->metastore.count; i++) {
|
|
ObjectMeta *meta = &state->metastore.entries[i];
|
|
if (meta->deleted) continue;
|
|
for (uint32_t j = 0; j < meta->num_chunks; j++) {
|
|
if (memcmp(&meta->chunks[j].hash, &message.hash, sizeof(SHA256)) == 0) {
|
|
chunk_size = meta->chunks[j].size;
|
|
goto found_size;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
FetchChunkResponseMessage response = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_FETCH_CHUNK_RESPONSE,
|
|
.length = sizeof(FetchChunkResponseMessage),
|
|
},
|
|
.hash = message.hash,
|
|
.size = 0,
|
|
};
|
|
reply_to_message(state->msys, raw_message, &response.base);
|
|
|
|
node_log(state, "RECV FETCH_CHUNK", "NO_META");
|
|
return HR_OK;
|
|
}
|
|
|
|
found_size:;
|
|
char *chunk_data = malloc(chunk_size);
|
|
if (chunk_data == NULL)
|
|
return HR_OUT_OF_MEMORY;
|
|
|
|
int ret = chunk_store_read(&state->chunk_store, message.hash, chunk_data, chunk_size);
|
|
if (ret <= 0) {
|
|
free(chunk_data);
|
|
FetchChunkResponseMessage response = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_FETCH_CHUNK_RESPONSE,
|
|
.length = sizeof(FetchChunkResponseMessage),
|
|
},
|
|
.hash = message.hash,
|
|
.size = 0,
|
|
};
|
|
if (message.sender_idx >= 0 && message.sender_idx < state->num_nodes)
|
|
send_message(state->msys,message.sender_idx, &response.base);
|
|
else
|
|
reply_to_message(state->msys, raw_message, &response.base);
|
|
node_log(state, "RECV FETCH_CHUNK", "READ_ERR size=%u", chunk_size);
|
|
return HR_OK;
|
|
}
|
|
|
|
FetchChunkResponseMessage response = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_FETCH_CHUNK_RESPONSE,
|
|
.length = sizeof(FetchChunkResponseMessage) + chunk_size,
|
|
},
|
|
.hash = message.hash,
|
|
.size = chunk_size,
|
|
};
|
|
reply_to_message_ex(state->msys, raw_message, &response.base, chunk_data, chunk_size);
|
|
|
|
node_log(state, "RECV FETCH_CHUNK", "size=%u", chunk_size);
|
|
|
|
free(chunk_data);
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_get_blob(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
GetBlobMessage message;
|
|
if (len != sizeof(message))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&message, raw_message, sizeof(message));
|
|
|
|
node_log(state, "RECV GET_BLOB", "%s/%s", message.bucket, message.key);
|
|
|
|
ObjectMeta *meta = meta_store_lookup(&state->metastore, message.bucket, message.key);
|
|
|
|
GetBlobResponseMessage response = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_GET_BLOB_RESPONSE,
|
|
.length = sizeof(GetBlobResponseMessage),
|
|
},
|
|
};
|
|
|
|
if (meta != NULL) {
|
|
response.found = true;
|
|
response.size = meta->size;
|
|
response.content_hash = meta->content_hash;
|
|
response.num_chunks = meta->num_chunks;
|
|
memcpy(response.chunks, meta->chunks, meta->num_chunks * sizeof(ChunkRef));
|
|
} else {
|
|
response.found = false;
|
|
}
|
|
|
|
reply_to_message(state->msys, raw_message, &response.base);
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
complete_recovery(Server *state)
|
|
{
|
|
assert(state->commit_index <= state->recovery_commit);
|
|
|
|
state->view_number = state->recovery_view;
|
|
log_move(&state->log, &state->recovery_log);
|
|
advance_commit_index(state, state->recovery_commit, false);
|
|
|
|
state->status = STATUS_NORMAL;
|
|
state->last_normal_view = state->view_number;
|
|
node_log(state, "STATUS NORMAL", "recovery complete view=%llu commit=%d",
|
|
(unsigned long long)state->view_number, state->commit_index);
|
|
|
|
if (is_primary(state)) {
|
|
for (int i = state->commit_index; i < state->log.count; i++) {
|
|
LogEntry *entry = &state->log.entries[i];
|
|
entry->votes = 0;
|
|
add_vote(&entry->votes, self_idx(state));
|
|
}
|
|
}
|
|
|
|
state->heartbeat = state->now;
|
|
return HR_OK;
|
|
}
|
|
|
|
static bool
|
|
received_recovery_primary(Server *state)
|
|
{
|
|
assert(state->status == STATUS_RECOVERY);
|
|
|
|
int pidx = state->recovery_view % state->num_nodes;
|
|
uint32_t primary_mask = 1 << pidx;
|
|
return (state->recovery_votes & primary_mask) != 0;
|
|
}
|
|
|
|
static bool
|
|
sender_thinks_he_is_primary(Server *state, RecoveryResponseMessage message)
|
|
{
|
|
(void) state;
|
|
return message.sender_idx == (int) (message.view_number % state->num_nodes);
|
|
}
|
|
|
|
static bool
|
|
should_store_recovery_log(Server *state, RecoveryResponseMessage message)
|
|
{
|
|
return sender_thinks_he_is_primary(state, message)
|
|
&& (!received_recovery_primary(state) || state->recovery_log_view < message.view_number);
|
|
}
|
|
|
|
static HandlerResult
|
|
process_recovery_response(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
RecoveryResponseMessage message;
|
|
if (len < sizeof(message))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&message, raw_message, sizeof(message));
|
|
|
|
node_log(state, "RECV RECOVERY_RESP", "from=%d view=%llu commit=%d nonce=%llu",
|
|
message.sender_idx, (unsigned long long)message.view_number, message.commit_index, (unsigned long long)message.nonce);
|
|
|
|
if (message.nonce != state->recovery_nonce)
|
|
return HR_OK;
|
|
|
|
state->recovery_view = MAX(state->recovery_view, message.view_number);
|
|
|
|
if (should_store_recovery_log(state, message)) {
|
|
|
|
LogEntry *entries = (LogEntry*) ((uint8_t*)raw_message + sizeof(RecoveryResponseMessage));
|
|
int num_entries = message.op_number + 1;
|
|
|
|
assert(num_entries == (int) ((len - sizeof(RecoveryResponseMessage)) / sizeof(LogEntry)));
|
|
|
|
log_free(&state->recovery_log);
|
|
if (log_init_from_network(&state->recovery_log, entries, num_entries) < 0)
|
|
return HR_OUT_OF_MEMORY;
|
|
|
|
state->recovery_log_view = message.view_number;
|
|
state->recovery_commit = message.commit_index;
|
|
}
|
|
|
|
add_vote(&state->recovery_votes, message.sender_idx);
|
|
if (reached_quorum(state, state->recovery_votes) && received_recovery_primary(state)) {
|
|
HandlerResult ret = complete_recovery(state);
|
|
if (ret != HR_OK)
|
|
return ret;
|
|
}
|
|
return HR_OK;
|
|
}
|
|
|
|
static int
|
|
process_single_future_list_entry(Server *state)
|
|
{
|
|
int i = 0;
|
|
while (i < state->num_future && state->future[i].log_index != state->log.count)
|
|
i++;
|
|
|
|
if (i == state->num_future)
|
|
return 0;
|
|
|
|
LogEntry entry = {
|
|
.oper = state->future[i].oper,
|
|
.votes = 0,
|
|
.view_number = state->view_number,
|
|
.client_id = state->future[i].client_id,
|
|
.request_id = state->future[i].request_id,
|
|
};
|
|
if (log_append(&state->log, entry) < 0)
|
|
return -1;
|
|
|
|
PrepareOKMessage message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_PREPARE_OK,
|
|
.length = sizeof(PrepareOKMessage),
|
|
},
|
|
.sender_idx = self_idx(state),
|
|
.log_index = state->log.count-1,
|
|
.view_number = state->view_number,
|
|
};
|
|
send_message(state->msys,state->future[i].sender_idx, &message.base);
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
remove_old_future_list_entries(Server *state)
|
|
{
|
|
for (int i = 0; i < state->num_future; i++) {
|
|
if (state->future[i].log_index < state->log.count) {
|
|
state->future[i--] = state->future[--state->num_future];
|
|
}
|
|
}
|
|
}
|
|
|
|
static int process_future_list(Server *state)
|
|
{
|
|
for (;;) {
|
|
int ret = process_single_future_list_entry(state);
|
|
if (ret != 1)
|
|
break;
|
|
}
|
|
remove_old_future_list_entries(state);
|
|
return 0;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_prepare(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
PrepareMessage message;
|
|
if (len != sizeof(message))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&message, raw_message, sizeof(message));
|
|
|
|
{
|
|
char oper_buf[128];
|
|
meta_snprint_oper(oper_buf, sizeof(oper_buf), &message.oper);
|
|
node_log(state, "RECV PREPARE", "from=%d idx=%d commit=%d view=%llu %s",
|
|
message.sender_idx, message.log_index, message.commit_index,
|
|
(unsigned long long)message.view_number, oper_buf);
|
|
}
|
|
|
|
if (message.view_number < state->view_number)
|
|
return HR_OK;
|
|
|
|
if (message.view_number > state->view_number) {
|
|
state->view_number = message.view_number;
|
|
if (state->num_future < FUTURE_LIMIT)
|
|
state->future[state->num_future++] = message;
|
|
begin_state_transfer(state, message.sender_idx);
|
|
return HR_OK;
|
|
}
|
|
|
|
if (message.log_index < state->log.count)
|
|
return HR_OK;
|
|
|
|
if (message.log_index > state->log.count) {
|
|
if (state->num_future < FUTURE_LIMIT)
|
|
state->future[state->num_future++] = message;
|
|
begin_state_transfer(state, message.sender_idx);
|
|
return HR_OK;
|
|
}
|
|
|
|
LogEntry log_entry = {
|
|
.oper = message.oper,
|
|
.votes = 0,
|
|
.view_number = state->view_number,
|
|
.client_id = message.client_id,
|
|
.request_id = message.request_id,
|
|
};
|
|
if (log_append(&state->log, log_entry) < 0)
|
|
return HR_OUT_OF_MEMORY;
|
|
|
|
PrepareOKMessage ok_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_PREPARE_OK,
|
|
.length = sizeof(PrepareOKMessage),
|
|
},
|
|
.sender_idx = self_idx(state),
|
|
.log_index = state->log.count-1,
|
|
.view_number = state->view_number,
|
|
};
|
|
send_message(state->msys,message.sender_idx, &ok_message.base);
|
|
node_log(state, "SEND PREPARE_OK", "to=%d idx=%d %s/%s",
|
|
message.sender_idx, state->log.count-1, message.oper.bucket, message.oper.key);
|
|
|
|
process_future_list(state);
|
|
advance_commit_index(state, message.commit_index, false);
|
|
|
|
state->heartbeat = state->now;
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_commit(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
CommitMessage message;
|
|
if (len != sizeof(CommitMessage))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&message, raw_message, sizeof(message));
|
|
|
|
node_log(state, "RECV COMMIT", "commit=%d", message.commit_index);
|
|
|
|
if (message.view_number < state->view_number)
|
|
return HR_OK;
|
|
|
|
if (message.view_number > state->view_number) {
|
|
begin_state_transfer(state, message.sender_idx);
|
|
return HR_OK;
|
|
}
|
|
|
|
advance_commit_index(state, message.commit_index, false);
|
|
|
|
state->heartbeat = state->now;
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_new_state(Server *state, void *raw_message)
|
|
{
|
|
uint64_t len = msg_len(raw_message);
|
|
|
|
NewStateMessage new_state_message;
|
|
if (len < sizeof(NewStateMessage))
|
|
return HR_INVALID_MESSAGE;
|
|
memcpy(&new_state_message, raw_message, sizeof(new_state_message));
|
|
|
|
node_log(state, "RECV NEW_STATE", "entries=%d commit=%d view=%llu",
|
|
new_state_message.op_number, new_state_message.commit_index, (unsigned long long)new_state_message.view_number);
|
|
|
|
if (new_state_message.view_number != state->view_number)
|
|
return HR_OK;
|
|
|
|
int num_entries = (len - sizeof(NewStateMessage)) / sizeof(LogEntry);
|
|
if (num_entries != new_state_message.op_number)
|
|
return HR_INVALID_MESSAGE;
|
|
|
|
if (num_entries == 0)
|
|
return HR_OK;
|
|
|
|
LogEntry *entries = (LogEntry *)((uint8_t *)raw_message + sizeof(NewStateMessage));
|
|
int start_index = new_state_message.start_index;
|
|
for (int i = 0; i < num_entries; i++) {
|
|
|
|
int global_idx = start_index + i;
|
|
if (global_idx < state->log.count)
|
|
continue;
|
|
|
|
LogEntry entry = {
|
|
.oper = entries[i].oper,
|
|
.votes = 0,
|
|
.view_number = state->view_number,
|
|
.client_id = entries[i].client_id,
|
|
.request_id = entries[i].request_id,
|
|
};
|
|
if (log_append(&state->log, entry) < 0)
|
|
return HR_OUT_OF_MEMORY;
|
|
|
|
PrepareOKMessage prepare_ok_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_PREPARE_OK,
|
|
.length = sizeof(PrepareOKMessage),
|
|
},
|
|
.sender_idx = self_idx(state),
|
|
.log_index = state->log.count - 1,
|
|
.view_number = state->view_number,
|
|
};
|
|
send_message(state->msys,primary_idx(state), &prepare_ok_message.base);
|
|
node_log(state, "SEND PREPARE_OK", "to=%d idx=%d %s/%s", primary_idx(state), state->log.count - 1,
|
|
state->log.entries[state->log.count - 1].oper.bucket, state->log.entries[state->log.count - 1].oper.key);
|
|
}
|
|
|
|
process_future_list(state);
|
|
advance_commit_index(state, new_state_message.commit_index, false);
|
|
|
|
state->state_transfer_pending = false;
|
|
state->heartbeat = state->now;
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
send_redirect(Server *state, void *raw_message)
|
|
{
|
|
RedirectMessage redirect_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_REDIRECT,
|
|
.length = sizeof(RedirectMessage),
|
|
},
|
|
.view_number = state->view_number,
|
|
};
|
|
reply_to_message(state->msys, raw_message, &redirect_message.base);
|
|
|
|
node_log(state, "SEND REDIRECT", "view=%llu leader=%d",
|
|
(unsigned long long)state->view_number, primary_idx(state));
|
|
return HR_OK;
|
|
}
|
|
|
|
static HandlerResult
|
|
process_message(Server *state, void *raw_message)
|
|
{
|
|
Message base;
|
|
memcpy(&base, raw_message, sizeof(base));
|
|
uint16_t type = base.type;
|
|
|
|
switch (type) {
|
|
case MESSAGE_TYPE_REQUEST:
|
|
if (is_primary(state)) {
|
|
if (state->status == STATUS_NORMAL) {
|
|
return process_request(state, raw_message);
|
|
}
|
|
} else {
|
|
if (state->status == STATUS_NORMAL) {
|
|
return send_redirect(state, raw_message);
|
|
}
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_COMMIT_PUT:
|
|
if (is_primary(state)) {
|
|
if (state->status == STATUS_NORMAL) {
|
|
return process_request(state, raw_message);
|
|
}
|
|
} else {
|
|
if (state->status == STATUS_NORMAL) {
|
|
return send_redirect(state, raw_message);
|
|
}
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_PREPARE:
|
|
if (is_primary(state)) {
|
|
// TODO
|
|
} else {
|
|
if (state->status == STATUS_NORMAL) {
|
|
return process_prepare(state, raw_message);
|
|
}
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_PREPARE_OK:
|
|
if (is_primary(state)) {
|
|
if (state->status == STATUS_NORMAL) {
|
|
return process_prepare_ok(state, raw_message);
|
|
}
|
|
} else {
|
|
// Ignore
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_COMMIT:
|
|
if (is_primary(state)) {
|
|
// Ignore
|
|
} else {
|
|
if (state->status == STATUS_NORMAL) {
|
|
return process_commit(state, raw_message);
|
|
}
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_BEGIN_VIEW_CHANGE:
|
|
if (state->status != STATUS_RECOVERY)
|
|
return process_begin_view_change(state, raw_message);
|
|
break;
|
|
case MESSAGE_TYPE_DO_VIEW_CHANGE:
|
|
if (is_primary(state)) {
|
|
if (state->status == STATUS_CHANGE_VIEW) {
|
|
return process_do_view_change(state, raw_message);
|
|
}
|
|
} else {
|
|
// Ignore
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_BEGIN_VIEW:
|
|
if (state->status != STATUS_RECOVERY) {
|
|
return process_begin_view(state, raw_message);
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_RECOVERY:
|
|
if (state->status == STATUS_NORMAL) {
|
|
return process_recovery(state, raw_message);
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_RECOVERY_RESPONSE:
|
|
if (is_primary(state)) {
|
|
// Ignore
|
|
} else {
|
|
if (state->status == STATUS_RECOVERY) {
|
|
return process_recovery_response(state, raw_message);
|
|
}
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_GET_STATE:
|
|
if (is_primary(state)) {
|
|
return process_get_state(state, raw_message);
|
|
} else {
|
|
// Ignore
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_NEW_STATE:
|
|
if (is_primary(state)) {
|
|
// Ignore
|
|
} else {
|
|
return process_new_state(state, raw_message);
|
|
}
|
|
break;
|
|
case MESSAGE_TYPE_STORE_CHUNK:
|
|
return process_store_chunk(state, raw_message);
|
|
case MESSAGE_TYPE_FETCH_CHUNK:
|
|
return process_fetch_chunk(state, raw_message);
|
|
case MESSAGE_TYPE_GET_BLOB:
|
|
return process_get_blob(state, raw_message);
|
|
}
|
|
|
|
return HR_OK;
|
|
}
|
|
|
|
#define POLL_CAPACITY 1024
|
|
|
|
int server_init(void *state_, int argc, char **argv,
|
|
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
|
int *timeout)
|
|
{
|
|
Server *state = state_;
|
|
|
|
Time now = get_current_time();
|
|
if (now == INVALID_TIME) {
|
|
fprintf(stderr, "Node :: Couldn't get current time\n");
|
|
return -1;
|
|
}
|
|
if (now > state->now)
|
|
state->now = now;
|
|
|
|
state->num_nodes = 0;
|
|
|
|
bool self_addr_set = false;
|
|
const char *chunks_path = "chunks";
|
|
for (int i = 1; i < argc; i++) {
|
|
if (!strcmp(argv[i], "--addr")) {
|
|
if (self_addr_set) {
|
|
fprintf(stderr, "Option --addr specified twice\n");
|
|
return -1;
|
|
}
|
|
self_addr_set = true;
|
|
i++;
|
|
if (i == argc) {
|
|
fprintf(stderr, "Option --addr missing value. Usage is --addr <addr>:<port>\n");
|
|
return -1;
|
|
}
|
|
int ret = parse_addr_arg((string) { argv[i], strlen(argv[i]) }, &state->self_addr);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Malformed <addr>:<port> pair for --addr option\n");
|
|
return -1;
|
|
}
|
|
if (state->num_nodes == NODE_LIMIT) {
|
|
fprintf(stderr, "Node limit of %d reached\n", NODE_LIMIT);
|
|
return -1;
|
|
}
|
|
state->node_addrs[state->num_nodes++] = state->self_addr;
|
|
} else if (!strcmp(argv[i], "--peer")) {
|
|
i++;
|
|
if (i == argc) {
|
|
fprintf(stderr, "Option --peer missing value. Usage is --peer <addr>:<port>\n");
|
|
return -1;
|
|
}
|
|
if (state->num_nodes == NODE_LIMIT) {
|
|
fprintf(stderr, "Node limit of %d reached\n", NODE_LIMIT);
|
|
return -1;
|
|
}
|
|
int ret = parse_addr_arg((string) { argv[i], strlen(argv[i]) }, &state->node_addrs[state->num_nodes]);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Malformed <addr>:<port> pair for --peer option\n");
|
|
return -1;
|
|
}
|
|
state->num_nodes++;
|
|
} else if (!strcmp(argv[i], "--chunks")) {
|
|
i++;
|
|
if (i == argc) {
|
|
fprintf(stderr, "Option --chunks missing value. Usage is --chunks <path>\n");
|
|
return -1;
|
|
}
|
|
chunks_path = argv[i];
|
|
} else {
|
|
printf("Ignoring option '%s'\n", argv[i]);
|
|
}
|
|
}
|
|
|
|
addr_sort(state->node_addrs, state->num_nodes);
|
|
|
|
Time deadline = INVALID_TIME;
|
|
|
|
state->view_number = 0;
|
|
state->last_normal_view = 0;
|
|
state->heartbeat = now;
|
|
state->commit_index = 0;
|
|
state->num_future = 0;
|
|
state->state_transfer_pending = false;
|
|
state->state_transfer_time = 0;
|
|
|
|
state->view_change_begin_votes = 0;
|
|
state->view_change_apply_votes = 0;
|
|
state->view_change_old_view = 0;
|
|
state->view_change_commit = 0;
|
|
log_init(&state->view_change_log);
|
|
|
|
state->recovery_votes = 0;
|
|
state->recovery_commit = 0;
|
|
state->recovery_view = 0;
|
|
state->recovery_log_view = 0;
|
|
log_init(&state->recovery_log);
|
|
|
|
int marker_fd = open("vsr_boot_marker", O_RDONLY, 0);
|
|
bool previously_crashed = (marker_fd >= 0);
|
|
if (previously_crashed)
|
|
close(marker_fd);
|
|
if (previously_crashed) {
|
|
state->status = STATUS_RECOVERY;
|
|
state->recovery_nonce = now;
|
|
state->recovery_time = now;
|
|
} else {
|
|
state->status = STATUS_NORMAL;
|
|
}
|
|
log_init(&state->log);
|
|
node_log(state, "INIT", "nodes=%d%s", state->num_nodes, previously_crashed ? " (recovering)" : "");
|
|
|
|
client_table_init(&state->client_table);
|
|
|
|
meta_store_init(&state->metastore);
|
|
if (chunk_store_init(&state->chunk_store, chunks_path) < 0) {
|
|
fprintf(stderr, "Node :: Couldn't initialize chunk store at '%s'\n", chunks_path);
|
|
return -1;
|
|
}
|
|
|
|
state->msys = message_system_init(state->node_addrs, state->num_nodes);
|
|
if (state->msys == NULL) {
|
|
fprintf(stderr, "Node :: Couldn't setup message system\n");
|
|
return -1;
|
|
}
|
|
|
|
int ret = message_system_listen_tcp(state->msys, state->self_addr);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Node :: Couldn't setup TCP listener\n");
|
|
message_system_free(state->msys);
|
|
return -1;
|
|
}
|
|
|
|
if (!previously_crashed) {
|
|
int fd = open("vsr_boot_marker", O_WRONLY | O_CREAT, 0644);
|
|
if (fd >= 0)
|
|
close(fd);
|
|
}
|
|
|
|
if (previously_crashed) {
|
|
node_log(state, "STATUS RECOVERY", "nonce=%llu (crash detected)", (unsigned long long)state->recovery_nonce);
|
|
|
|
RecoveryMessage recovery_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_RECOVERY,
|
|
.length = sizeof(RecoveryMessage),
|
|
},
|
|
.sender_idx = self_idx(state),
|
|
.nonce = state->recovery_nonce,
|
|
};
|
|
broadcast_message(state->msys, self_idx(state),&recovery_message.base);
|
|
node_log(state, "SEND RECOVERY", "to=* nonce=%llu", (unsigned long long)state->recovery_nonce);
|
|
|
|
nearest_deadline(&deadline, state->recovery_time + RECOVERY_TIMEOUT_SEC * 1000000000ULL);
|
|
}
|
|
|
|
*timeout = deadline_to_timeout(deadline, now);
|
|
if (pcap < POLL_CAPACITY) {
|
|
fprintf(stderr, "Node :: Not enough poll() capacity (got %d, needed %d)\n", pcap, POLL_CAPACITY);
|
|
return -1;
|
|
}
|
|
*pnum = message_system_register_events(state->msys, ctxs, pdata, pcap);
|
|
return 0;
|
|
}
|
|
|
|
int server_tick(void *state_, void **ctxs,
|
|
struct pollfd *pdata, int pcap, int *pnum, int *timeout)
|
|
{
|
|
Server *state = state_;
|
|
|
|
state->now = get_current_time();
|
|
if (state->now == INVALID_TIME)
|
|
return -1;
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// NETWORK EVENTS
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
message_system_process_events(state->msys, ctxs, pdata, *pnum);
|
|
|
|
for (void *ptr; (ptr = get_next_message(state->msys)) != NULL; ) {
|
|
|
|
HandlerResult ret = process_message(state, ptr);
|
|
if (ret == HR_OUT_OF_MEMORY)
|
|
return -1;
|
|
|
|
if (ret != HR_DEFERRED)
|
|
consume_message(state->msys, ptr);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// TIME EVENTS
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
Time deadline = INVALID_TIME;
|
|
|
|
if (state->status == STATUS_RECOVERY) {
|
|
Time recovery_deadline = state->recovery_time + RECOVERY_TIMEOUT_SEC * 1000000000ULL;
|
|
if (recovery_deadline <= state->now) {
|
|
node_log_simple(state, "TIMEOUT RECOVERY");
|
|
|
|
RecoveryMessage recovery_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_RECOVERY,
|
|
.length = sizeof(RecoveryMessage),
|
|
},
|
|
.sender_idx = self_idx(state),
|
|
.nonce = state->recovery_nonce,
|
|
};
|
|
broadcast_message(state->msys, self_idx(state),&recovery_message.base);
|
|
node_log(state, "SEND RECOVERY", "to=* nonce=%llu", (unsigned long long)state->recovery_nonce);
|
|
|
|
state->recovery_time = state->now;
|
|
|
|
} else {
|
|
nearest_deadline(&deadline, recovery_deadline);
|
|
}
|
|
} else if (state->status == STATUS_CHANGE_VIEW) {
|
|
|
|
Time view_change_deadline = state->heartbeat + VIEW_CHANGE_TIMEOUT_SEC * 1000000000ULL;
|
|
if (view_change_deadline <= state->now) {
|
|
|
|
node_log_simple(state, "TIMEOUT CHANGE_VIEW");
|
|
|
|
clear_view_change_fields(state);
|
|
|
|
add_vote(&state->view_change_begin_votes, self_idx(state));
|
|
|
|
state->view_number++;
|
|
state->heartbeat = state->now;
|
|
|
|
BeginViewChangeMessage begin_view_change_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_BEGIN_VIEW_CHANGE,
|
|
.length = sizeof(BeginViewChangeMessage),
|
|
},
|
|
.view_number = state->view_number,
|
|
.sender_idx = self_idx(state),
|
|
};
|
|
node_log(state, "SEND BEGIN_VIEW_CHG", "to=* view=%llu", (unsigned long long)state->view_number);
|
|
broadcast_message(state->msys, self_idx(state),&begin_view_change_message.base);
|
|
|
|
} else {
|
|
nearest_deadline(&deadline, view_change_deadline);
|
|
}
|
|
} else {
|
|
assert(state->status == STATUS_NORMAL);
|
|
|
|
if (is_primary(state)) {
|
|
Time heartbeat_deadline = state->heartbeat + HEARTBEAT_INTERVAL_SEC * 1000000000ULL;
|
|
if (heartbeat_deadline <= state->now) {
|
|
|
|
CommitMessage commit_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_COMMIT,
|
|
.length = sizeof(CommitMessage),
|
|
},
|
|
.view_number = state->view_number,
|
|
.sender_idx = self_idx(state),
|
|
.commit_index = state->commit_index,
|
|
};
|
|
broadcast_message(state->msys, self_idx(state),&commit_message.base);
|
|
node_log(state, "SEND COMMIT", "to=* commit=%d", state->commit_index);
|
|
|
|
state->heartbeat = state->now;
|
|
|
|
} else {
|
|
nearest_deadline(&deadline, heartbeat_deadline);
|
|
}
|
|
} else {
|
|
Time death_deadline = state->heartbeat + PRIMARY_DEATH_TIMEOUT_SEC * 1000000000ULL;
|
|
if (death_deadline <= state->now) {
|
|
|
|
node_log_simple(state, "TIMEOUT PRIMARY_DEATH");
|
|
|
|
clear_view_change_fields(state);
|
|
|
|
add_vote(&state->view_change_begin_votes, self_idx(state));
|
|
|
|
state->view_number++;
|
|
state->status = STATUS_CHANGE_VIEW;
|
|
state->heartbeat = state->now;
|
|
|
|
BeginViewChangeMessage begin_view_change_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_BEGIN_VIEW_CHANGE,
|
|
.length = sizeof(BeginViewChangeMessage),
|
|
},
|
|
.view_number = state->view_number,
|
|
.sender_idx = self_idx(state),
|
|
};
|
|
broadcast_message(state->msys, self_idx(state),&begin_view_change_message.base);
|
|
|
|
node_log(state, "SEND BEGIN_VIEW_CHG", "to=* view=%llu", (unsigned long long)state->view_number);
|
|
node_log(state, "STATUS CHANGE_VIEW", "view=%llu", (unsigned long long)state->view_number);
|
|
} else {
|
|
nearest_deadline(&deadline, death_deadline);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (state->state_transfer_pending) {
|
|
|
|
Time st_deadline = state->state_transfer_time + STATE_TRANSFER_TIMEOUT_SEC * 1000000000ULL;
|
|
if (st_deadline <= state->now) {
|
|
node_log(state, "TIMEOUT STATE_TRANSFER", "op=%d", state->log.count);
|
|
|
|
GetStateMessage get_state_message = {
|
|
.base = {
|
|
.version = MESSAGE_VERSION,
|
|
.type = MESSAGE_TYPE_GET_STATE,
|
|
.length = sizeof(GetStateMessage),
|
|
},
|
|
.view_number = state->view_number,
|
|
.op_number = state->log.count,
|
|
.sender_idx = self_idx(state),
|
|
};
|
|
send_message(state->msys,primary_idx(state), &get_state_message.base);
|
|
node_log(state, "SEND GET_STATE", "to=%d op=%d", primary_idx(state), state->log.count);
|
|
|
|
state->state_transfer_time = state->now;
|
|
|
|
} else {
|
|
nearest_deadline(&deadline, st_deadline);
|
|
}
|
|
}
|
|
|
|
*timeout = deadline_to_timeout(deadline, state->now);
|
|
if (pcap < POLL_CAPACITY)
|
|
return -1;
|
|
*pnum = message_system_register_events(state->msys, ctxs, pdata, pcap);
|
|
return 0;
|
|
}
|
|
|
|
int server_free(void *state_)
|
|
{
|
|
Server *state = state_;
|
|
|
|
node_log_simple(state, "CRASHED");
|
|
|
|
log_free(&state->log);
|
|
log_free(&state->recovery_log);
|
|
log_free(&state->view_change_log);
|
|
message_system_free(state->msys);
|
|
client_table_free(&state->client_table);
|
|
meta_store_free(&state->metastore);
|
|
chunk_store_free(&state->chunk_store);
|
|
return 0;
|
|
}
|