/* Must be called after tpclog_iterate_begin has been called on LOG. Attempts * to return the next most recent entry after the entry previously returned * during the current iteration, or, immediately after tpclog_iterate_begin, * the oldest entry in LOG, using malloc()d memory which should later be * free()d. Returns NULL if there is an error or no more recent entry exists * (i.e., all entries have been iterated over). */ logentry_t *tpclog_iterate_next(tpclog_t *log, logentry_t *entry) { char filename[MAX_FILENAME]; int ret; pthread_rwlock_rdlock(&log->lock); if (!tpclog_iterate_has_next(log)) { pthread_rwlock_unlock(&log->lock); return NULL; } sprintf(filename, "%s/%lu%s", log->dirname, log->iterpos++, TPCLOG_FILETYPE); ret = tpclog_load_entry(entry, filename); pthread_rwlock_unlock(&log->lock); return (ret < 0) ? NULL : entry; }
/* Restore SERVER back to the state it should be in, according to the * associated LOG. Must be called on an initialized SERVER. Only restores the * state of the most recent TPC transaction, assuming that all previous actions * have been written to persistent storage. Should restore SERVER to its exact * state; e.g. if SERVER had written into its log that it received a PUTREQ but * no corresponding COMMIT/ABORT, after calling this function SERVER should * again be waiting for a COMMIT/ABORT. This should also ensure that as soon * as a server logs a COMMIT, even if it crashes immediately after (before the * KVStore has a chance to write to disk), the COMMIT will be finished upon * rebuild. The cache need not be the same as before rebuilding. * * Checkpoint 2 only. */ int kvserver_rebuild_state(kvserver_t *server) { if (server == NULL || server->state == TPC_INIT) { return -1; } tpclog_iterate_begin(&server->log); logentry_t *prev = NULL, *next = NULL; while (tpclog_iterate_has_next(&server->log)) { next = tpclog_iterate_next(&server->log); if (next->type == PUTREQ || next->type == DELREQ) prev = next; } if (prev == NULL && next == NULL) { // log was empty return 0; } else if (prev == NULL) { prev = next; } server->msg = (kvmessage_t *) malloc(sizeof(kvmessage_t)); if (server->msg == NULL) return -1; if (next->type == COMMIT) { server->state = TPC_READY; if (prev->type == PUTREQ) { if (rebuild_kvmessage(server, prev, true) == -1) { return -1; } kvserver_put(server, server->msg->key, server->msg->value); } else if (prev->type == DELREQ) { if (rebuild_kvmessage(server, prev, false) == -1) { return -1; } kvserver_del(server, server->msg->key); } } else if (next->type == ABORT) { // might want to avoid commiting very first time with a one-time use bool server->state = TPC_READY; if (prev != NULL && rebuild_kvmessage(server, prev, prev->type == PUTREQ) == -1) return -1; } else { server->state = TPC_WAIT; if (rebuild_kvmessage(server, next, next->type == PUTREQ) == -1) return -1; } return tpclog_clear_log(&server->log); }
/* Handles an incoming kvrequest REQ, and populates RES as a response. REQ and * RES both must point to valid kvrequest_t and kvrespont_t structs, * respectively. Assumes that the request should be handled as a TPC * message. This should also log enough information in the server's TPC log to * be able to recreate the current state of the server upon recovering from * failure. See the spec for details on logic and error messages. */ void tpcfollower_handle_tpc(tpcfollower_t *server, kvrequest_t *req, kvresponse_t *res) { /* TODO: Implement me! */ if (req->type == DELREQ) { if (server->state != TPC_WAIT) { server->state = TPC_WAIT; } else { res->type = ERROR; strcpy(res->body, ERRMSG_INVALID_REQUEST); return; } int del_retval = tpcfollower_del_check(server, req->key); if (del_retval < 0) { res->type = VOTE; if (del_retval == ERR_KEYLEN) { strcpy(res->body, ERRMSG_KEY_LEN); } else if (del_retval == ERR_NOKEY) { strcpy(res->body, ERRMSG_NO_KEY); } else { strcpy(res->body, ERRMSG_GENERIC_ERROR); } } else { tpclog_log(&server->log, DELREQ, req->key, NULL); res->type = VOTE; strcpy(res->body, MSG_COMMIT); } } else if (req->type == PUTREQ) { if (server->state != TPC_WAIT) { server->state = TPC_WAIT; } else { res->type = ERROR; strcpy(res->body, ERRMSG_INVALID_REQUEST); return; } int put_retval = tpcfollower_put_check(server, req->key, req->val); if (put_retval < 0) { res->type = VOTE; if (put_retval == ERR_KEYLEN) { strcpy(res->body, ERRMSG_KEY_LEN); } else if (put_retval == ERR_VALLEN) { strcpy(res->body, ERRMSG_VAL_LEN); } else { strcpy(res->body, ERRMSG_GENERIC_ERROR); } } else { tpclog_log(&server->log, PUTREQ, req->key, req->val); res->type = VOTE; strcpy(res->body, MSG_COMMIT); } } else if (req->type == COMMIT) { server->state = TPC_COMMIT; res->type = ACK; tpclog_iterate_begin(&server->log); logentry_t* entry = malloc(sizeof(logentry_t)); while (tpclog_iterate_has_next(&server->log)) { entry = tpclog_iterate_next(&server->log, entry); char* log_key = malloc(strlen(entry->data)); if (entry->type == DELREQ) { strcpy(log_key, entry->data); tpcfollower_del(server, log_key); } else if (entry->type == PUTREQ) { char* log_val = malloc(entry->length - strlen(entry->data)); strcpy(log_key, entry->data); strcpy(log_val, entry->data + strlen(entry->data) + 1); tpcfollower_put(server, log_key, log_val); } } tpclog_clear_log(&server->log); } else if (req->type == ABORT) { server->state = TPC_ABORT; res->type = ACK; tpclog_clear_log(&server->log); } else if (req->type == GETREQ) { int get_retval = tpcfollower_get(server, req->key, res->body); if (get_retval < 0) { res->type = ERROR; if (get_retval == ERR_KEYLEN) { strcpy(res->body, ERRMSG_KEY_LEN); } else if (get_retval == ERR_NOKEY) { strcpy(res->body, ERRMSG_NO_KEY); } else { strcpy(res->body, ERRMSG_INVALID_REQUEST); } } else { res->type = GETRESP; } } }