/** * @file MMappedUUIDHashTable.cpp * @brief Open-addressing hash table over a memory-mapped file: implementation. * * Implements all methods of @c MMappedUUIDHashTable declared in * @c MMappedUUIDHashTable.h: * - @c MMappedUUIDHashTable(): open/create the backing file and map it. * - @c ~MMappedUUIDHashTable(): sync and unmap. * - @c add(): insert a UUID and assign the next sequential integer. * - @c operator[](): look up an integer by UUID. * - @c sync(): flush dirty pages with @c msync(). * * Internal helpers: * - @c mmap(): map (or remap) @p length bytes of the backing file. * - @c grow(): double the table size and rehash. * - @c find(): locate the slot index for a UUID (or @c NOTHING if absent). * - @c set(): write a key-value pair into the table. */ #include "MMappedUUIDHashTable.h" #include #include #include #include #include #include #include #include #include #include #include MMappedUUIDHashTable::MMappedUUIDHashTable(const char *filename, bool read_only, uint64_t magic_value) { fd=open(filename, O_CREAT|(read_only?O_RDONLY:O_RDWR), 0600); // flawfinder: ignore if(fd==-1) throw std::runtime_error(strerror(errno)); auto size = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); bool empty=false; if(size==0) { empty=true; size=table_t::sizeForLogSize(STARTING_LOG_SIZE); if(ftruncate(fd, size)) throw std::runtime_error(strerror(errno)); } mmap(size, read_only); if(empty) { table->magic = magic_value; table->version = 1; table->elem_size = static_cast(sizeof(value_t)); table->_reserved = 0; table->log_size = table_t::logSizeForSize(size); table->nb_elements = 0; table->next_value = 0; for(unsigned long i=0; icapacity(); ++i) { table->t[i].value = NOTHING; } } else { if(table->magic != magic_value) throw std::runtime_error("ProvSQL mmap: wrong file type (magic mismatch)"); if(table->version != 1) throw std::runtime_error("ProvSQL mmap: unsupported format version " + std::to_string(table->version)); if(table->elem_size != sizeof(value_t)) throw std::runtime_error("ProvSQL mmap: element size mismatch (recompile required)"); } } void MMappedUUIDHashTable::mmap(size_t length, bool read_only) { table = reinterpret_cast(::mmap( NULL, length, PROT_READ|(read_only?0:PROT_WRITE), MAP_SHARED, fd, 0)); if(table == MAP_FAILED) throw std::runtime_error(strerror(errno)); } void MMappedUUIDHashTable::grow() { sync(); std::vector elements; elements.reserve(table->nb_elements); for(unsigned long i=0; icapacity(); ++i) if(table->t[i].value != NOTHING) elements.push_back(table->t[i]); auto new_log_size = table->log_size+1; munmap(table, table_t::sizeForLogSize(table->log_size)); auto new_size = table_t::sizeForLogSize(new_log_size); if(ftruncate(fd, new_size)) throw std::runtime_error(strerror(errno)); mmap(new_size, false); table->log_size = new_log_size; for(unsigned long i=0; icapacity(); ++i) { table->t[i].value = NOTHING; } for(const auto &u: elements) set(u.uuid, u.value); } MMappedUUIDHashTable::~MMappedUUIDHashTable() { munmap(table, table_t::sizeForLogSize(table->log_size)); close(fd); } unsigned long MMappedUUIDHashTable::find(pg_uuid_t u) const { auto k = hash(u); while(table->t[k].value != NOTHING && std::memcmp(&table->t[k].uuid, &u, sizeof(pg_uuid_t))) { k = (k+1) % table->capacity(); } return k; } unsigned long MMappedUUIDHashTable::operator[](pg_uuid_t u) const { auto k = find(u); return table->t[k].value; } std::pair MMappedUUIDHashTable::add(pg_uuid_t u) { auto k = find(u); if(table->t[k].value == NOTHING) { if(table->nb_elements >= MAXIMUM_LOAD_FACTOR * table->capacity()) { grow(); } k = find(u); ++table->nb_elements; table->t[k].uuid = u; return std::make_pair(table->t[k].value = table->next_value++, true); } else return std::make_pair(table->t[k].value, false); } // Only used when growing the table, so no need to check/update nb_elements void MMappedUUIDHashTable::set(pg_uuid_t u, unsigned long i) { table->t[find(u)] = {u, i}; } void MMappedUUIDHashTable::sync() { msync(table, table_t::sizeForLogSize(table->log_size), MS_SYNC); }