Your assignment is to implement a thread-safe key-value table (akin to a hash table), with the keys and values being integers, that works as follows:
key % bucket count, and then the key-value pair is added to an empty place in the bucketBelow, you are provided a header file with declarations of functions and the description of how they have to work. You must not modify the header file. For synchronization you must use the POSIX API.
The solutions will be graded basing on the correctness of the code, use of fine-grained locking and correctness of memory management. Moreover, severe code illegibility, spaghetti code, code bloat and redundancy will lower the grade. Implementing the table in a different manner than the one described on this page will be counted as a fail.
The deadline for the assignment is 05.06.2025 (AoE).
You must send me your solution as a single uncompressed attachment
to an e-mail message with the subject beginning with [OSCP].
Please reach me by e-mail if anything there is not clear for you.
#pragma once #include <limits.h> #include <stdlib.h> struct table_t; /** "INVALID_KEY" may be used internally to mark non-existing items */ #define INVALID_KEY INT_MIN /** "INVALID_VAL" shall be returned if no value was associated with the key */ #define INVALID_VAL INT_MIN /** "INVALID_ARGUMENTS" shall be returned if provided arguments are invalid */ #define INVALID_ARGUMENTS (INVALID_VAL + 1) /** "OUT_OF_MEMORY" shall be returned if the system ran out of memory */ #define OUT_OF_MEMORY (INVALID_VAL + 2) /** Creates a new hash table. * Bucket size (number of items in bucket) stays fixed. * The map shall initially have the number of buckets as provided. * @returns * INVALID_ARGUMENTS if table is NULL or numberOfBuckets is 0 or bucketSize is 0, or * OUT_OF_MEMORY if dynamic memory allocation failed, or * EXIT_SUCCESS. */ int table_init(struct table_t **table, size_t numberOfBuckets, size_t bucketSize); /** Adds or replaces "value" under the "key" to the "table", and if the pointer * "oldValue" is not null, the old value is written to "*oldValue" (if there * was no value associated with the key, INVALID_VAL must be written instead). * If needed, doubles the bucket count to make space for the new element. * @returns * INVALID_ARGUMENTS if table is NULL or key is "INVALID_KEY" or value is "INVALID_VAL", or * OUT_OF_MEMORY if dynamic memory allocation failed, or * EXIT_SUCCESS. */ int table_insert(struct table_t *table, int key, int value, int *oldValue); /** @returns * INVALID_ARGUMENTS if table is NULL or key is "INVALID_KEY", or * INVALID_VAL if the provided key does not exist in the table, or * the value associated with "key" in the "table" */ int table_lookup(struct table_t *table, int key); /** Acts identical to "table_lookup", but additionally erases the key from the * table */ int table_deleteKey(struct table_t *table, int key); /** If the table contains given key, "table_awaitAndDelete" acts identical to * "table_delete", else "table_awaitAndDelete" waits until the given key is * added (by a concurrent thread) to the table and then acts identical to * "table_delete". */ int table_awaitAndDelete(struct table_t *table, int key); /** Deallocates all memory associated with the table. * @returns * INVALID_ARGUMENTS if table is NULL, or * EXIT_SUCCESS. */ int table_destroy(struct table_t *table); struct table_iterator_t; /** Creates a new "table_iterator_t" object and returns it to caller. * The caller must then execute "table_next" until it returns "TABLE_END". * Please read the description of "table_next" for more information. * @returns * NULL if table is NULL, or * address of the new iterator object. */ struct table_iterator_t *table_begin(struct table_t *table); /** "TABLE_END" is returned by "table_next" if all items were iterated over */ #define TABLE_END -1 /** Checks if there is an item that was not yet visited. * * If there is such item, this function sets "*key" to the key and "*value" * to the pointer to the item and returns EXIT_SUCCESS. Note that the value * can be changed via the pointer until subsequent call to "table_next". * * If there is no such item, this function returns "TABLE_END" and destroys * the iterator, freeing all resources associated with it. * * * If a "table_deleteKey" or "table_awaitAndDelete" is called concurrently * (i.e., between the "table_begin" and the last "table_next"), the deleted * key may or may not be iterated over. * * If a "table_insert" is called concurrently (i.e., between the "table_begin" * and the last "table_next"), the added key may or may not be iterated over, * but no item can be iterated over multiple times. * * @returns * INVALID_ARGUMENTS if table_next is NULL, or * TABLE_END if all elements were returned by previous calls to table_next, or * EXIT_SUCCESS if key and *value point to the next item. */ int table_next(struct table_iterator_t *iterator, int *key, int **value);