feat(lsm): introduce entry handles for concurrent access
							parent
							
								
									0e4e18da6c
								
							
						
					
					
						commit
						f44c512099
					
				|  | @ -21,27 +21,10 @@ typedef enum lsm_attr_type : uint64_t { | |||
| } lsm_attr_type; | ||||
| 
 | ||||
| /**
 | ||||
|  * An entry inside an LSM store. | ||||
|  * | ||||
|  * Each entry consists of the key it's stored behind, zero  or more attributes | ||||
|  * (metadata) and a data field. The data field can be stored on disk or | ||||
|  * in-memory, depending on the size. | ||||
|  * A handle referencing an entry inside a store. Read/write operations from/to | ||||
|  * the entry go through this handle. | ||||
|  */ | ||||
| typedef struct lsm_entry lsm_entry; | ||||
| 
 | ||||
| /**
 | ||||
|  * Allocate and initialize a new lsm_entry object. | ||||
|  * | ||||
|  * @param ptr where to store newly allocated pointer | ||||
|  */ | ||||
| lsm_error lsm_entry_init(lsm_entry **ptr); | ||||
| 
 | ||||
| /**
 | ||||
|  * Deallocate an existing lsm_entry object. | ||||
|  * | ||||
|  * @param entry object to deallocate | ||||
|  */ | ||||
| void lsm_entry_free(lsm_entry *entry); | ||||
| typedef struct lsm_entry_handle lsm_entry_handle; | ||||
| 
 | ||||
| /**
 | ||||
|  * Checks whether the entry has an attribute with the specified type. | ||||
|  | @ -49,7 +32,7 @@ void lsm_entry_free(lsm_entry *entry); | |||
|  * @param entry entry to check | ||||
|  * @param type type of attribute to check for | ||||
|  */ | ||||
| bool lsm_entry_attr_present(lsm_entry *entry, lsm_attr_type type); | ||||
| bool lsm_entry_attr_present(lsm_entry_handle *handle, lsm_attr_type type); | ||||
| 
 | ||||
| /**
 | ||||
|  * Retrieve the contents of an attribute from an entry, if present | ||||
|  | @ -58,7 +41,7 @@ bool lsm_entry_attr_present(lsm_entry *entry, lsm_attr_type type); | |||
|  * @param entry entry to search for | ||||
|  * @param type type of attribute to return | ||||
|  */ | ||||
| lsm_error lsm_entry_attr_get(lsm_str **out, lsm_entry *entry, | ||||
| lsm_error lsm_entry_attr_get(lsm_str **out, lsm_entry_handle *handle, | ||||
|                              lsm_attr_type type); | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -68,7 +51,7 @@ lsm_error lsm_entry_attr_get(lsm_str **out, lsm_entry *entry, | |||
|  * @param type type of attribute to add | ||||
|  * @param data data of attribute; ownership of pointer is taken over | ||||
|  */ | ||||
| lsm_error lsm_entry_attr_insert(lsm_entry *entry, lsm_attr_type type, | ||||
| lsm_error lsm_entry_attr_insert(lsm_entry_handle *handle, lsm_attr_type type, | ||||
|                                 lsm_str *data); | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -79,7 +62,7 @@ lsm_error lsm_entry_attr_insert(lsm_entry *entry, lsm_attr_type type, | |||
|  * @param entry entry to remove attribute from | ||||
|  * @param type type of attribute to remove | ||||
|  */ | ||||
| lsm_error lsm_entry_attr_remove(lsm_str **out, lsm_entry *entry, | ||||
| lsm_error lsm_entry_attr_remove(lsm_str **out, lsm_entry_handle *handle, | ||||
|                                 lsm_attr_type type); | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -104,7 +87,7 @@ lsm_error lsm_store_init(lsm_store **ptr); | |||
|  * @param db_path path to the database file | ||||
|  * @param data_path path to the data directory | ||||
|  */ | ||||
| lsm_error lsm_store_open(lsm_store **ptr, lsm_str *db_path, lsm_str *data_path); | ||||
| lsm_error lsm_store_load(lsm_store **ptr, lsm_str *db_path, lsm_str *data_path); | ||||
| 
 | ||||
| /**
 | ||||
|  * Dealocate an existing lsm_store object. | ||||
|  | @ -114,43 +97,45 @@ lsm_error lsm_store_open(lsm_store **ptr, lsm_str *db_path, lsm_str *data_path); | |||
| void lsm_store_free(lsm_store *store); | ||||
| 
 | ||||
| /**
 | ||||
|  * Retrieve an entry from the store, preparing & locking it for the purpose of | ||||
|  * reading. | ||||
|  * Open a read handle to the given entry. This entry must be properly closed | ||||
|  * using `lsm_store_handle_close`. | ||||
|  * | ||||
|  * @param out pointer to store entry pointer | ||||
|  * @param out pointer to store handle pointer | ||||
|  * @param store store to retrieve entry from | ||||
|  * @param key key to search | ||||
|  */ | ||||
| lsm_error lsm_store_get_read(lsm_entry **out, lsm_store *store, lsm_str *key); | ||||
| lsm_error lsm_store_open_read(lsm_entry_handle **out, lsm_store *store, | ||||
|                               lsm_str *key); | ||||
| 
 | ||||
| /**
 | ||||
|  * Retrieve an entry from the store for the purposes of writing. This | ||||
|  * write-locks the entry. | ||||
|  * Open a write handle to the given entry.  This entry must be properly closed | ||||
|  * using `lsm_store_handle_close`. | ||||
|  * | ||||
|  * @param out pointer to store entry pointer | ||||
|  * @param out pointer to store handle pointer | ||||
|  * @param store store to retrieve entry from | ||||
|  * @param key key to search | ||||
|  */ | ||||
| lsm_error lsm_store_get_write(lsm_entry **out, lsm_store *store, lsm_str *key); | ||||
| lsm_error lsm_store_open_write(lsm_entry_handle **out, lsm_store *store, | ||||
|                                lsm_str *key); | ||||
| 
 | ||||
| /**
 | ||||
|  * Unlock a locked entry. | ||||
|  * Close an open entry handle. | ||||
|  * | ||||
|  * @param store store to unlock entry in | ||||
|  * @param entry entry to unlock | ||||
|  * @param store store the handle's entry is stored in | ||||
|  * @param handle handle to close | ||||
|  */ | ||||
| lsm_error lsm_store_unlock(lsm_store *store, lsm_entry *entry); | ||||
| void lsm_entry_close(lsm_entry_handle *handle); | ||||
| 
 | ||||
| /**
 | ||||
|  * Allocate a new entry in the store with the specified key. The entry returned | ||||
|  * will be write-locked, and should be unlocked after streaming the necessary | ||||
|  * data. | ||||
|  * Insert a new entry into the store, returning a write handle to the newly | ||||
|  * created entry. | ||||
|  * | ||||
|  * @param out pointer to store new entry pointer in | ||||
|  * @param store store to modify | ||||
|  * @param key key to add; ownership of key pointer is taken over | ||||
|  */ | ||||
| lsm_error lsm_store_insert(lsm_entry **out, lsm_store *store, lsm_str *key); | ||||
| lsm_error lsm_store_insert(lsm_entry_handle **out, lsm_store *store, | ||||
|                            lsm_str *key); | ||||
| 
 | ||||
| /**
 | ||||
|  * Append new data to the given entry, which is expected to be in the store. | ||||
|  | @ -162,7 +147,7 @@ lsm_error lsm_store_insert(lsm_entry **out, lsm_store *store, lsm_str *key); | |||
|  * @param entry entry to append data to | ||||
|  * @param data data to append | ||||
|  */ | ||||
| lsm_error lsm_store_data_write(lsm_store *store, lsm_entry *entry, | ||||
|                                lsm_str *data); | ||||
| lsm_error lsm_entry_data_append(lsm_store *store, lsm_entry_handle *handle, | ||||
|                                 lsm_str *data); | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -13,7 +13,14 @@ typedef struct lsm_attr { | |||
|   lsm_str *str; | ||||
| } lsm_attr; | ||||
| 
 | ||||
| struct lsm_entry { | ||||
| /**
 | ||||
|  * An entry inside an LSM store. | ||||
|  * | ||||
|  * Each entry consists of the key it's stored behind, zero  or more attributes | ||||
|  * (metadata) and a data field. The data field can be stored on disk or | ||||
|  * in-memory, depending on the size. | ||||
|  */ | ||||
| typedef struct lsm_entry { | ||||
|   lsm_str *key; | ||||
|   struct { | ||||
|     uint64_t count; | ||||
|  | @ -28,7 +35,21 @@ struct lsm_entry { | |||
|     } value; | ||||
|     bool on_disk; | ||||
|   } data; | ||||
| }; | ||||
| } lsm_entry; | ||||
| 
 | ||||
| /**
 | ||||
|  * Allocate and initialize a new lsm_entry object. | ||||
|  * | ||||
|  * @param ptr where to store newly allocated pointer | ||||
|  */ | ||||
| lsm_error lsm_entry_init(lsm_entry **ptr); | ||||
| 
 | ||||
| /**
 | ||||
|  * Deallocate an existing lsm_entry object. | ||||
|  * | ||||
|  * @param entry object to deallocate | ||||
|  */ | ||||
| void lsm_entry_free(lsm_entry *entry); | ||||
| 
 | ||||
| typedef struct lsm_entry_wrapper { | ||||
|   pthread_rwlock_t lock; | ||||
|  | @ -36,6 +57,14 @@ typedef struct lsm_entry_wrapper { | |||
| } lsm_entry_wrapper; | ||||
| 
 | ||||
| lsm_error lsm_entry_wrapper_init(lsm_entry_wrapper **ptr); | ||||
| void lsm_entry_wrapper_free(lsm_entry_wrapper *wrapper); | ||||
| 
 | ||||
| struct lsm_entry_handle { | ||||
|   lsm_entry_wrapper *wrapper; | ||||
|   FILE *f; | ||||
| }; | ||||
| 
 | ||||
| lsm_error lsm_entry_handle_init(lsm_entry_handle **out); | ||||
| 
 | ||||
| struct lsm_store { | ||||
|   lsm_trie *trie; | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ lsm_error lsm_store_init(lsm_store **ptr) { | |||
|   return lsm_error_ok; | ||||
| } | ||||
| 
 | ||||
| lsm_error lsm_store_open(lsm_store **ptr, lsm_str *db_path, | ||||
| lsm_error lsm_store_load(lsm_store **ptr, lsm_str *db_path, | ||||
|                          lsm_str *data_path) { | ||||
|   lsm_store *store; | ||||
|   LSM_RES(lsm_store_init(&store)); | ||||
|  | @ -42,12 +42,13 @@ lsm_error lsm_store_open(lsm_store **ptr, lsm_str *db_path, | |||
|   return lsm_error_ok; | ||||
| } | ||||
| 
 | ||||
| lsm_error lsm_store_get_read(lsm_entry **out, lsm_store *store, lsm_str *key) { | ||||
| lsm_error lsm_store_open_read(lsm_entry_handle **out, lsm_store *store, | ||||
|                               lsm_str *key) { | ||||
|   lsm_entry_wrapper *wrapper; | ||||
| 
 | ||||
|   LSM_RES(lsm_trie_search((void **)&wrapper, store->trie, key)); | ||||
| 
 | ||||
|   // We don't want to block the thread
 | ||||
|   // Try to get a read lock on the entry's lock
 | ||||
|   if (pthread_rwlock_tryrdlock(&wrapper->lock) != 0) { | ||||
|     return lsm_error_lock_busy; | ||||
|   } | ||||
|  | @ -62,8 +63,17 @@ lsm_error lsm_store_get_read(lsm_entry **out, lsm_store *store, lsm_str *key) { | |||
|     return lsm_error_not_found; | ||||
|   } | ||||
| 
 | ||||
|   lsm_entry_handle *handle; | ||||
|   lsm_error res = lsm_entry_handle_init(&handle); | ||||
| 
 | ||||
|   if (res != lsm_error_ok) { | ||||
|     pthread_rwlock_unlock(&wrapper->lock); | ||||
| 
 | ||||
|     return res; | ||||
|   } | ||||
| 
 | ||||
|   // Open a new file descriptor if needed
 | ||||
|   if (entry->data.on_disk && (entry->data.value.file == NULL)) { | ||||
|   if (entry->data.on_disk) { | ||||
|     char path[store->data_path->len + entry->key->len + 2]; | ||||
|     sprintf(path, "%s/%s", lsm_str_ptr(store->data_path), | ||||
|             lsm_str_ptr(entry->key)); | ||||
|  | @ -71,36 +81,111 @@ lsm_error lsm_store_get_read(lsm_entry **out, lsm_store *store, lsm_str *key) { | |||
|     FILE *f = fopen(path, "rb"); | ||||
| 
 | ||||
|     if (f == NULL) { | ||||
|       free(handle); | ||||
| 
 | ||||
|       return lsm_error_failed_io; | ||||
|     } | ||||
| 
 | ||||
|     entry->data.value.file = f; | ||||
|     handle->f = f; | ||||
|   } | ||||
| 
 | ||||
|   handle->wrapper = wrapper; | ||||
|   *out = handle; | ||||
| 
 | ||||
|   return lsm_error_ok; | ||||
| } | ||||
| 
 | ||||
| lsm_error lsm_store_insert(lsm_entry **out, lsm_store *store, lsm_str *key) { | ||||
| lsm_error lsm_store_open_write(lsm_entry_handle **out, lsm_store *store, | ||||
|                                lsm_str *key) { | ||||
|   lsm_entry_wrapper *wrapper; | ||||
| 
 | ||||
|   LSM_RES(lsm_trie_search((void **)&wrapper, store->trie, key)); | ||||
| 
 | ||||
|   // Try to get a write lock on the entry's lock
 | ||||
|   // TODO make this timeout to not block
 | ||||
|   if (pthread_rwlock_wrlock(&wrapper->lock) != 0) { | ||||
|     return lsm_error_lock_busy; | ||||
|   } | ||||
| 
 | ||||
|   lsm_entry *entry = wrapper->entry; | ||||
| 
 | ||||
|   // While the trie's data field will never be NULL, the actual entry pointer
 | ||||
|   // might be
 | ||||
|   if (entry == NULL) { | ||||
|     pthread_rwlock_unlock(&wrapper->lock); | ||||
| 
 | ||||
|     return lsm_error_not_found; | ||||
|   } | ||||
| 
 | ||||
|   lsm_entry_handle *handle; | ||||
|   lsm_error res = lsm_entry_handle_init(&handle); | ||||
| 
 | ||||
|   if (res != lsm_error_ok) { | ||||
|     pthread_rwlock_unlock(&wrapper->lock); | ||||
| 
 | ||||
|     return res; | ||||
|   } | ||||
| 
 | ||||
|   // Open a new file descriptor if needed
 | ||||
|   if (entry->data.on_disk) { | ||||
|     char path[store->data_path->len + entry->key->len + 2]; | ||||
|     sprintf(path, "%s/%s", lsm_str_ptr(store->data_path), | ||||
|             lsm_str_ptr(entry->key)); | ||||
| 
 | ||||
|     FILE *f = fopen(path, "wb"); | ||||
| 
 | ||||
|     if (f == NULL) { | ||||
|       free(handle); | ||||
| 
 | ||||
|       return lsm_error_failed_io; | ||||
|     } | ||||
| 
 | ||||
|     handle->f = f; | ||||
|   } | ||||
| 
 | ||||
|   handle->wrapper = wrapper; | ||||
|   *out = handle; | ||||
| 
 | ||||
|   return lsm_error_ok; | ||||
| } | ||||
| 
 | ||||
| lsm_error lsm_store_insert(lsm_entry_handle **out, lsm_store *store, | ||||
|                            lsm_str *key) { | ||||
|   // TODO what happens when two inserts to the same key happen at the same time?
 | ||||
|   lsm_entry_wrapper *wrapper; | ||||
|   LSM_RES(lsm_entry_wrapper_init(&wrapper)); | ||||
|   pthread_rwlock_wrlock(&wrapper->lock); | ||||
| 
 | ||||
|   lsm_error res = lsm_trie_insert(store->trie, key, wrapper); | ||||
| 
 | ||||
|   // Check if entry isn't already present in advance
 | ||||
|   if (res != lsm_error_ok) { | ||||
|     lsm_entry_wrapper_free(wrapper); | ||||
| 
 | ||||
|     return res; | ||||
|   } | ||||
| 
 | ||||
|   lsm_entry *entry; | ||||
|   LSM_RES(lsm_entry_init(&entry)); | ||||
| 
 | ||||
|   entry->key = key; | ||||
|   wrapper->entry = entry; | ||||
|   pthread_rwlock_wrlock(&wrapper->lock); | ||||
| 
 | ||||
|   // TODO mem leak if already present
 | ||||
|   LSM_RES(lsm_trie_insert(store->trie, key, wrapper)); | ||||
|   lsm_entry_handle *handle; | ||||
|   LSM_RES(lsm_entry_handle_init(&handle)); | ||||
| 
 | ||||
|   *out = entry; | ||||
|   // No need to set the handle's file, as the entry doesn't have any data yet
 | ||||
|   handle->wrapper = wrapper; | ||||
| 
 | ||||
|   *out = handle; | ||||
| 
 | ||||
|   return lsm_error_ok; | ||||
| } | ||||
| 
 | ||||
| lsm_error lsm_store_data_write(lsm_store *store, lsm_entry *entry, | ||||
|                                lsm_str *data) { | ||||
| lsm_error lsm_entry_data_append(lsm_store *store, lsm_entry_handle *handle, | ||||
|                                 lsm_str *data) { | ||||
|   lsm_entry *entry = handle->wrapper->entry; | ||||
| 
 | ||||
|   uint64_t new_len = entry->data.len + lsm_str_len(data); | ||||
|   const char *data_s = lsm_str_ptr(data); | ||||
| 
 | ||||
|  | @ -124,13 +209,13 @@ lsm_error lsm_store_data_write(lsm_store *store, lsm_entry *entry, | |||
|       sprintf(path, "%s/%s", lsm_str_ptr(store->data_path), | ||||
|               lsm_str_ptr(entry->key)); | ||||
| 
 | ||||
|       FILE *f = fopen(path, "w"); | ||||
|       FILE *f = fopen(path, "ab"); | ||||
| 
 | ||||
|       if (f == NULL) { | ||||
|         return lsm_error_failed_io; | ||||
|       } | ||||
| 
 | ||||
|       entry->data.value.file = f; | ||||
|       handle->f = f; | ||||
|       entry->data.on_disk = true; | ||||
| 
 | ||||
|       // TODO free old buff, write original data to file
 | ||||
|  | @ -141,7 +226,7 @@ lsm_error lsm_store_data_write(lsm_store *store, lsm_entry *entry, | |||
|     // TODO what happens when I/O fails?
 | ||||
|     while (written < data->len) { | ||||
|       written += fwrite(&data_s[written], sizeof(char), data->len - written, | ||||
|                         entry->data.value.file); | ||||
|                         handle->f); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -32,16 +32,40 @@ lsm_error lsm_entry_wrapper_init(lsm_entry_wrapper **ptr) { | |||
|   return lsm_error_ok; | ||||
| } | ||||
| 
 | ||||
| bool lsm_entry_attr_present(lsm_entry *entry, lsm_attr_type type) { | ||||
|   return (entry->attrs.bitmap & type) != 0; | ||||
| void lsm_entry_wrapper_free(lsm_entry_wrapper *wrapper) { free(wrapper); } | ||||
| 
 | ||||
| lsm_error lsm_entry_handle_init(lsm_entry_handle **out) { | ||||
|   lsm_entry_handle *handle = calloc(1, sizeof(lsm_entry_handle)); | ||||
| 
 | ||||
|   if (handle == NULL) { | ||||
|     return lsm_error_failed_alloc; | ||||
|   } | ||||
| 
 | ||||
|   *out = handle; | ||||
| 
 | ||||
|   return lsm_error_ok; | ||||
| } | ||||
| 
 | ||||
| lsm_error lsm_entry_attr_get(lsm_str **out, lsm_entry *entry, | ||||
| void lsm_entry_close(lsm_entry_handle *handle) { | ||||
|   if (handle->f != NULL) { | ||||
|     fclose(handle->f); | ||||
|   } | ||||
| 
 | ||||
|   pthread_rwlock_unlock(&handle->wrapper->lock); | ||||
|   free(handle); | ||||
| } | ||||
| 
 | ||||
| bool lsm_entry_attr_present(lsm_entry_handle *handle, lsm_attr_type type) { | ||||
|   return (handle->wrapper->entry->attrs.bitmap & type) != 0; | ||||
| } | ||||
| 
 | ||||
| lsm_error lsm_entry_attr_get(lsm_str **out, lsm_entry_handle *handle, | ||||
|                              lsm_attr_type type) { | ||||
|   if (!lsm_entry_attr_present(entry, type)) { | ||||
|   if (!lsm_entry_attr_present(handle, type)) { | ||||
|     return lsm_error_not_found; | ||||
|   } | ||||
| 
 | ||||
|   lsm_entry *entry = handle->wrapper->entry; | ||||
|   uint64_t i = 0; | ||||
| 
 | ||||
|   while (entry->attrs.items[i].type != type) { | ||||
|  | @ -53,12 +77,14 @@ lsm_error lsm_entry_attr_get(lsm_str **out, lsm_entry *entry, | |||
|   return lsm_error_ok; | ||||
| } | ||||
| 
 | ||||
| lsm_error lsm_entry_attr_remove(lsm_str **out, lsm_entry *entry, | ||||
| lsm_error lsm_entry_attr_remove(lsm_str **out, lsm_entry_handle *handle, | ||||
|                                 lsm_attr_type type) { | ||||
|   if (!lsm_entry_attr_present(entry, type)) { | ||||
|   if (!lsm_entry_attr_present(handle, type)) { | ||||
|     return lsm_error_not_found; | ||||
|   } | ||||
| 
 | ||||
|   lsm_entry *entry = handle->wrapper->entry; | ||||
| 
 | ||||
|   if (entry->attrs.count == 1) { | ||||
|     *out = entry->attrs.items[0].str; | ||||
| 
 | ||||
|  | @ -99,12 +125,14 @@ lsm_error lsm_entry_attr_remove(lsm_str **out, lsm_entry *entry, | |||
|   return lsm_error_ok; | ||||
| } | ||||
| 
 | ||||
| lsm_error lsm_entry_attr_insert(lsm_entry *entry, lsm_attr_type type, | ||||
| lsm_error lsm_entry_attr_insert(lsm_entry_handle *handle, lsm_attr_type type, | ||||
|                                 lsm_str *data) { | ||||
|   if (lsm_entry_attr_present(entry, type)) { | ||||
|   if (lsm_entry_attr_present(handle, type)) { | ||||
|     return lsm_error_already_present; | ||||
|   } | ||||
| 
 | ||||
|   lsm_entry *entry = handle->wrapper->entry; | ||||
| 
 | ||||
|   lsm_attr *new_attrs = | ||||
|       realloc(entry->attrs.items, (entry->attrs.count + 1) * sizeof(lsm_attr)); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue