You've just arrived at your new internshp at Microoglebook, Inc., and you've received your first project! Your fellow Microoglers have nearly completed work on their Real-Time Cross-Platform Big Data Mining Whosiwhatsit™, and they've left one big task up to you!
The RTCPFDMW™ requires a Key-Value data structure that can be accessed in parallel through an HTTP interface. All of the necessary piping for the server is done, and you are tasked with creating the data structure itself. Your data structure must not only be efficient in both memory usage and runtime (hint: constant time access would work wonders), but also be able to save and load itself from disk.
In order to handle race conditions, your data structure must use the concept of a "revision number." Whenever a request is made to get a key, you must provide the requester with the revision number of the key. To modify the value associated with a key, a requester must provide the data structure with the most updated revision number of the key.
We provide you with two structs, a request struct and a response struct which include the following:
struct jsonreq_t
{
char* key;
char* value;
int rev;
}
struct jsonres_t
{
char* success;
char* value;
int rev;
}
Note that your solution must be self-contained in datastore_control.c, datastore_control.h, datastore.c, and datastore.h. That is to say, you shouldn't create your own files to write your data structure in, nor should you #include any other files within the mp7 folder.
You must implement the three functions in datastore_control.c:
jsonres_t process_request(const char * uri, jsonreq_t request)
void save()
void load()
process_request is the main function of the MP. It will handle all modifications to the state of your data structure. It is important to note that multiple threads may call this function at the same time, so your data structure must be able to handle concurrent requests from it. You may use datastore.h and datastore.c as a separate place to write your data structure. process_request takes two arguments: uri, which defines which action is being taken, and request, which holds the relevant data for the request. You must implement the functionalities for each of the following possible input values for uri:
When uri is equal to "/add", you must do the following:
If the key in the request parameter already exists in your datastructure,
return a jsonres_t struct with the success field pointing to
a string with value "KEY ALREADY EXISTS".
Otherwise, add the key-value pair in request to your data structure
with revision number 1, and return a jsonres_t struct with the
success field pointing to a string with value "true".
When uri is equal to "/update", you must do the following:
The request is inteded to update the value associated with request.key in your
data structure to request.value.
If the key exists in your data structure and the revision number is equal to request.rev, increment
the revision number associated with request.key by 1 and update the value associated
with it to request.value.
For the return jsonres_t, if the update was successful, success should be set to "true" and rev should be set to the current revision number. If the key is not found in the table, success should be set to "KEY DOES NOT EXIST". If the revision number given in the input is not equal to the current revision number of key, this function sets success to "REVISION NUMBER DOES NOT MATCH".
When uri is equal to "/get", you must do the following:
For the return jsonres_t, the success value of this struct is set to "true" if request.key is found in your data structure, and the value successfully retrieved. In this case the value and rev are set to the value and revision number retrieved from your data structure. If the key is not found in the table, success is set to "KEY DOES NOT EXIST".
When uri is equal to "/delete", you must do the following:
if request.key exists in your data structure, remove it.
For the return jsonres_t, success is set to "true" if the key was found in your data structure and its entry successfully removed. If the key could not be found, success is set to "KEY DOES NOT EXIST".
For save(), you must take the current state of your data structure to disk by saving it
as a file in the format of your choosing. We have provided you with a
folder named data in which to store your data structure.
Note that you may only store to disk within the data folder.
Storing to any other locations will result in a 0% score on the MP.
After saving to disk, save must free all heap memory that you allocated.
We may test this with valgrind.
For load(), you must initialized you data structure and restore it's state from the files you saved in the data folder. If the data folder is empty, assume the program is being run for the first time. Note that this is how your program will be tested; We will empty the data folder to "reset" your program inbetween tests.
You may assume that all process_request calls are between a load and save call. For clairifaction, you can look at test-1.c, ... , test-5.c in the testing folder, especially test-3.c.
make clean
make
To get 100% on this MP, you must pass all of the tests supplied in the testing folder and use memory correctly. You can run these tests by running the following:
cd testing
make clean
make
You have passed all the tests if you get the following output for make (worth 100%):
make --quiet quietrun
test-1: SUCCESS
test-2: SUCCESS
test-3: SUCCESS
test-4: SUCCESS
test-5: SUCCESS
You must also use memory correctly for these tests. You can verify that you use memory correctly with valgrind by running the following for [n] = 1,2,...,5:
rm -rf data
mkdir data
valgrind ./test-[n]
The output of each of these must contain the following for full credit:
All heap blocks were freed -- no leaks are possible
ERROR SUMMARY: 0 errors from 0 contexts
You can also test the end-to-end functionality of the RTCPFDMW™ by running the following in the mp7 directory:
make clean
make
./server [PORT]
Where [PORT] is a valid, open port. you can then send and receive JSON object from the server, formatting requests using the following formatting:
{
"key": "[KEY]"
"value": "[VALUE]"
"revision": [REVISION]
}
Requests will be converted to a jsonreq_t with key "[KEY]", value "[VALUE]" and rev [REVISION]. You can make these requests with curl with the following format:
curl -H "Content-Type: application/json" --data '[REQUEST JSON]' http://localhost:[PORT]/[URI]
Here's an example add request for key "get", value "money" for a server running on port 3000:
curl -H "Content-Type: application/json" --data '{"key": "get", "value": "money"}' http://localhost:3000/add
You have the opportunity to earn 2% extra credit if you can beat a a solution made in 400 minutes by TA Paul. You results are posted here. The contest will be constantly updated much like the malloc contest starting later this week. Your score will be normalized with Paul's, with 6/7 of the score coming from runtime and 1/7 of the score coming from maximum memory usage. These numbers will be calculated using getrusage(). If your solution on the due date has a score of < 100.00%, you'll receive the extra credit! Good luck!