/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2021 Martin Raiber * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **************************************************************************/ #ifndef _WIN32 #include #endif #include "TransactionalKvStore.h" #include "../urbackupcommon/os_functions.h" #include "../stringtools.h" #include "../Interface/File.h" #include #include "../Interface/Server.h" #include "../Interface/ThreadPool.h" #include "../Interface/Thread.h" #include #include #include #include #include #include "ICompressEncrypt.h" #include "CloudFile.h" #include "../common/data.h" #ifdef HAS_LINUX_MEMORY_FILE #include "LinuxMemFile.h" #endif #ifndef _WIN32 #include #include #include #include #include #endif #include "../urbackupcommon/events.h" #ifdef HAS_FILE_SIZE_CACHE #define FILE_SIZE_CACHE(x) x #else #define FILE_SIZE_CACHE(x) #endif using namespace std::chrono_literals; #ifdef _WIN32 typedef int ssize_t; ssize_t sendfile(IFsFile::os_file_handle out_fd, IFsFile::os_file_handle in_fd, off_t* offset, size_t count) { return -1; } #define POSIX_FADV_RANDOM 1 #endif #define TRACE_CACHESIZE(x) //#define OPEN_DIRECT MODE_RW_DIRECT //#define CREATE_DIRECT MODE_RW_CREATE_DIRECT #define OPEN_DIRECT MODE_RW #define CREATE_DIRECT MODE_RW_CREATE namespace { #ifdef _WIN32 int get_file_fd(IFsFile::os_file_handle h) { return reinterpret_cast(h); } #else int get_file_fd(IFsFile::os_file_handle h) { return h; } #endif const unsigned int max_retry_waittime = 30 * 60 * 1000; const size_t max_retry_n = 20; const size_t retry_log_n = 8; const int64 preload_once_removal_delay_ms = 30000; const int64 max_cachesize_throttle_size = 10LL * 1024 * 1024 * 1024; void retryWait(size_t n) { unsigned int waittime = (std::min)(static_cast(1000.*pow(2., static_cast(n))), (unsigned int)30*60*1000); //30min if(n>max_retry_n) { waittime = max_retry_waittime; } Server->Log("Waiting "+PrettyPrintTime(waittime)); Server->wait(waittime); } char compressed_evicted_dirty = 2; bool sync_link(IBackupFileSystem* cachefs, std::string source, std::string dest) { if (!cachefs->reflinkFile(source, dest + ".tmp")) { return false; } std::unique_ptr f(cachefs->openFile(dest + ".tmp", MODE_RW)); if (f.get() == nullptr) { return false; } if (!f->Sync()) { return false; } f.reset(); return cachefs->rename((dest + ".tmp").c_str(), dest.c_str()); } bool compare_files(IBackupFileSystem* cachefs, std::string filea, IFile* fb) { std::unique_ptr fa(cachefs->openFile(filea, MODE_READ)); if (fa.get() == nullptr) { Server->Log("Could not open " + filea, LL_ERROR); return false; } fb->Seek(0); char bufa[32768]; char bufb[32768]; while (true) { _u32 reada = fa->Read(bufa, 32768); _u32 readb = fb->Read(bufb, 32768); if (reada != readb || memcmp(bufa, bufb, reada) != 0) { Server->Log(filea+" != "+fb->getFilename(), LL_ERROR); return false; } if (reada == 0) { return true; } } } int64 get_compsize_file(IBackupFileSystem* cachefs, const std::string& path) { std::string val; if (cachefs->getXAttr(path, "user.cs", val)) { if (val.size() != sizeof(int64)) { return -1; } int64 ret; memcpy(&ret, val.data(), sizeof(ret)); ret = little_endian(ret); return ret; } return -1; } bool set_compsize_file(IBackupFileSystem* cachefs, const std::string& path, int64 compsize) { compsize = little_endian(compsize); char * buf = reinterpret_cast(&compsize); std::string val(buf, buf+sizeof(compsize)); return cachefs->setXAttr(path, "user.cs", val); } TransactionalKvStore::SCacheVal* cache_get(common::lrucache& lru_cache, const std::string& key, std::unique_lock& lock, bool bring_front = true) { assert(lock.owns_lock()); return lru_cache.get(key, bring_front); } void cache_put(common::lrucache& lru_cache, const std::string& key, TransactionalKvStore::SCacheVal val, std::unique_lock& lock) { assert(lock.owns_lock()); lru_cache.put(key, val); } void cache_put_back(common::lrucache& lru_cache, const std::string& key, TransactionalKvStore::SCacheVal val, std::unique_lock& lock) { assert(lock.owns_lock()); lru_cache.put_back(key, val); } void cache_del(common::lrucache& lru_cache, const std::string& key, std::unique_lock& lock) { assert(lock.owns_lock()); lru_cache.del(key); } std::pair cache_eviction_candidate(common::lrucache& lru_cache, std::unique_lock& lock, size_t skip = 0) { assert(lock.owns_lock()); return lru_cache.eviction_candidate(skip); } std::list >::iterator cache_eviction_iterator_start(common::lrucache& lru_cache, std::unique_lock& lock) { assert(lock.owns_lock()); return lru_cache.eviction_iterator_start(); } std::list >::iterator cache_eviction_iterator_finish(common::lrucache& lru_cache, std::unique_lock& lock) { assert(lock.owns_lock()); return lru_cache.eviction_iterator_finish(); } class Bitmap { public: Bitmap(int64 n, bool init_set) { resize(n, init_set); } void resize(int64 n, bool init_set) { total_size = n; bitmap_size = static_cast(n / 8 + (n % 8 == 0 ? 0 : 1)); data.resize(bitmap_size); if (init_set) { memset(&data[0], 255, bitmap_size); } } void set(int64 i, bool v) { size_t bitmap_byte = (size_t)(i / 8); size_t bitmap_bit = i % 8; unsigned char b = data[bitmap_byte]; if (v == true) b = b | (1 << (7 - bitmap_bit)); else b = b&(~(1 << (7 - bitmap_bit))); data[bitmap_byte] = b; } size_t set_range(int64 start, int64 end, bool v) { size_t set_bits = 0; for (; start0); return has_bit; } size_t count_bits() { size_t set_count = 0; for (int64 i = 0; i data; }; cache_mutex_t* g_cache_mutex = nullptr; } class TransactionalKvStore::SubmitWorker : IThread { public: friend class TransactionalKvStore; SubmitWorker(TransactionalKvStore& kv_store, bool no_compress, bool less_throttle) : kv_store(kv_store), no_compress(no_compress) { } void operator()() { bool background_prio=false; bool slight_background_prio = false; SPrioInfo prio_info; bool put_is_sync = kv_store.online_kv_store->is_put_sync(); while(true) { std::string path; bool do_stop=false; SMemFile* memf = nullptr; std::list::iterator item = kv_store.next_submission_item(no_compress, no_compress, no_compress, path, do_stop, memf); IFsFile* memf_file = memf==nullptr ? nullptr : memf->file.get(); if(do_stop) { break; } if(item->action==SubmissionAction_Working_Delete) { if(background_prio) { os_disable_background_priority(prio_info); background_prio=false; } if (!slight_background_prio) { slight_background_prio = os_enable_prioritize(prio_info, Prio_SlightBackground); } size_t n=0; while(!kv_store.online_kv_store->del(item->keys, item->transid) ) { Server->Log("Error deleting blocks. Retrying...", LL_ERROR); retryWait(n++); if(do_stop) { break; } } } else if(item->action==SubmissionAction_Working_Compress) { if (slight_background_prio) { os_disable_prioritize(prio_info); slight_background_prio = false; } if(!background_prio) { background_prio = os_enable_background_priority(prio_info); } int64 size_diff; int64 dst_size; bool compression_ok = kv_store.compress_item(item->key, item->transid, memf_file, size_diff, dst_size, !item->compressed); kv_store.item_compressed(item, compression_ok, size_diff, dst_size, memf!=nullptr); continue; } else { if(background_prio) { os_disable_background_priority(prio_info); background_prio=false; } if (!slight_background_prio) { slight_background_prio = os_enable_prioritize(prio_info, Prio_SlightBackground); } if(!item->key.empty()) { size_t n=0; int64 compressed_size; assert(memf_file == nullptr || !kv_store.cacheFileExists(path)); unsigned int flags = 0; if (item->compressed) flags |= IOnlineKvStore::PutAlreadyCompressedEncrypted; if (kv_store.online_kv_store->want_put_metadata() && kv_store.num_second_chances_cb != nullptr && kv_store.num_second_chances_cb->is_metadata(item->key)) flags |= IOnlineKvStore::PutMetadata; IFsFile* putf = memf_file; std::unique_ptr putf_holder; while(putf==nullptr) { putf_holder.reset(kv_store.cachefs->openFile(path, MODE_READ)); if(!putf_holder) { std::string syserr = kv_store.cachefs->lastError(); Server->Log("Cannot open file to submit: " + path+". "+ syserr, LL_ERROR); addSystemEvent("online_kv_store", "Cannot open file to submit", "Cannot open file to submit: " + path + ". " + syserr, LL_ERROR); retryWait(n++); } else { putf = putf_holder.get(); } } while(!kv_store.online_kv_store->put(item->key, item->transid, putf, flags, n>retry_log_n, compressed_size) ) { Server->Log("Error submitting dirty data block "+kv_store.hex(item->key)+" transid "+convert(item->transid)+". Retrying...", LL_ERROR); if (kv_store.online_kv_store->fast_write_retry()) { Server->wait(1000); } else { retryWait(n++); } if(do_stop) { break; } } putf_holder.reset(); ++kv_store.total_put_ops; if (!item->compressed && kv_store.comp_percent>0 && kv_store.resubmit_compressed_ratio<1) { if (memf == nullptr) { set_compsize_file(kv_store.cachefs, path, compressed_size); } else { memf->compsize = compressed_size; } } } } if(do_stop) { break; } size_t n=0; while(!kv_store.item_submitted(item, put_is_sync, memf!=nullptr)) { Server->Log("Error setting item to submitted", LL_ERROR); retryWait(n++); if(do_stop) { break; } } } if (background_prio) { os_disable_background_priority(prio_info); } if (slight_background_prio) { os_disable_prioritize(prio_info); } delete this; } private: TransactionalKvStore& kv_store; bool no_compress; bool less_throttle; }; class TransactionalKvStore::RetrievalOperation { public: friend class TransactionalKvStore; RetrievalOperation(std::unique_lock& lock, TransactionalKvStore& kv_store, const std::string& key) : lock(lock), kv_store(kv_store), key(key) { assert(fuse_io_context::is_sync_thread()); kv_store.initiate_retrieval(key); lock.unlock(); } ~RetrievalOperation() { stop(); } void stop() { assert(fuse_io_context::is_sync_thread()); if (lock.owns_lock()) return; lock.lock(); kv_store.finish_retrieval(key); } private: std::unique_lock& lock; TransactionalKvStore& kv_store; const std::string& key; }; class TransactionalKvStore::RetrievalOperationUnlockOnly { public: explicit RetrievalOperationUnlockOnly(std::unique_lock& lock) : lock(lock) { lock.unlock(); } ~RetrievalOperationUnlockOnly() { stop(); } void stop() { if (lock.owns_lock()) return; lock.lock(); } private: std::unique_lock& lock; }; class TransactionalKvStore::RetrievalOperationNoLock { public: friend class TransactionalKvStore; RetrievalOperationNoLock( TransactionalKvStore& kv_store, const std::string& key) : kv_store(kv_store), key(key), running(true) { assert(fuse_io_context::is_sync_thread()); kv_store.initiate_retrieval(key); } ~RetrievalOperationNoLock() { stop(); } void stop() { assert(fuse_io_context::is_sync_thread()); if (!running) return; kv_store.finish_retrieval(key); running = false; } private: TransactionalKvStore& kv_store; const std::string& key; bool running; }; namespace { class ReadOnlyFileWrapper : public IFsFile { public: ReadOnlyFileWrapper(IFsFile* wrapped_file) : wrapped_file(wrapped_file) { } virtual std::string Read( _u32 tr, bool* has_error=nullptr) { return wrapped_file->Read(tr, has_error); } virtual std::string Read(int64 spos, _u32 tr, bool* has_error = nullptr) { return wrapped_file->Read(spos, tr, has_error); } virtual _u32 Read( char* buffer, _u32 bsize, bool* has_error = nullptr) { return wrapped_file->Read(buffer, bsize, has_error); } virtual _u32 Read(int64 spos, char* buffer, _u32 bsize, bool* has_error = nullptr) { return wrapped_file->Read(spos, buffer, bsize, has_error); } virtual _u32 Write( const std::string &tw, bool* has_error = nullptr) { assert(false); return 0; } virtual _u32 Write(int64 spos, const std::string &tw, bool* has_error = nullptr) { assert(false); return 0; } virtual _u32 Write( const char* buffer, _u32 bsize, bool* has_error = nullptr) { assert(false); return 0; } virtual _u32 Write( int64 spos, const char* buffer, _u32 bsize, bool* has_error = nullptr) { assert(false); return 0; } virtual bool Seek( _i64 spos ) { return wrapped_file->Seek(spos); } virtual _i64 Size( void ) { return wrapped_file->Size(); } virtual _i64 RealSize(void) { return wrapped_file->RealSize(); } virtual bool PunchHole( _i64 spos, _i64 size ) { return false; } virtual bool Resize(int64 nsize, bool set_sparse = true) { assert(false); return false; } virtual std::string getFilename( void ) { return wrapped_file->getFilename(); } virtual bool Sync() { assert(false); return false; } virtual void resetSparseExtentIter() { } virtual SSparseExtent nextSparseExtent() { return SSparseExtent(); } virtual IFsFile::os_file_handle getOsHandle(bool release_handle) { return wrapped_file->getOsHandle(); } virtual std::vector getFileExtents(int64 starting_offset, int64 block_size, bool& more_data, unsigned int flags) { return wrapped_file->getFileExtents(starting_offset, block_size, more_data, flags); } virtual IVdlVolCache* createVdlVolCache() override { return nullptr; } virtual int64 getValidDataLength(IVdlVolCache* vol_cache) override { return int64(); } virtual void setCachedSize(int64 size) { assert(false); } virtual void increaseCachedSize(int64 size) { assert(false); } private: IFsFile* wrapped_file; }; } TransactionalKvStore::~TransactionalKvStore() { Server->Log("Shutting down TransactionalKvStore..."); stop(); Server->Log("Deleting TransactionalKvStore..."); for (auto it : read_only_open_files) { Server->destroy(it.second); } std::scoped_lock lock(cache_mutex); for (auto it : open_files) { if (!fd_cache.has_key(it.first)) { std::scoped_lock lock(memfiles_mutex); SMemFile* memfile_nf = memfiles.get(std::make_pair(transid, it.first), false); if (memfile_nf == nullptr || memfile_nf->file.get() != it.second.fd) { assert(dynamic_cast(it.second.fd)==nullptr); Server->destroy(it.second.fd); } } } while (!fd_cache.empty()) { std::pair candidate = fd_cache.evict_one(); assert(dynamic_cast(candidate.second.fd) == nullptr); Server->destroy(candidate.second.fd); } } IFsFile* TransactionalKvStore::get(const std::string& key, BitmapInfo bitmap_present, unsigned int flags, int64 size_hint, int preload_tag) { IFsFile* ret = get_internal(key, bitmap_present, flags, size_hint, preload_tag); if (ret != nullptr) { if (cachefs->filePath(ret) != keypath2(key, transid) && ret->getFilename() != key) { Server->Log("Unexpected file name in ::get. Got " + cachefs->filePath(ret) + " Expected " + keypath2(key, transid), LL_ERROR); abort(); } if (cachefs->filePath(ret) == keypath2(key, transid)) { if ( (cachefs->getFileType(cachefs->filePath(ret)) & EFileType_File)==0) { Server->Log("File does not exist (was deleted) in ::get: "+ cachefs->filePath(ret) +". "+os_last_error_str(), LL_ERROR); Server->Log(std::string(" base path - ") + (cachefs->getFileType("") == 0 ? "does not exist" : "exists"), LL_ERROR); Server->Log("trans_" + convert(transid) + " - " + (cachefs->getFileType("trans_" + convert(transid)) == 0 ? "does not exist" : "exists"), LL_ERROR); Server->Log(ExtractFilePath(cachefs->filePath(ret)) + " - " + (cachefs->getFileType(ExtractFilePath(cachefs->filePath(ret))) == 0 ? "does not exist" : "exists"), LL_ERROR); Server->Log(ExtractFilePath(ExtractFilePath(cachefs->filePath(ret))) + " - " + (cachefs->getFileType(ExtractFilePath(ExtractFilePath(cachefs->filePath(ret)))) == 0 ? "does not exist" : "exists"), LL_ERROR); abort(); } #ifndef NDEBUG std::scoped_lock memfile_lock(memfiles_mutex); assert(memfiles.get(std::make_pair(transid, key))==nullptr); #endif } else { wait_for_del_file(keypath2(ret->getFilename(), transid)); if ((cachefs->getFileType(keypath2(cachefs->filePath(ret), transid)) & EFileType_File) != 0) { Server->Log("File does exist for memfile in ::get: " + keypath2(ret->getFilename(), transid), LL_ERROR); abort(); } #ifndef NDEBUG std::scoped_lock memfile_lock(memfiles_mutex); assert(memfiles.get(std::make_pair(transid, key)) != nullptr); #endif } } return ret; } #ifdef HAS_ASYNC fuse_io_context::io_uring_task TransactionalKvStore::get_async(fuse_io_context& io, const std::string& key, BitmapInfo bitmap_present, unsigned int flags, int64 size_hint, int preload_tag) { bool cache_fd = (flags & Flag::disable_fd_cache) == 0; const bool can_block = (flags & Flag::disable_throttling) == 0; const bool prioritize_read = (flags & Flag::prioritize_read) > 0; const bool read_random = (flags & Flag::read_random) > 0; const bool read_only = (flags & Flag::read_only) > 0; if (can_block) co_await only_memfiles_throttle_async(io, key); std::unique_lock lock(cache_mutex); if (has_new_remaining_gets) { remaining_gets = new_remaining_gets; has_new_remaining_gets = false; } if (!read_only || (remaining_gets == 0 && can_block && cache_get(lru_cache, key, lock, false) == NULL)) { while (remaining_gets == 0 && can_block) { lock.unlock(); co_await io.sleep_ms(1000); lock.lock(); if (has_new_remaining_gets) { remaining_gets = new_remaining_gets; has_new_remaining_gets = false; } } if (remaining_gets != std::string::npos) { if (remaining_gets > 0) { --remaining_gets; } } else { ++unthrottled_gets; } } lock = std::move((co_await wait_for_retrieval_async(io, std::move(lock), key)).lock); if(flags & Flag::preload_once) { Server->Log("Preload add " + hex(key)); preload_once_items[key] = preload_tag; auto it = preload_once_delayed_removal.find(key); if (it != preload_once_delayed_removal.end()) preload_once_delayed_removal.erase(it); } else { auto it = preload_once_items.find(key); if (it != preload_once_items.end()) { Server->Log("Preload removal schedule of " + hex(key)); preload_once_delayed_removal[key] = Server->getTimeMS() + preload_once_removal_delay_ms; } } std::map::iterator it_open_file = open_files.find(key); if (it_open_file != open_files.end()) { if (it_open_file->first != key) abort(); if (cachefs->filePath(it_open_file->second.fd) != keypath2(key, transid) && it_open_file->second.fd->getFilename() != key) { Server->Log("Filename of file from open files list: " + cachefs->filePath(it_open_file->second.fd) + " Expected: " + keypath2(key, transid), LL_ERROR); abort(); } if (!read_only && it_open_file->second.read_only) { ++it_open_file->second.refcount; } else { ++it_open_file->second.refcount; if (read_only) { std::map::iterator wrapper_it = read_only_open_files.find(it_open_file->second.fd); if (wrapper_it != read_only_open_files.end()) { if (cachefs->filePath(wrapper_it->second) != keypath2(key, transid) && wrapper_it->second->getFilename() != key) { Server->Log("Filename of file from read only open files list: " + cachefs->filePath(wrapper_it->second) + " Expected: " + keypath2(key, transid), LL_ERROR); abort(); } co_return wrapper_it->second; } else { ReadOnlyFileWrapper* nf_rdonly = new ReadOnlyFileWrapper(it_open_file->second.fd); read_only_open_files[it_open_file->second.fd] = nf_rdonly; co_return nf_rdonly; } } co_return it_open_file->second.fd; } } else { ++total_hits; } SFdKey* res = NULL; if (it_open_file == open_files.end()) { res = fd_cache.get(key); if (res != NULL) { if (read_only || !res->read_only) { open_files[key] = SFdKey(res->fd, res->fd->Size(), read_only); if (cachefs->filePath(res->fd) != keypath2(key, transid)) { Server->Log("Filename of file from fd cache: " + cachefs->filePath(res->fd) + " Expected: " + keypath2(key, transid), LL_ERROR); abort(); } assert(dynamic_cast(res->fd) == nullptr); if (read_only) { ReadOnlyFileWrapper* nf_rdonly = new ReadOnlyFileWrapper(res->fd); read_only_open_files[res->fd] = nf_rdonly; co_return nf_rdonly; } co_return res->fd; } } } if (!read_only) { std::set::iterator it_untouched = nosubmit_untouched_items.find(key); if (it_untouched != nosubmit_untouched_items.end()) { nosubmit_untouched_items.erase(it_untouched); } } SCacheVal* dirty = cache_get(lru_cache, key, lock); IFsFile* nf = NULL; bool has_initiate_retrieval = false; if (dirty != NULL) { bool new_dirty = false; if (!read_only && !dirty->dirty) { dirty->dirty = true; addDirtyItem(transid, key); new_dirty = true; auto missing_it = missing_items.find(key); if (missing_it != missing_items.end()) { cachefs->deleteFile("missing_" + hex(key)); missing_items.erase(missing_it); } } if (it_open_file != open_files.end()) { if (!read_only) { if (dynamic_cast(it_open_file->second.fd) != nullptr) { assert(it_open_file->second.fd->getFilename() == key); SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key)); } if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } assert(memfile_nf != nullptr); if (memfile_nf->cow) { { if (!has_initiate_retrieval) { initiate_retrieval(key); has_initiate_retrieval = true; } RetrievalOperationUnlockOnly retrieval_operation(lock); while (!(co_await cow_mem_file_async(io, memfile_nf, true)) ) { Server->Log("Waiting for memory for " + keypath2(key, transid) + " -1 ...", LL_INFO); co_await io.sleep_ms(1000); } } it_open_file->second.fd = memfile_nf->file.get(); } } else { assert(cachefs->filePath(it_open_file->second.fd) == keypath2(key, transid)); } nf = it_open_file->second.fd; it_open_file->second.read_only = false; } } else if (res != NULL) { nf = res->fd; assert(dynamic_cast(nf) == nullptr); if (!read_only) { res->read_only = false; } } else { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key)); } if (memfile_nf != nullptr) { if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } if (!read_only && memfile_nf->cow) { if (!has_initiate_retrieval) { initiate_retrieval(key); has_initiate_retrieval = true; } RetrievalOperationUnlockOnly retrieval_operation(lock); while (!(co_await cow_mem_file_async(io, memfile_nf, false)) ) { Server->Log("Waiting for memory for " + keypath2(key, transid) + " -2 ...", LL_INFO); co_await io.sleep_ms(1000); } } nf = memfile_nf->file.get(); cache_fd = false; ++total_memory_hits; } else { if (!has_initiate_retrieval) { initiate_retrieval(key); has_initiate_retrieval = true; } RetrievalOperationUnlockOnly retrieval_operation(lock); nf = co_await cachefs->openFileAsync(io, keypath2(key, transid), OPEN_DIRECT); if (read_random && nf != nullptr) { co_await set_read_random_async(io, nf); } } } if (nf == NULL) { std::string err; std::string msg = "Could not open cached file " + keypath2(key, transid) + ". " + os_last_error_str(); Server->Log(msg, LL_ERROR); err += msg; if (!cachefs->directoryExists(transpath2())) { msg = "Transaction path " + transpath2() + " is offline"; Server->Log(msg, LL_ERROR); err += "\n" + msg; } if (!cachefs->directoryExists(std::string())) { msg = "Cache base path " + cachefs->getName() + " is offline"; Server->Log(msg, LL_ERROR); err += "\n" + msg; } addSystemEvent("cache_err_fatal", "Error opening file on cache device", err, LL_ERROR); abort(); } if (new_dirty) { add_dirty_bytes(transid, key, nf->Size()); } if (it_open_file == open_files.end()) { open_files[key] = SFdKey(nf, nf->Size(), read_only); } } else { assert(it_open_file == open_files.end()); if (!has_initiate_retrieval) { initiate_retrieval(key); has_initiate_retrieval = true; } RetrievalOperationUnlockOnly retrieval_operation(lock); auto kv_retrieve = [key, bitmap_present, flags, size_hint, res, cache_fd, this, prioritize_read]() { SPrioInfo prio_info; bool unprio = false;; if (prioritize_read) { unprio = os_enable_prioritize(prio_info, Prio_SlightPrioritize); } std::unique_lock lock(this->cache_mutex); bool l_cache_fd = cache_fd; IFsFile* nf = get_retrieve(key, bitmap_present, flags, size_hint, res, l_cache_fd, lock); if (unprio) { os_disable_prioritize(prio_info); } return std::make_pair(nf, l_cache_fd); }; auto [nf_fd, n_cache_fd] = co_await io.run_in_threadpool(kv_retrieve, "kv retrieve"); nf = nf_fd; cache_fd = n_cache_fd; } assert(nf != NULL); std::vector cache_free_list; if (cache_fd && it_open_file == open_files.end() && fd_cache_size > 0) { assert(dynamic_cast(nf) == nullptr); SFdKey fd_cache_val(nf, -1, read_only); fd_cache.put(key, fd_cache_val); int retries = static_cast(fd_cache_size - 1); while (fd_cache.size() > fd_cache_size) { std::pair candidate = fd_cache.evict_one(); if (open_files.find(candidate.first) == open_files.end()) { cache_free_list.push_back(candidate.second.fd); } else { fd_cache.put(candidate.first, candidate.second); --retries; if (retries < 0) break; } } } IFsFile* ret = NULL; if (read_only) { if (it_open_file != open_files.end()) { std::map::iterator wrapper_it = read_only_open_files.find(nf); if (wrapper_it != read_only_open_files.end()) { ret = wrapper_it->second; } } if (ret == NULL) { ReadOnlyFileWrapper* nf_rdonly = new ReadOnlyFileWrapper(nf); read_only_open_files[nf] = nf_rdonly; ret = nf_rdonly; } } else { ret = nf; } #ifndef NDEBUG { std::scoped_lock memfile_lock(memfiles_mutex); SMemFile* memfile_nf = memfiles.get(std::make_pair(transid, key), false); if (memfile_nf != nullptr) { if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } assert(nf == memfile_nf->file.get()); if (!read_only) { assert(!memfile_nf->cow); assert(!memfile_nf->evicted); } } } #endif if (has_initiate_retrieval) { finish_retrieval_async(io, std::move(lock), key); } else { lock.unlock(); } for (IFsFile* f : cache_free_list) { assert(dynamic_cast(f) == nullptr); co_await drop_cache_async(io, f); cachefs->closeAsync(io, f); Server->destroy(f); } co_return ret; } #endif void TransactionalKvStore::check_mutex_not_held() { #ifndef NDEBUG if (cache_mutex.has_lock()) abort(); #endif } void g_check_mutex_not_held() { #ifndef NDEBUG if (g_cache_mutex == nullptr) return; if (g_cache_mutex->has_lock()) abort(); #endif } IFsFile* TransactionalKvStore::get_internal( const std::string& key, BitmapInfo bitmap_present, unsigned int flags, int64 size_hint, int preload_tag) { assert(fuse_io_context::is_sync_thread()); bool cache_fd = (flags & Flag::disable_fd_cache) == 0; const bool can_block = (flags & Flag::disable_throttling) == 0; const bool prioritize_read = (flags & Flag::prioritize_read) > 0; const bool read_random = (flags & Flag::read_random) > 0; const bool read_only = (flags & Flag::read_only) > 0; std::unique_lock lock(cache_mutex); if(can_block) only_memfiles_throttle(key, lock); if (has_new_remaining_gets) { remaining_gets = new_remaining_gets; has_new_remaining_gets = false; } if (!read_only || (remaining_gets==0 && can_block && cache_get(lru_cache, key, lock, false)==nullptr ) ) { while (remaining_gets == 0 && can_block) { lock.unlock(); Server->wait(1000); lock.lock(); if (has_new_remaining_gets) { remaining_gets = new_remaining_gets; has_new_remaining_gets = false; } } if (remaining_gets != std::string::npos) { if (remaining_gets > 0) { --remaining_gets; } } else { ++unthrottled_gets; } } wait_for_retrieval(lock, key); if (flags & Flag::preload_once) { Server->Log("Preload add " + hex(key)); preload_once_items[key] = preload_tag; auto it = preload_once_delayed_removal.find(key); if (it != preload_once_delayed_removal.end()) preload_once_delayed_removal.erase(it); } else { auto it = preload_once_items.find(key); if (it != preload_once_items.end()) { Server->Log("Preload removal schedule of " + hex(key)); preload_once_delayed_removal[key] = Server->getTimeMS() + preload_once_removal_delay_ms; } } std::map::iterator it_open_file = open_files.find(key); if(it_open_file!=open_files.end()) { if (it_open_file->first != key) abort(); if (cachefs->filePath(it_open_file->second.fd) != keypath2(key, transid) && it_open_file->second.fd->getFilename() != key) { Server->Log("Filename of file from open files list: " + cachefs->filePath(it_open_file->second.fd) + " Expected: " + keypath2(key, transid), LL_ERROR); abort(); } if(!read_only && it_open_file->second.read_only) { ++it_open_file->second.refcount; } else { ++it_open_file->second.refcount; if(read_only) { std::map::iterator wrapper_it = read_only_open_files.find(it_open_file->second.fd); if(wrapper_it!=read_only_open_files.end()) { if (cachefs->filePath(wrapper_it->second) != keypath2(key, transid) && wrapper_it->second->getFilename() != key) { Server->Log("Filename of file from read only open files list: " + wrapper_it->second->getFilename() + " Expected: " + keypath2(key, transid), LL_ERROR); abort(); } return wrapper_it->second; } else { ReadOnlyFileWrapper* nf_rdonly = new ReadOnlyFileWrapper(it_open_file->second.fd); read_only_open_files[it_open_file->second.fd] = nf_rdonly; return nf_rdonly; } } return it_open_file->second.fd; } } else { ++total_hits; } SFdKey* res=nullptr; if(it_open_file==open_files.end()) { res = fd_cache.get(key); if(res!=nullptr) { if(read_only || !res->read_only) { open_files[key] = SFdKey(res->fd, res->fd->Size(), read_only); if (cachefs->filePath(res->fd) != keypath2(key, transid)) { Server->Log("Filename of file from fd cache: " + cachefs->filePath(res->fd) + " Expected: " + keypath2(key, transid), LL_ERROR); abort(); } assert(dynamic_cast(res->fd) == nullptr); if(read_only) { ReadOnlyFileWrapper* nf_rdonly = new ReadOnlyFileWrapper(res->fd); read_only_open_files[res->fd] = nf_rdonly; return nf_rdonly; } return res->fd; } } } if(!read_only) { std::set::iterator it_untouched = nosubmit_untouched_items.find(key); if(it_untouched!=nosubmit_untouched_items.end()) { nosubmit_untouched_items.erase(it_untouched); } } SCacheVal* dirty = cache_get(lru_cache, key, lock); IFsFile* nf = nullptr; if(dirty!=nullptr) { bool new_dirty = false; if(!read_only && !dirty->dirty ) { dirty->dirty = true; addDirtyItem(transid, key); new_dirty=true; auto missing_it = missing_items.find(key); if (missing_it != missing_items.end()) { cachefs->deleteFile("missing_" + hex(key)); missing_items.erase(missing_it); } } if(it_open_file!=open_files.end()) { if(!read_only) { if (dynamic_cast(it_open_file->second.fd) != nullptr) { assert(it_open_file->second.fd->getFilename() == key); SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key)); } if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } assert(memfile_nf != nullptr); if (memfile_nf->cow) { while (!cow_mem_file(memfile_nf, true)) { RetrievalOperation retrieval_operation(lock, *this, key); Server->Log("Waiting for memory for " + keypath2(key, transid) + " -1 ...", LL_INFO); Server->wait(1000); } it_open_file->second.fd = memfile_nf->file.get(); } } else { assert(it_open_file->second.fd->getFilename() == keypath2(key, transid)); } nf = it_open_file->second.fd; it_open_file->second.read_only = false; } } else if(res!=nullptr) { nf = res->fd; assert(dynamic_cast(nf)==nullptr); if(!read_only) { res->read_only=false; } } else { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key)); } if (memfile_nf != nullptr) { if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } if (!read_only && memfile_nf->cow) { while (!cow_mem_file(memfile_nf, false)) { RetrievalOperation retrieval_operation(lock, *this, key); Server->Log("Waiting for memory for " + keypath2(key, transid) + " -2 ...", LL_INFO); Server->wait(1000); } } nf = memfile_nf->file.get(); cache_fd = false; ++total_memory_hits; } else { RetrievalOperation retrieval_operation(lock, *this, key); nf = cachefs->openFile(keypath2(key, transid), OPEN_DIRECT); if (read_random && nf != nullptr) { set_read_random(nf); } #ifdef HAS_FILE_SIZE_CACHE if (nf != nullptr) nf->setCachedSize(nf->Size()); #endif } } if(nf==nullptr) { std::string err; std::string msg = "Could not open cached file " + keypath2(key, transid) + ". " + cachefs->lastError(); Server->Log(msg, LL_ERROR); err += msg; if (!cachefs->directoryExists(transpath2())) { msg = "Transaction path " + transpath2() + " is offline"; Server->Log(msg, LL_ERROR); err += "\n" + msg; } if (!cachefs->directoryExists("")) { msg = "Cache base path is offline"; Server->Log(msg, LL_ERROR); err += "\n" + msg; } addSystemEvent("cache_err_fatal", "Error opening file on cache device", err, LL_ERROR); abort(); } if(new_dirty) { add_dirty_bytes(transid, key, nf->Size()); } if(it_open_file==open_files.end()) { open_files[key] = SFdKey(nf, nf->Size(), read_only); } } else { assert(it_open_file == open_files.end()); RetrievalOperationNoLock retrieval_operation(*this, key); nf = get_retrieve(key, bitmap_present, flags, size_hint, res, cache_fd, lock); } assert(nf!=nullptr); std::vector cache_free_list; if(cache_fd && it_open_file==open_files.end() && fd_cache_size>0) { assert(dynamic_cast(nf) == nullptr); SFdKey fd_cache_val(nf, -1, read_only); fd_cache.put(key, fd_cache_val); int retries=static_cast(fd_cache_size-1); while(fd_cache.size()>fd_cache_size) { std::pair candidate = fd_cache.evict_one(); if(open_files.find(candidate.first)==open_files.end()) { cache_free_list.push_back(candidate.second.fd); } else { fd_cache.put(candidate.first, candidate.second); --retries; if(retries<0) break; } } } IFsFile* ret = nullptr; if(read_only) { if(it_open_file!=open_files.end()) { std::map::iterator wrapper_it = read_only_open_files.find(nf); if(wrapper_it!=read_only_open_files.end()) { ret = wrapper_it->second; } } if (ret == nullptr) { ReadOnlyFileWrapper* nf_rdonly = new ReadOnlyFileWrapper(nf); read_only_open_files[nf] = nf_rdonly; ret = nf_rdonly; } } else { ret=nf; } #ifndef NDEBUG { std::scoped_lock memfile_lock(memfiles_mutex); SMemFile* memfile_nf = memfiles.get(std::make_pair(transid, key), false); if (memfile_nf != nullptr) { if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } assert(nf == memfile_nf->file.get()); if (!read_only) { assert(!memfile_nf->cow); assert(!memfile_nf->evicted); } } } #endif lock.unlock(); for (IFsFile* f : cache_free_list) { assert(dynamic_cast(f) == nullptr); drop_cache(f); Server->destroy(f); } return ret; } IFsFile* TransactionalKvStore::get_retrieve(const std::string& key, BitmapInfo bitmap_present, unsigned int flags, int64 size_hint, SFdKey* res, bool& cache_fd, std::unique_lock& cache_lock) { const bool read_random = (flags & Flag::read_random) > 0; const bool read_only = (flags & Flag::read_only) > 0; const bool prioritize_read = (flags & Flag::prioritize_read) > 0; IFsFile* nf = nullptr; std::set::iterator del_queue_it = queued_dels.find(key); if (del_queue_it != queued_dels.end()) { queued_dels.erase(del_queue_it); } std::unique_lock lock_evict(submission_mutex); std::map, std::list::iterator >::iterator it = submission_items.find(std::make_pair(transid, key)); while (it != submission_items.end() && (it->second->action == SubmissionAction_Working_Compress || it->second->action == SubmissionAction_Working_Evict)) { lock_evict.unlock(); { RetrievalOperationUnlockOnly retrieval_operation(cache_lock); Server->wait(50); } lock_evict.lock(); it = submission_items.find(std::make_pair(transid, key)); } bool retrieve_file = true; if (it != submission_items.end() && (it->second->action == SubmissionAction_Evict || it->second->action == SubmissionAction_Compress)) { bool submission_dirty = true; if (it->second->action == SubmissionAction_Compress) { submission_dirty = it->second->compressed; } if (it->second->action == SubmissionAction_Evict && it->second->compressed) { retrieve_file = true; } else { retrieve_file = false; } if (it->second->action == SubmissionAction_Evict) { submitted_bytes -= (*it->second).size; } rm_submission_item(it); lock_evict.unlock(); SCacheVal curr_cache_val; { RetrievalOperationUnlockOnly retrieval_operation(cache_lock); if(retrieve_file) { curr_cache_val = cache_val(key, submission_dirty); } else { curr_cache_val = cache_val(key, submission_dirty || !read_only); } } if(retrieve_file) { cache_put(compressed_items, key, curr_cache_val, cache_lock); } else { cache_put(lru_cache, key, curr_cache_val, cache_lock); } if (!retrieve_file) { if (res != nullptr) { nf = res->fd; if (!read_only) { res->read_only = false; } } else { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key)); } if (memfile_nf != nullptr) { if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } assert(!memfile_nf->evicted); if (!read_only && memfile_nf->cow) { while (!cow_mem_file(memfile_nf, false)) { RetrievalOperationUnlockOnly retrieval_operation(cache_lock); Server->Log("Waiting for memory for " + keypath2(key, transid) + " -3 ...", LL_INFO); Server->wait(1000); } } nf = memfile_nf->file.get(); cache_fd = false; } else { RetrievalOperationUnlockOnly retrieval_operation(cache_lock); nf = cachefs->openFile(keypath2(key, transid), OPEN_DIRECT); if (read_random && nf != nullptr) { set_read_random(nf); } #ifdef HAS_FILE_SIZE_CACHE if(nf!=nullptr) nf->setCachedSize(nf->Size()); #endif assert(nf != nullptr); } } open_files[key] = SFdKey(nf, nf->Size(), read_only); if (!submission_dirty && !read_only) { addDirtyItem(transid, key); assert(nf != nullptr); if (nf != nullptr) { add_dirty_bytes(transid, key, nf->Size()); } remove_missing(key); } } } if (retrieve_file) { if(lock_evict.owns_lock()) lock_evict.unlock(); bool not_found = false; IFsFile* online_file = nullptr; bool decompressed_ok = false; bool evicted_dirty = false; SCacheVal curr_cache_val; SCacheVal* compressed_orig = cache_get(compressed_items, key, cache_lock); SCacheVal compressed_src; SCacheVal* compressed = nullptr; if (compressed_orig != nullptr) { compressed = &compressed_src; *compressed = *compressed_orig; not_found = false; int64 size_diff; int64 src_size = 0; IFsFile* memfile = nullptr; if (!(flags & Flag::disable_memfiles)) { memfile = get_mem_file(key, size_hint, read_only); } { bool nosubmit_untouched = nosubmit_untouched_items.find(key) != nosubmit_untouched_items.end(); wait_for_del_file(keypath2(key, transid)); assert(!cacheFileExists(keypath2(key, transid))); RetrievalOperationUnlockOnly retrieval_operation(cache_lock); Server->Log("Decompressing cache item " + keypath2(key, transid) + ".comp ...", LL_INFO); decompressed_ok = decompress_item(key, transid, memfile, size_diff, src_size, with_prev_link && (!compressed->dirty || nosubmit_untouched)); if (compressed->dirty && !decompressed_ok) { addSystemEvent("cache_err", "Cannot decompress dirty cache item on cache", "Cannot decompress dirty cache item at " + keypath2(key, transid) + ".comp. \n\n" "Last error:\n" + extractLastLogErrors(), LL_ERROR); Server->Log("Cannot decompress dirty data block " + keypath2(key, transid) + ".comp", LL_ERROR); abort(); } else if (!decompressed_ok) { retrieval_operation.stop(); delete_item(nullptr, key, true, cache_lock, nosubmit_untouched ? basetrans : 0, 0, DeleteImm::Unlock); } curr_cache_val = cache_val(key, !read_only || compressed->dirty); if (decompressed_ok) { if (with_prev_link && memfile == nullptr) { if (!compressed->dirty || nosubmit_untouched) { std::string path = keypath2(key, basetrans); if (!sync_link(cachefs, keypath2(key, transid), path)) { nosubmit_untouched = false; } } } else { nosubmit_untouched = false; } if (memfile == nullptr) { online_file = cachefs->openFile(keypath2(key, transid), OPEN_DIRECT); if (online_file == nullptr) { std::string syserr = os_last_error_str(); Server->Log("Error opening decompressed file " + keypath2(key, transid) + ". " + syserr, LL_ERROR); addSystemEvent("cache_err_fatal", "Error opening decompressed file on cache", "Error opening decompressed file " + keypath2(key, transid) + ". " + syserr, LL_ERROR); abort(); } else { FILE_SIZE_CACHE(online_file->setCachedSize(online_file->Size())); } } else { online_file = memfile; cache_fd = false; } retrieval_operation.stop(); delete_item(nullptr, key, true, cache_lock, nosubmit_untouched ? basetrans : 0, 0, DeleteImm::Unlock); } } if (!decompressed_ok && memfile != nullptr) { rm_mem_file(nullptr, transid, key, false); memfile = nullptr; } if (decompressed_ok) { ++total_cache_miss_decompress; if (compressed->dirty) { add_dirty_bytes(transid, key, size_diff); } cache_del(compressed_items, key, cache_lock); sub_cachesize(src_size); comp_bytes -= src_size; } } if (!decompressed_ok && (bitmap_present == BitmapInfo::Present || bitmap_present == BitmapInfo::Unknown)) { wait_for_del_file(keypath2(key, transid)); #ifndef NDEBUG wait_for_del_file(keypath2(key, transid) + ".comp"); #endif assert(!cacheFileExists(keypath2(key, transid) + ".comp")); assert(!cacheFileExists(keypath2(key, transid))); RetrievalOperationUnlockOnly retrieval_operation(cache_lock); ++total_cache_miss_backend; bool in_submission; bool logged_in_submission = false; int64 max_transid = -1; do { in_submission = false; { std::scoped_lock lock(submission_mutex); std::scoped_lock dirty_lock(dirty_item_mutex); //TODO: We can copy/decompress directly from the highest transaction here for (std::map >::iterator it = nosubmit_dirty_items.begin(); it != nosubmit_dirty_items.end(); ++it) { if (!it->second.empty()) { auto it_submission = submission_items.find(std::make_pair(it->first, key)); if (it_submission != submission_items.end()) { if (max_transid == -1) { max_transid = online_kv_store->get_transid(key, transid); } if (max_transid > 0 && max_transid > it->first) { if (!logged_in_submission) { Server->Log("Ignoring eviction of item " + hex(key) + " trans " + convert(it->first) + " because max_transid is " + convert(max_transid)); } continue; } bool copy_src = false; std::shared_ptr memf_src; if (max_transid > 0 && !it_submission->second->compressed) { copy_src = true; } in_submission = true; SMemFile* memf; { std::scoped_lock memfile_lock(memfiles_mutex); memf = memfiles.get(std::make_pair(it->first, key), false); if (memf != nullptr && memf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memf->key), LL_ERROR); abort(); } if (copy_src && memf != nullptr) { memf_src = memf->file; } } if (copy_src && memf_src) break; if (copy_src) { keypath2(key, it->first); } if (memf != nullptr) { submission_queue_memfile_first = submission_queue.end(); } //Do not evict non-dirty item after submission if (it_submission->second->keys.empty()) it_submission->second->keys.push_back(std::string()); submission_queue.splice(submission_queue.begin(), submission_queue, it_submission->second); if (!logged_in_submission) { Server->Log("Waiting for eviction of item " + hex(key) + " trans " + convert(it->first) + " to finish before retrieving object..."); logged_in_submission = true; } } } } for (std::map::iterator it = num_dirty_items.begin(); it != num_dirty_items.end(); ++it) { if (it->second > 0) { auto it_submission = submission_items.find(std::make_pair(it->first, key)); if (it_submission != submission_items.end()) { if (max_transid == -1) { max_transid = online_kv_store->get_transid(key, transid); } if (max_transid > 0 && max_transid > it->first) { if (!logged_in_submission) { Server->Log("Ignoring submission of item " + hex(key) + " trans " + convert(it->first) + " because max_transid is " + convert(max_transid)); } continue; } in_submission = true; //Do not evict non-dirty item after submission if (it_submission->second->keys.empty()) it_submission->second->keys.push_back(std::string()); SMemFile* memf; { std::scoped_lock memfile_lock(memfiles_mutex); memf = memfiles.get(std::make_pair(it->first, key), false); if (memf != nullptr && memf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memf->key), LL_ERROR); abort(); } } if (memf != nullptr) { submission_queue_memfile_first = submission_queue.end(); } submission_queue.splice(submission_queue.begin(), submission_queue, it_submission->second); if (!logged_in_submission) { Server->Log("Waiting for submission of item " + hex(key) + " trans " + convert(it->first) + " to finish before retrieving object..."); logged_in_submission = true; } } } } } if (in_submission) { Server->wait(10); } } while (in_submission); IFsFile* memfile = nullptr; if (max_memfile_size > 0 && !(flags & Flag::disable_memfiles)) { std::scoped_lock lock(cache_mutex); memfile = get_mem_file(key, size_hint, read_only); if (memfile != nullptr) { cache_fd = false; } } size_t retry_n = 0; do { not_found = false; IFsFile* online_file_tmpl = memfile; if (online_file_tmpl == nullptr) { std::string online_file_path = keypath2(key, transid); bool path_err = false; if (!cachefs->directoryExists(ExtractFilePath(online_file_path))) { if (!cachefs->createDir(ExtractFilePath(online_file_path))) { std::string syserr = os_last_error_str(); Server->Log("Error creating directory for GET-file with key " + bytesToHex(key) + " path " + ExtractFilePath(online_file_path) + ". " + syserr, LL_ERROR); addSystemEvent("cache_err_retry", "Error creating directory for get file on cache", "Error creating directory for GET-file with key " + bytesToHex(key) + " path " + ExtractFilePath(online_file_path) + ". " + syserr, LL_ERROR); path_err = true; } } if (!path_err) { online_file_tmpl = cachefs->openFile(online_file_path, CREATE_DIRECT); if (online_file_tmpl == nullptr) { std::string syserr = os_last_error_str(); Server->Log("Error opening output file " + online_file_path + ". " + syserr, LL_ERROR); addSystemEvent("online_kv_store", "Error opening output file", "Error opening output file " + online_file_path + ". " + syserr, LL_ERROR); } else { set_cache_file_compression(key, online_file_path); } } } if (online_file_tmpl != nullptr) { online_file = online_kv_store->get(key, transid, prioritize_read, online_file_tmpl, retry_n > retry_log_n, not_found); assert(online_file == nullptr || memfile == nullptr || online_file == memfile); if (online_file == nullptr && online_file_tmpl != memfile) { Server->destroy(online_file_tmpl); cachefs->deleteFile(keypath2(key, transid)); } } if (online_file != nullptr) { FILE_SIZE_CACHE(online_file->setCachedSize(online_file->Size())); } if (online_file == nullptr) { std::string reset_retries = readCacheFile("clouddrive_reset_retries"); size_t retry_max = 12; if (!trim(reset_retries).empty()) { retry_max = watoi(trim(reset_retries)); } if (retry_n + 1 > 10) { Server->Log("Error getting online file " + hex(key) + ". Retrying... Try " + convert(retry_n), LL_ERROR); } else { Server->Log("Error getting online file " + hex(key) + ". Retrying...", LL_WARNING); } if (retry_n > retry_max && cacheFileExists("clouddrive_reset_unreadable")) { Server->Log("CRITICAL: Removing object " + hex(key) + " which cannot be read.", LL_ERROR); online_kv_store->reset(key, transid); } retryWait(++retry_n); } else if (not_found && bitmap_present == BitmapInfo::Present) { if (online_file != nullptr) { std::string reset_retries = readCacheFile("clouddrive_reset_retries"); size_t retry_max = 12; if (!trim(reset_retries).empty()) { retry_max = watoi(trim(reset_retries)); if (retry_max < 2) retry_max = 2; } bool missing_file = cacheFileExists("missing_" + hex(key)); if (retry_n > retry_max - 2 || missing_file) { addSystemEvent("kv_store_critical", "Error getting object", "Error getting online file " + hex(key) + " (not found). Assuming file was deleted.", LL_ERROR + 1); Server->Log("CRITICAL: Error getting online file " + hex(key) + " (not found). Assuming file was deleted. missing_file=" + convert(missing_file), LL_ERROR); not_found = false; if (!missing_file && read_only) { writeToCacheFile("", "missing_" + hex(key)); std::scoped_lock lock(cache_mutex); missing_items.insert(key); } break; } else { if (memfile == nullptr) { std::string fname = cachefs->filePath(online_file); Server->destroy(online_file); cachefs->deleteFile(fname); } online_file = nullptr; } } if (retry_n + 1 > 5) { Server->Log("Error getting online file " + hex(key) + " (not found). Retrying... Try " + convert(retry_n), LL_ERROR); } else { Server->Log("Error getting online file " + hex(key) + " (not found). Retrying...", LL_WARNING); } retryWait(++retry_n); } } while (online_file == nullptr); if (read_random && online_file != nullptr && memfile == nullptr) { set_read_random(online_file); } std::unique_lock sub_lock(submission_mutex); std::set::iterator it_dirty = dirty_evicted_items.find(key); if (it_dirty == dirty_evicted_items.end() && bitmap_present == BitmapInfo::Present) { sub_lock.unlock(); if (with_prev_link && memfile == nullptr) { std::unique_ptr base_file(cachefs->openFile(keypath2(key, basetrans), MODE_READ)); if (base_file.get() == nullptr) { online_file->Sync(); std::string path = keypath2(key, basetrans); if (!cachefs->directoryExists(ExtractFilePath(path))) { if (!cachefs->createDir(ExtractFilePath(path)) && !cachefs->directoryExists(ExtractFilePath(path))) { Server->Log("Error creating directory " + path + " for reflink-file with key " + hexpath(key) + ". " + os_last_error_str(), LL_ERROR); addSystemEvent("cache_err_fatal", "Error creating directory for reflink-file on cache", "Error creating directory " + path + " for reflink-file with key " + hexpath(key) + ". " + os_last_error_str(), LL_ERROR); abort(); } } sync_link(cachefs, keypath2(key, transid), path); } } } else if (it_dirty != dirty_evicted_items.end()) { evicted_dirty = true; dirty_evicted_items.erase(it_dirty); sub_lock.unlock(); } else { sub_lock.unlock(); } curr_cache_val = cache_val(key, !read_only || (compressed != nullptr && compressed->dirty) || evicted_dirty); } else if (!decompressed_ok) { std::string path = keypath2(key, transid); wait_for_del_file(path); IFsFile* memfile = nullptr; if (!(flags & Flag::disable_memfiles)) { memfile = get_mem_file(key, size_hint, read_only); } RetrievalOperationUnlockOnly retrieval_operation(cache_lock); not_found = true; size_t retry_n = 0; do { if (!cachefs->directoryExists(ExtractFilePath(path))) { if (!cachefs->createDir(ExtractFilePath(path))) { std::string syserr = os_last_error_str(); Server->Log("Error creating directory " + path + " for GET-file with key " + hexpath(key) + ". " + syserr, LL_ERROR); addSystemEvent("cache_err_fatal", "Error creating directory for GET-file on cache", "Error creating directory " + path + " for GET-file with key " + hexpath(key) + ". " + syserr, LL_ERROR); abort(); } } if (memfile != nullptr) { online_file = memfile; cache_fd = false; } else { online_file = cachefs->openFile(path, CREATE_DIRECT); if (online_file == nullptr) { Server->Log("Error creating file. Retrying...", LL_WARNING); retryWait(++retry_n); } FILE_SIZE_CACHE(online_file->setCachedSize(0)); set_cache_file_compression(key, path); } } while (online_file == nullptr); if (read_random && online_file != nullptr && memfile == nullptr) { set_read_random(online_file); } curr_cache_val = cache_val(key, !read_only || (compressed != nullptr && compressed->dirty) || evicted_dirty); } if (read_only && not_found) { Server->Log("Not found and read only: " + keypath2(key, transid), LL_WARNING); if (dynamic_cast(online_file) == nullptr) { #ifndef NDEBUG { std::scoped_lock memfile_lock(memfiles_mutex); SMemFile* f = memfiles.get(std::make_pair(transid, key), false); assert(f == nullptr); } #endif Server->destroy(online_file); cachefs->deleteFile(keypath2(key, transid)); } else { #ifndef NDEBUG { std::scoped_lock memfile_lock(memfiles_mutex); SMemFile* f = memfiles.get(std::make_pair(transid, key), false); assert(f != nullptr); } #endif rm_mem_file(nullptr, transid, key, false); } return nullptr; } else if (!decompressed_ok && (!read_only || evicted_dirty)) { addDirtyItem(transid, key); add_dirty_bytes(transid, key, online_file->Size()); remove_missing(key); } else if (decompressed_ok && !read_only && !compressed->dirty) { addDirtyItem(transid, key); add_dirty_bytes(transid, key, online_file->Size()); remove_missing(key); } cache_put(lru_cache, key, curr_cache_val, cache_lock); nf = online_file; open_files[key] = SFdKey(nf, nf->Size(), read_only); add_cachesize(nf->Size()); } return nf; } bool TransactionalKvStore::del( const std::string& key ) { assert(fuse_io_context::is_sync_thread()); std::unique_lock lock(cache_mutex); if(queued_dels.find(key)!=queued_dels.end()) { return true; } Server->Log("Del key="+hex(key)+" transid="+convert(transid), LL_INFO); while (open_files.find(key) != open_files.end() || wait_for_retrieval(lock, key)) { lock.unlock(); Server->wait(1); lock.lock(); } IFsFile* fd = nullptr; bool is_memfile = false; SFdKey* res = fd_cache.get(key); if(res!=nullptr) { if (cachefs->filePath(res->fd) != keypath2(key, transid)) { Server->Log("Fd cache path wrong. Got " + cachefs->filePath(res->fd) + " Expected " + keypath2(key, transid), LL_ERROR); abort(); } fd = res->fd; fd_cache.del(key); } else { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key), false); } if (memfile_nf != nullptr) { if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } fd = memfile_nf->file.get(); is_memfile = true; } else { RetrievalOperation retrieval_operation(lock, *this, key); fd = cachefs->openFile(keypath2(key, transid), MODE_RW); } } bool accounted_for=false; SCacheVal* dirty = cache_get(lru_cache, key, lock, false); SCacheVal* compressed = nullptr; if(dirty!=nullptr) { assert(fd != nullptr); if(dirty->dirty) { removeDirtyItem(transid, key); if(fd!=nullptr) { rm_dirty_bytes(transid, key, fd->Size(), true); sub_cachesize(fd->Size()); } accounted_for=true; } cache_del(lru_cache, key, lock); } else { compressed = cache_get(compressed_items, key, lock, false); if (compressed != nullptr) { assert(fd == nullptr); { RetrievalOperation retrieval_operation(lock, *this, key); fd = cachefs->openFile(keypath2(key, transid) + ".comp", MODE_RW); assert(fd != nullptr); } if (compressed->dirty) { removeDirtyItem(transid, key); if (fd != nullptr) { rm_dirty_bytes(transid, key, fd->Size(), true); sub_cachesize(fd->Size()); } accounted_for = true; } cache_del(compressed_items, key, lock); comp_bytes -= fd->Size(); } } bool delete_it = true; { std::scoped_lock lock_evict(submission_mutex); auto it = submission_items.find(std::make_pair(transid, key)); if (it != submission_items.end() && (it->second->action == SubmissionAction_Evict || it->second->action == SubmissionAction_Compress || it->second->action == SubmissionAction_Working_Compress )) { if(it->second->action == SubmissionAction_Evict) submitted_bytes -= (*it->second).size; if (!accounted_for && (it->second->action == SubmissionAction_Evict || it->second->compressed)) { rm_dirty_bytes(transid, key, it->second->size, true); sub_cachesize(it->second->size); removeDirtyItem(transid, key); accounted_for = true; } else if (!accounted_for) { sub_cachesize(it->second->size); accounted_for = true; } if (it->second->action == SubmissionAction_Working_Compress) { Server->Log("Don't finish compression key=" + hex(key) + " transid=" + convert(transid), LL_INFO); it->second->finish = false; } else { rm_submission_item(it); } } else if (it != submission_items.end() && (it->second->action == SubmissionAction_Working_Evict)) { accounted_for = true; delete_it = false; } } if (!accounted_for && fd!=nullptr) { sub_cachesize(fd->Size()); } if (!is_memfile && fd!=nullptr) { assert(dynamic_cast(fd) == nullptr); Server->destroy(fd); } if (delete_it) { delete_item(nullptr, key, compressed != nullptr, lock, 0, 0, DeleteImm::None, allow_evict ? 0 : transid); } queued_dels.insert(key); if (delete_it) { lock.unlock(); run_del_file_queue(); } return true; } #ifdef HAS_ASYNC fuse_io_context::io_uring_task TransactionalKvStore::del_async(fuse_io_context& io, const std::string key) { assert(fuse_io_context::is_async_thread()); std::unique_lock lock(cache_mutex); if (queued_dels.find(key) != queued_dels.end()) { co_return true; } Server->Log("Del key=" + hex(key) + " transid=" + convert(transid), LL_INFO); bool retry; do { retry = false; while (open_files.find(key) != open_files.end()) { retry = true; lock.unlock(); co_await io.sleep_ms(10); lock.lock(); } auto p = co_await wait_for_retrieval_async(io, std::move(lock), key); lock = std::move(p.lock); if (p.waited) retry = true; } while (retry); IFsFile* fd = NULL; bool is_memfile = false; bool has_initiate_retrieval = false; SFdKey* res = fd_cache.get(key); if (res != NULL) { if (cachefs->filePath(res->fd) != keypath2(key, transid)) { Server->Log("Fd cache path wrong. Got " + cachefs->filePath(res->fd) + " Expected " + keypath2(key, transid), LL_ERROR); abort(); } fd = res->fd; fd_cache.del(key); } else { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key), false); } if (memfile_nf != nullptr) { if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } fd = memfile_nf->file.get(); is_memfile = true; } else { if (!has_initiate_retrieval) { has_initiate_retrieval = true; initiate_retrieval(key); } RetrievalOperationUnlockOnly retrieval_operation(lock); fd = co_await cachefs->openFileAsync(io, keypath2(key, transid), MODE_RW); } } bool accounted_for = false; SCacheVal* dirty = cache_get(lru_cache, key, lock, false); SCacheVal* compressed = NULL; if (dirty != NULL) { assert(fd != NULL); if (dirty->dirty) { removeDirtyItem(transid, key); if (fd != NULL) { rm_dirty_bytes(transid, key, fd->Size(), true); sub_cachesize(fd->Size()); } accounted_for = true; } cache_del(lru_cache, key, lock); } else { compressed = cache_get(compressed_items, key, lock, false); if (compressed != NULL) { assert(fd == NULL); { if (!has_initiate_retrieval) { has_initiate_retrieval = true; initiate_retrieval(key); } RetrievalOperationUnlockOnly retrieval_operation(lock); fd = co_await cachefs->openFileAsync(io, keypath2(key, transid) + ".comp", MODE_RW); assert(fd != NULL); } if (compressed->dirty) { removeDirtyItem(transid, key); if (fd != NULL) { rm_dirty_bytes(transid, key, fd->Size(), true); sub_cachesize(fd->Size()); } accounted_for = true; } cache_del(compressed_items, key, lock); comp_bytes -= fd->Size(); } } bool delete_it = true; { std::scoped_lock lock_evict(submission_mutex); auto it = submission_items.find(std::make_pair(transid, key)); if (it != submission_items.end() && (it->second->action == SubmissionAction_Evict || it->second->action == SubmissionAction_Compress || it->second->action == SubmissionAction_Working_Compress)) { if (it->second->action == SubmissionAction_Evict) submitted_bytes -= (*it->second).size; if (!accounted_for && (it->second->action == SubmissionAction_Evict || it->second->compressed)) { rm_dirty_bytes(transid, key, it->second->size, true); sub_cachesize(it->second->size); removeDirtyItem(transid, key); accounted_for = true; } else if (!accounted_for) { sub_cachesize(it->second->size); accounted_for = true; } if (it->second->action == SubmissionAction_Working_Compress) { Server->Log("Don't finish compression key=" + hex(key) + " transid=" + convert(transid), LL_INFO); it->second->finish = false; } else { rm_submission_item(it); } } else if (it != submission_items.end() && (it->second->action == SubmissionAction_Working_Evict)) { accounted_for = true; delete_it = false; } } if (!accounted_for && fd != NULL) { sub_cachesize(fd->Size()); } if (!is_memfile && fd != nullptr) { assert(dynamic_cast(fd) == nullptr); cachefs->closeAsync(io, fd); Server->destroy(fd); } if (delete_it) { delete_item(&io, key, compressed != NULL, lock, 0, 0, DeleteImm::None, allow_evict ? 0 : transid); } queued_dels.insert(key); if (has_initiate_retrieval) { finish_retrieval_async(io, std::move(lock), key); assert(!cache_mutex.has_lock()); } else { lock.unlock(); } if (delete_it) { co_await run_del_file_queue_async(io); } co_return true; } #endif bool TransactionalKvStore::set_second_chances(const std::string & key, unsigned int chances) { if (chances >= 128) chances = 127; std::unique_lock lock(cache_mutex); SCacheVal* cval = cache_get(lru_cache, key, lock, false); if (cval != nullptr) { cval->chances = chances; return true; } cval = cache_get(compressed_items, key, lock, false); if (cval != nullptr) { cval->chances = chances; return true; } return false; } bool TransactionalKvStore::has_preload_once(const std::string & key) { std::scoped_lock lock(cache_mutex); auto it = preload_once_items.find(key); return it != preload_once_items.end(); } bool TransactionalKvStore::has_item_cached(const std::string & key) { std::unique_lock lock(cache_mutex); auto it = preload_once_items.find(key); if (it != preload_once_items.end()) return true; if (open_files.find(key) != open_files.end()) return true; SCacheVal* dirty = cache_get(lru_cache, key, lock); return dirty!=nullptr; } void TransactionalKvStore::remove_preload_items(int preload_tag) { std::scoped_lock lock(cache_mutex); for(auto it=preload_once_items.begin();it!=preload_once_items.end();) { if (it->second == preload_tag || preload_tag==0) { Server->Log("Preload (1) removal of " + hex(it->first)); auto it_curr = it; ++it; preload_once_items.erase(it_curr); } else { ++it; } } } void TransactionalKvStore::dirty_all() { bool retry = true; while (retry) { retry = false; std::unique_lock lock(cache_mutex); for (std::pair& item : lru_cache.get_list()) { if (!item.second.dirty) { int64 fsize = 0; std::string key = *item.first; std::map::iterator it_open_file = open_files.find(key); SFdKey* res; if (it_open_file != open_files.end()) { if (dynamic_cast(it_open_file->second.fd) != nullptr) { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key), false); } assert(memfile_nf != nullptr); if (memfile_nf->cow) { if (!cow_mem_file(memfile_nf, true)) { retry = true; break; } it_open_file->second.fd = memfile_nf->file.get(); } } fsize = it_open_file->second.fd->Size(); it_open_file->second.read_only = false; } else if ((res = fd_cache.get(key, false)) != nullptr) { fsize = res->fd->Size(); res->read_only = false; } else { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key), false); } if (memfile_nf != nullptr) { if (memfile_nf->cow) { if (!cow_mem_file(memfile_nf, false)) { retry = true; break; } } fsize = memfile_nf->file->Size(); } else { std::unique_ptr nf(cachefs->openFile(keypath2(key, transid), OPEN_DIRECT)); if (nf != nullptr) { fsize = nf->Size(); } } } item.second.dirty = true; std::set::iterator it_untouched = nosubmit_untouched_items.find(key); if (it_untouched != nosubmit_untouched_items.end()) { nosubmit_untouched_items.erase(it_untouched); } addDirtyItem(transid, key); remove_missing(key); add_dirty_bytes(transid, key, fsize); } } if (retry) { lock.unlock(); Server->wait(1000); } } } TransactionalKvStore::TransactionalKvStore(IBackupFileSystem* cachefs, int64 min_cachesize, int64 min_free_size, int64 critical_free_size, int64 throttle_free_size, int64 min_metadata_cache_free, float comp_percent, int64 comp_start_limit, IOnlineKvStore* online_kv_store, const std::string& encryption_key, ICompressEncryptFactory* compress_encrypt_factory, bool verify_cache, float cpu_multiplier, size_t no_compress_mult, bool with_prev_link, bool allow_evict, bool with_submitted_files, float resubmit_compressed_ratio, int64 max_memfile_size, std::string memcache_path, float memory_usage_factor, bool only_memfiles, unsigned int background_comp_method, unsigned int cache_comp, unsigned int meta_cache_comp) : min_cachesize(min_cachesize), min_free_size(min_free_size), critical_free_size(critical_free_size), comp_percent(comp_percent), comp_start_limit(comp_start_limit), throttle_free_size(throttle_free_size), do_stop(false), online_kv_store(online_kv_store), submitted_bytes(0), dirty_bytes(0), compress_encrypt_factory(compress_encrypt_factory), encryption_key(encryption_key), comp_bytes(0), curr_submit_compress_evict(true), remaining_gets(std::string::npos), unthrottled_gets(0), unthrottled_gets_avg(0), do_evict(false), do_evict_starttime(0), submit_bundle_starttime(0), del_file_mutex(Server->createMutex()), del_file_single_mutex(Server->createMutex()), curr_submit_bundle_items(&submit_bundle_items_a), other_submit_bundle_items(&submit_bundle_items_b), with_prev_link(with_prev_link), allow_evict(allow_evict), with_submitted_files(with_submitted_files), regular_submit_bundle_thread(this), throttle_thread(this), evicted_mutex(Server->createMutex()), prio_del_file_cond(Server->createCondition()), resubmit_compressed_ratio(resubmit_compressed_ratio), max_memfile_size(max_memfile_size), memcache_path(memcache_path), memfile_size(0), submitted_memfiles(0), submitted_memfile_size(0), submission_queue_memfile_first(submission_queue.end()), fd_cache_size(1000), submit_bundle_item_mutex(Server->createMutex()), evict_non_dirty_memfiles(false), total_hits(0), total_memory_hits(0), total_cache_miss_backend(0), total_cache_miss_decompress(0), total_dirty_ops(0), total_put_ops(0), metadata_cache_free(-1), min_metadata_cache_free(min_metadata_cache_free), metadata_update_thread(this), compression_starttime(0), num_second_chances_cb(nullptr), only_memfiles(only_memfiles), max_cachesize(LLONG_MAX), background_comp_method(background_comp_method), disable_read_memfiles(false), disable_write_memfiles(false), total_submitted_bytes(0), retrieval_waiters_async(0), retrieval_waiters_sync(0), cachefs(cachefs), cache_comp(cache_comp), meta_cache_comp(meta_cache_comp) { g_cache_mutex = &cache_mutex; cache_lock_file.reset(cachefs->openFile("lock", MODE_RW_CREATE)); if (cache_lock_file.get() == nullptr) { std::string msg = "Error opening cache lock file at " + cachefs->getName() + "/lock . " + os_last_error_str(); Server->Log(msg, LL_ERROR); throw std::runtime_error(msg); } #ifndef _WIN32 { IFsFile::os_file_handle cache_lock_fd = cache_lock_file->getOsHandle(); if (cache_lock_fd != IFsFile::os_file_handle()) { int rc = flock(cache_lock_fd, LOCK_EX | LOCK_NB); if (rc != 0) { std::string msg; if (errno == EWOULDBLOCK) { msg = "Error locking cache lock file at " + cachefs->getName() + "/lock . Another cloud drive may already be active using the same location. " + os_last_error_str(); } else { msg = "Error locking cache lock file at " + cachefs->getName() + "/lock . " + os_last_error_str(); } Server->Log(msg, LL_ERROR); throw std::runtime_error(msg); } } } #endif std::string log_level_override = trim(readCacheFile("log_level_override")); if (!log_level_override.empty()) { int ll = LL_WARNING; if (log_level_override == "debug") ll = LL_DEBUG; else if (log_level_override == "info") ll = LL_INFO; else if (log_level_override == "error") ll = LL_ERROR; else if (log_level_override == "warn") ll = LL_WARNING; else ll = watoi(log_level_override); Server->setLogLevel(ll); std::string log_rotation_files = trim(readCacheFile("log_rotation_files")); if (log_rotation_files.empty()) { Server->setLogRotationFiles(100); } else { Server->setLogRotationFiles(watoi(log_rotation_files)); } } fd_cache_size = (std::max)(static_cast(10), static_cast(memory_usage_factor*fd_cache_size)); setMountStatus("{\"state\": \"update_trans\"}"); update_transactions(); std::unique_lock lock(cache_mutex); int64 maxtrans = set_active_transactions(lock, true); setMountStatus("{\"state\": \"start_trans\"}"); if(maxtrans>=0) { transid = online_kv_store->new_transaction(true); if( transid==0) { throw std::runtime_error("Error requesting new transaction"); } if(!cachefs->createSnapshot("trans_"+std::to_string(maxtrans), "trans_"+std::to_string(transid))) { Server->Log("Error creating new transaction subvolume", LL_ERROR); throw std::runtime_error("Error creating new transaction subvolume"); } else { update_transactions(); clean_snapshot(transid); } basetrans = maxtrans; } else { basetrans = online_kv_store->new_transaction(true); if( basetrans==0) { throw std::runtime_error("Error requesting new transaction"); } if(!cachefs->createSubvol("trans_"+std::to_string(basetrans))) { Server->Log("Error creating initial transaction directory", LL_ERROR); throw std::runtime_error("Error creating initial transaction directory"); } update_transactions(); std::unique_ptr tmp(cachefs->openFile(basepath2()+ cachefs->fileSep() + "commited", MODE_WRITE)); transid = online_kv_store->new_transaction(true); if( transid==0) { throw std::runtime_error("Error requesting new transaction"); } if(!cachefs->createSnapshot("trans_"+std::to_string(basetrans), "trans_"+std::to_string(transid))) { Server->Log("Error creating initial transaction directory -2", LL_ERROR); throw std::runtime_error("Error creating initial transaction directory -2"); } else { update_transactions(); clean_snapshot(transid); } } cleanup(true); setMountStatus("{\"state\": \"enum_cache\"}"); cachesize=0; read_keys(lock, transpath2(), verify_cache); read_missing(); while(!read_dirty_items(lock, basetrans, transid)) { Server->Log("Error reading dirty items. Retrying...", LL_ERROR); Server->wait(10000); } threads.push_back(Server->getThreadPool()->execute(this, "evict worker")); threads.push_back(Server->getThreadPool()->execute(®ular_submit_bundle_thread, "regular submit")); threads.push_back(Server->getThreadPool()->execute(&throttle_thread, "get throttle")); threads.push_back(Server->getThreadPool()->execute(&metadata_update_thread, "meta update")); threads.push_back(Server->getThreadPool()->execute(&memfd_del_thread, "memfd del")); size_t num_cpus = static_cast(os_get_num_cpus()*cpu_multiplier+0.5f); if(num_cpus>10000 || num_cpus<1) { num_cpus = 1; } for(size_t i=0;igetThreadPool()->execute(new TransactionalKvStore::SubmitWorker(*this, false, false), "submit worker")); } for (size_t j = 0; j < no_compress_mult; ++j) { for (size_t i = 0; i < num_cpus; ++i) { threads.push_back(Server->getThreadPool()->execute(new TransactionalKvStore::SubmitWorker(*this, true, j==0 && i==0), "+submit worker")); } } evict_queue_depth = num_cpus + no_compress_mult*num_cpus + 20; compress_queue_depth = num_cpus + 10; } std::string TransactionalKvStore::hex( const std::string& key ) { return bytesToHex(reinterpret_cast(key.c_str()), key.size()); } std::string TransactionalKvStore::hexpath( const std::string& key ) { std::string hexkey = bytesToHex(reinterpret_cast(key.c_str()), key.size()); if(hexkey.size()<=4) { return "s"+cachefs->fileSep() + hexkey; } return hexkey.substr(0, 4) + cachefs->fileSep() + hexkey; } std::string TransactionalKvStore::transpath2() { return "trans_" + convert(transid); } void TransactionalKvStore::operator()() { int64 total_cache_size = cachefs->totalSpace(); if (critical_free_size > total_cache_size) { critical_free_size = total_cache_size - 300LL * 1024 * 1024; } if (throttle_free_size > total_cache_size) { throttle_free_size = total_cache_size - 500LL * 1024 * 1024; } size_t last_n_eviction = 0; size_t last_n_compression = 0; size_t last_n_chances = 0; bool last_evict_stop_prem = false; bool last_compress_stop_prem = false; bool has_del_files = false; bool last_only_chances = true; while(true) { if (has_del_files) { has_del_files = false; run_del_file_queue(); } Server->wait(1000); int64 orig_free_space = cache_free_space(); int64 free_space = orig_free_space; std::unique_lock lock(cache_mutex); if(do_stop) { return; } if(!preload_once_delayed_removal.empty()) { int64 ctime = Server->getTimeMS(); for (auto it =preload_once_delayed_removal.begin();it!=preload_once_delayed_removal.end();) { if (it->second < ctime) { auto it_rm = preload_once_items.find(it->first); if (it_rm != preload_once_items.end()) { Server->Log("Preload (2) removal of " + hex(it_rm->first)); preload_once_items.erase(it_rm); } auto it_curr = it; ++it; preload_once_delayed_removal.erase(it_curr); } else ++it; } } if (curr_submit_compress_evict) { if (evict_non_dirty_memfiles) { evict_non_dirty_memfiles = false; if (evict_memfiles(lock, false)) { has_del_files = true; } } if (evict_memfiles(lock, true)) { has_del_files = true; } } do_evict = false; bool evicting = false; bool do_start_compress = true; size_t curr_evict_n = 0; size_t curr_last_n_eviction = last_n_eviction; size_t curr_last_n_compression = last_n_compression; size_t curr_last_n_chances = last_n_chances; bool curr_last_evict_stop_prem = last_evict_stop_prem; bool curr_last_compress_stop_prem = last_compress_stop_prem; bool curr_last_only_chances = last_only_chances; last_n_eviction = 0; last_n_compression = 0; last_n_chances = 0; last_evict_stop_prem = false; last_compress_stop_prem = false; last_only_chances = true; bool curr_doubled = false; int64 freed_space = 0; bool evict_use_chances = true; if (curr_last_evict_stop_prem && curr_last_n_chances>0 && curr_last_only_chances) { evict_use_chances = false; } common::lrucache* evict_target_cache = &lru_cache; if (comp_bytes>0) { evict_target_cache = &compressed_items; } std::list >::iterator evict_it; int64 orig_cachesize = cachesize; int64 curr_cachesize = orig_cachesize; int64 max_cachesize_lower = (std::max)(min_cachesize, max_cachesize - max_cachesize_throttle_size*2); while( allow_evict && ( (free_space=min_cachesize) || free_spacemax_cachesize_lower) && curr_submit_compress_evict ) { if (!do_evict) { do_evict = true; evict_it = cache_eviction_iterator_start(*evict_target_cache, lock); if (evict_it == cache_eviction_iterator_finish(*evict_target_cache, lock)) { break; } --evict_it; } size_t n_evicting = 0; { std::scoped_lock submission_lock(submission_mutex); do_evict_starttime = Server->getTimeMS(); for (std::map, std::list::iterator >::iterator it = submission_items.begin(); it != submission_items.end(); ++it) { if (it->second->action == SubmissionAction_Evict || it->second->action == SubmissionAction_Dirty) { ++n_evicting; if (n_evicting > evict_queue_depth) { do_start_compress = false; break; } } } } if (n_evicting == 0 && curr_last_n_eviction>0 && !curr_last_evict_stop_prem && evict_queue_depth<1000000 && !curr_doubled) { Server->Log("Doubling eviction queue depth from " + convert(evict_queue_depth) + " to " + convert(evict_queue_depth * 2), LL_WARNING); evict_queue_depth *= 2; curr_doubled = true; } std::vector >::iterator> cache_move_front; bool used_chance; if(n_evictingLog("Starting eviction of cache items... " "free_space="+PrettyPrintBytes(free_space) +" min_free_size="+PrettyPrintBytes(min_free_size) +" cachesize="+PrettyPrintBytes(cachesize) +" min_cachesize="+PrettyPrintBytes(min_cachesize) +(max_cachesize!=LLONG_MAX ? (" max_cachesize="+PrettyPrintBytes(max_cachesize)+" max_cachesize_lower="+PrettyPrintBytes(max_cachesize_lower)) : "" ) +" freed_space="+PrettyPrintBytes(freed_space), LL_INFO); if(!evict_one(lock, false, false, evict_it, *evict_target_cache, evict_use_chances, freed_space, has_del_files, cache_move_front, used_chance)) { evict_move_front(*evict_target_cache, cache_move_front); last_evict_stop_prem = true; break; } else if (!used_chance) { last_only_chances = false; ++last_n_eviction; evicting = true; } else { ++last_n_chances; } } else { if (!evict_one(lock, true, true, evict_it, *evict_target_cache, evict_use_chances, freed_space, has_del_files, cache_move_front, used_chance)) { evict_move_front(*evict_target_cache, cache_move_front); last_evict_stop_prem = true; break; } else if (!used_chance) { last_only_chances = false; evicting = true; } else { ++last_n_chances; } } evict_move_front(*evict_target_cache, cache_move_front); free_space = orig_free_space - freed_space; curr_cachesize = orig_cachesize - freed_space; if (evicting) { ++curr_evict_n; if (curr_evict_n>2000) { last_evict_stop_prem = true; break; } } } if (do_evict && free_space >= min_free_size && free_space >= critical_free_size && free_space >= throttle_free_size && cachesize <= max_cachesize_lower) { do_evict = false; } float curr_comp_pc = ((float)comp_bytes)/cachesize; if( !evicting && do_start_compress && comp_percent>0 && curr_comp_pccomp_start_limit && curr_submit_compress_evict && free_space>critical_free_size && compression_starttime<=Server->getTimeMS()) { std::scoped_lock submission_lock(submission_mutex); bool curr_submitting=false; for(std::map, std::list::iterator >::iterator it=submission_items.begin(); it!=submission_items.end();++it) { if(it->second->action!=SubmissionAction_Compress && it->second->action!=SubmissionAction_Working_Compress) { curr_submitting=true; break; } } if(!curr_submitting) { size_t n_compressing=0; for(std::map, std::list::iterator >::iterator it=submission_items.begin(); it!=submission_items.end();++it) { if(it->second->action==SubmissionAction_Compress ) { ++n_compressing; } } if (n_compressing == 0 && curr_last_n_compression>0 && !curr_last_compress_stop_prem && compress_queue_depth<1000000 && !curr_doubled) { Server->Log("Doubling compression queue depth from " + convert(compress_queue_depth) + " to " + convert(compress_queue_depth * 2), LL_WARNING); compress_queue_depth *= 2; curr_doubled = true; } std::list >::iterator compress_it = cache_eviction_iterator_start(lru_cache, lock); if (compress_it != cache_eviction_iterator_finish(lru_cache, lock)) { --compress_it; for (; n_compressing < compress_queue_depth; ++n_compressing) { Server->Log("Starting compression of cache items... curr_comp_pc=" + convert(curr_comp_pc) + " comp_percent=" + convert(comp_percent) + " cachesize=" + PrettyPrintBytes(cachesize) + " comp_start_limit=" + PrettyPrintBytes(comp_start_limit), LL_INFO); if (!compress_one(lock, compress_it)) { last_compress_stop_prem = true; break; } else { ++last_n_compression; } } } } } } } bool TransactionalKvStore::read_dirty_items(std::unique_lock& cache_lock, int64 transaction_id, int64 attibute_to_trans_id) { std::unique_ptr nosubmit(cachefs->openFile("trans_"+convert(transaction_id)+ cachefs->fileSep() + "dirty.nosubmit", MODE_READ)); if(nosubmit.get()) { Server->Log("Transaction "+convert(attibute_to_trans_id)+" basetrans "+convert(transaction_id)+ " was not submitted. Marking items as dirty...", LL_INFO); bool ret = read_from_deleted_file("trans_"+convert(transaction_id)+ cachefs->fileSep() + "deleted", attibute_to_trans_id, false); ret = ret && read_from_dirty_file(cache_lock, "trans_"+convert(transaction_id)+cachefs->fileSep() + "dirty", attibute_to_trans_id, false, transaction_id); return ret; } return true; } void TransactionalKvStore::release( const std::string& key ) { std::unique_lock lock(cache_mutex); std::map::iterator it = open_files.find(key); if (it->first != key) { abort(); } if (cachefs->filePath(it->second.fd) != keypath2(key, transid) && it->second.fd->getFilename() != key) { Server->Log("Unexpected fn in release. Expected " + keypath2(key, transid) + " Got " + cachefs->filePath(it->second.fd), LL_ERROR); abort(); } if (dynamic_cast(it->second.fd) == nullptr) { if ((cachefs->getFileType(cachefs->filePath(it->second.fd)) & EFileType_File) == 0) { Server->Log("File does not exist (was deleted) in ::release: " + cachefs->filePath(it->second.fd), LL_ERROR); abort(); } #ifndef NDEBUG std::scoped_lock memfile_lock(memfiles_mutex); assert(memfiles.get(std::make_pair(transid, key)) == nullptr); #endif } else { wait_for_del_file(keypath2(it->second.fd->getFilename(), transid)); if ((os_get_file_type(keypath2(it->second.fd->getFilename(), transid)) & EFileType_File) != 0) { Server->Log("File does does exist for memfile in ::release: " + keypath2(it->second.fd->getFilename(), transid), LL_ERROR); abort(); } #ifndef NDEBUG std::scoped_lock memfile_lock(memfiles_mutex); assert(memfiles.get(std::make_pair(transid, key)) != nullptr); #endif } assert(it!=open_files.end()); --it->second.refcount; if(it->second.refcount>0) { return; } int64 fsize = it->second.fd->Size(); if (fsize != it->second.size) { add_dirty_bytes(transid, key, fsize - it->second.size); add_cachesize(fsize - it->second.size); } std::map::iterator it_rdonly = read_only_open_files.find(it->second.fd); if(it_rdonly!=read_only_open_files.end()) { if (it_rdonly->first != it->second.fd) abort(); Server->destroy(it_rdonly->second); read_only_open_files.erase(it_rdonly); } IFsFile* close_fd = nullptr; std::shared_ptr close_shared_fd; if(!fd_cache.has_key(key)) { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key), false); } if (memfile_nf != nullptr && memfile_nf->file.get() != it->second.fd) { Server->Log("Memfile fd differs from open_files fd " + it->second.fd->getFilename(), LL_WARNING); } if (memfile_nf==nullptr || (memfile_nf->file.get() != it->second.fd && memfile_nf->old_file.get()!=it->second.fd) ) { assert(dynamic_cast(it->second.fd) == nullptr); close_fd = it->second.fd; } if (memfile_nf != nullptr) { if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } memfile_nf->old_file.swap(close_shared_fd); } } open_files.erase(it); lock.unlock(); if (close_fd != nullptr) { Server->destroy(close_fd); } } #ifdef HAS_ASYNC fuse_io_context::io_uring_task TransactionalKvStore::release_async(fuse_io_context& io, const std::string& key) { std::unique_lock lock(cache_mutex); std::map::iterator it = open_files.find(key); #ifndef NDEBUG if (it == open_files.end()) { Server->Log("Key not found in release_async in open_files. Key " + keypath2(key, transid), LL_ERROR); abort(); } if (it->first != key) { abort(); } if (cachefs->filePath(it->second.fd) != keypath2(key, transid) && it->second.fd->getFilename() != key) { Server->Log("Unexpected fn in release. Expected " + keypath2(key, transid) + " Got " + it->second.fd->getFilename(), LL_ERROR); abort(); } if (dynamic_cast(it->second.fd) == nullptr) { std::scoped_lock memfile_lock(memfiles_mutex); assert(memfiles.get(std::make_pair(transid, key)) == nullptr); } else { std::scoped_lock memfile_lock(memfiles_mutex); assert(memfiles.get(std::make_pair(transid, key)) != nullptr); } #endif assert(it != open_files.end()); --it->second.refcount; if (it->second.refcount > 0) { co_return; } int64 fsize = it->second.fd->Size(); if (fsize != it->second.size) { assert(!it->second.read_only); add_dirty_bytes(transid, key, fsize - it->second.size); add_cachesize(fsize - it->second.size); } std::map::iterator it_rdonly = read_only_open_files.find(it->second.fd); if (it_rdonly != read_only_open_files.end()) { if (it_rdonly->first != it->second.fd) abort(); Server->destroy(it_rdonly->second); read_only_open_files.erase(it_rdonly); } IFsFile* close_fd = nullptr; std::shared_ptr close_shared_fd; if (!fd_cache.has_key(key)) { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, key), false); } if (memfile_nf != nullptr && memfile_nf->file.get() != it->second.fd) { if(memfile_nf->old_file.get() == it->second.fd) Server->Log("Memfile fd differs from open_files fd " + it->second.fd->getFilename()+" (is old_file)", LL_WARNING); else Server->Log("Memfile fd differs from open_files fd " + it->second.fd->getFilename(), LL_WARNING); } if (memfile_nf == nullptr || (memfile_nf->file.get() != it->second.fd && memfile_nf->old_file.get()!= it->second.fd ) ) { assert(dynamic_cast(it->second.fd) == nullptr); close_fd = it->second.fd; } if (memfile_nf != nullptr) { if (memfile_nf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } memfile_nf->old_file.swap(close_shared_fd); } } open_files.erase(it); lock.unlock(); if (close_fd != nullptr) { cachefs->closeAsync(io, close_fd); Server->destroy(close_fd); } if (close_shared_fd.get() != nullptr && close_shared_fd.use_count() <= 1) { cachefs->closeAsync(io, close_shared_fd.get()); } } #endif void TransactionalKvStore::read_keys(std::unique_lock& cache_lock, const std::string& tpath, bool verify) { std::vector files = cachefs->listFiles(tpath); for(size_t i=0;ifileSep() +files[i].name, verify); } else { size_t tmp_pos = files[i].name.find(".tmp"); if (tmp_pos != std::string::npos && tmp_pos == files[i].name.size() - 4) { continue; } bool compressed=false; size_t comp_pos = files[i].name.find(".comp"); if(comp_pos!=std::string::npos && comp_pos==files[i].name.size()-5) { compressed=true; SFile uncomp; uncomp.name = files[i].name.substr(0, files[i].name.size()-5); if (std::binary_search(files.begin(), files.end(), uncomp)) { //uncompressed file exists Server->Log(keypath2(hexToBytes(uncomp.name), transid) + ": Both compressed and uncompressed present. " "Deleting compressed file "+ tpath + cachefs->fileSep() + files[i].name); if (!cachefs->deleteFile(tpath + cachefs->fileSep() + files[i].name)) { Server->Log("Failed to delete " + tpath + cachefs->fileSep() + files[i].name + ". " + os_last_error_str(), LL_ERROR); abort(); } continue; } files[i].name = uncomp.name; comp_bytes += files[i].size; } add_cachesize(files[i].size); std::string key = hexToBytes(files[i].name); bool dirty = submission_items.find(std::make_pair(transid, key))!=submission_items.end(); if(compressed) { cache_put(compressed_items, key, cache_val(key, dirty), cache_lock); } else { cache_put(lru_cache, key, cache_val(key, dirty), cache_lock); } if(dirty) { addDirtyItem(transid, key, false); add_dirty_bytes(transid, key, files[i].size); } else if(verify) { Server->Log("Verifying " + keypath2(key, transid), LL_DEBUG); if (compressed) { int64 size_diff; int64 src_size; if (!decompress_item(key, transid, nullptr, size_diff, src_size, false)) { Server->Log("Decompressing item " + keypath2(key, transid) + " failed /verify", LL_ERROR); assert(false); throw std::runtime_error("Decompressing item " + keypath2(key, transid) + " failed /verify"); } } std::string tmpfn = "verify.file"; IFsFile* tmpl_file = cachefs->openFile(tmpfn, CREATE_DIRECT); if (tmpl_file == nullptr) { Server->Log("Could not verify.file on cache fs. "+cachefs->lastError(), LL_ERROR); assert(false); throw std::runtime_error("Could not verify.file on cache fs. " + cachefs->lastError()); } bool not_found = false; IFile* online_file = online_kv_store->get(key, transid, false, tmpl_file, true, not_found); if (online_file == nullptr) { Server->destroy(tmpl_file); cachefs->deleteFile(tmpfn); } if (online_file == nullptr || not_found) { Server->Log("Could not get " + keypath2(key, transid) + " from online store", LL_ERROR); assert(false); throw std::runtime_error("Could not get " + keypath2(key, transid) + " from online store"); } if (!compare_files(cachefs, keypath2(key, transid), online_file)) { assert(false); throw std::runtime_error("Cache file " + keypath2(key, transid) + " differs from online version"); } if (compressed) { cachefs->deleteFile(keypath2(key, transid)); } Server->destroy(online_file); cachefs->deleteFile(tmpfn); } } } } void TransactionalKvStore::read_missing() { std::vector files = cachefs->listFiles(""); for (size_t i = 0; i < files.size(); ++i) { if (!files[i].isdir && next(files[i].name, 0, "missing_")) { std::string hexkey = getafter("missing_", files[i].name); if (IsHex(hexkey)) { missing_items.insert(hexToBytes(hexkey)); } } } } void TransactionalKvStore::remove_transaction( int64 transid ) { Server->Log("Removing transaction " + convert(transid), LL_INFO); { std::scoped_lock dirty_lock(dirty_item_mutex); std::map >::iterator it = nosubmit_dirty_items.find(transid); if (it != nosubmit_dirty_items.end()) { nosubmit_dirty_items.erase(it); } } if (!cachefs->deleteSubvol("trans_" + convert(transid))) { Server->Log("Error removing transaction subvolume", LL_ERROR); } update_transactions(); } std::string TransactionalKvStore::basepath2() { return "trans_" + convert(basetrans); } bool TransactionalKvStore::evict_one(std::unique_lock& cache_lock, bool break_on_skip, bool only_non_dirty, std::list >::iterator& evict_it, common::lrucache& target_cache, bool use_chances, int64& freed_space, bool& run_del_items, std::vector >::iterator>& move_front, bool& used_chance) { used_chance = false; bool compressed = (&target_cache==&compressed_items); while(true) { if(open_files.find(*evict_it->first)!=open_files.end() || in_retrieval.find(*evict_it->first)!= in_retrieval.end()) { if (break_on_skip) { return false; } } else if (!is_congested_nolock() && preload_once_items.find(*evict_it->first) != preload_once_items.end()) { move_front.push_back(evict_it); if (break_on_skip) { return false; } } else { std::unique_lock lock(submission_mutex); bool in_submission=false; { std::scoped_lock dirty_lock(dirty_item_mutex); for (std::map::iterator it = num_dirty_items.begin(); it != num_dirty_items.end(); ++it) { if (it->second>0) { if (submission_items.find(std::make_pair(it->first, *evict_it->first)) != submission_items.end()) { in_submission = true; } } } } if(in_submission) { if (break_on_skip) { return false; } } else { lock.unlock(); if (is_sync_wait_item(*evict_it->first)) { if (break_on_skip) { return false; } } else { break; } } } if (evict_it == cache_eviction_iterator_finish(target_cache, cache_lock)) { return false; } --evict_it; } if (only_non_dirty && evict_it->second.dirty) { return false; } bool last = evict_it == cache_eviction_iterator_finish(target_cache, cache_lock); if (use_chances && evict_it->second.chances > 0) { --evict_it->second.chances; used_chance = true; auto evict_it_prev = evict_it; if (!last) { --evict_it; } target_cache.bring_to_front(evict_it_prev); } else { if (!compressed) { SFdKey* res = fd_cache.get(*evict_it->first); if (res != nullptr) { if (cachefs->filePath(res->fd) != keypath2(*evict_it->first, transid)) { Server->Log("Fd cache filename not as expected. Got " + cachefs->filePath(res->fd) + " Expected " + keypath2(*evict_it->first, transid), LL_ERROR); abort(); } assert(dynamic_cast(res->fd) == nullptr); Server->destroy(res->fd); fd_cache.del(*evict_it->first); } } else { assert(!fd_cache.has_key(*evict_it->first)); } evict_item(*evict_it->first, evict_it->second.dirty, target_cache, &evict_it, cache_lock, std::string(), freed_space); run_del_items = true; } std::scoped_lock lock(submission_mutex); check_submission_items(); return !last; } void TransactionalKvStore::evict_move_front(common::lrucache& target_cache, std::vector>::iterator>& move_front) { for (auto it : move_front) { target_cache.bring_to_front(it); } } void TransactionalKvStore::evict_item(const std::string & key, bool dirty, common::lrucache& target_cache, std::list >::iterator* evict_it, std::unique_lock& cache_lock, const std::string& from, int64& freed_space) { bool compressed = (&target_cache == &compressed_items); std::set::iterator it_untouched = nosubmit_untouched_items.find(key); if (dirty) { std::unique_lock dirty_lock(dirty_item_mutex); std::map >::iterator it_di = nosubmit_dirty_items.find(basetrans); if (it_di != nosubmit_dirty_items.end()) { std::set::iterator it2 = it_di->second.find(key); if (it2 != it_di->second.end()) { dirty_lock.unlock(); std::unique_ptr file_base(cachefs->openFile(basepath2() + cachefs->fileSep() + hexpath(key) + (compressed ? ".comp" : ""), MODE_READ)); bool base_compressed = compressed; if (file_base.get() == nullptr) { file_base.reset(cachefs->openFile(basepath2() + cachefs->fileSep() + hexpath(key) + ((!compressed) ? ".comp" : ""), MODE_READ)); base_compressed = !compressed; } assert(file_base.get() != nullptr); if (file_base.get() != nullptr) { Server->Log("Evicting dirty base cache item " + basepath2() + cachefs->fileSep() + hexpath(key) + (base_compressed ? ".comp" : "") + " via submission first" + from, LL_INFO); std::scoped_lock lock(submission_mutex); if (submission_items.find(std::make_pair(basetrans, key)) != submission_items.end()) { Server->Log(basepath2() + cachefs->fileSep() + hexpath(key) + (base_compressed ? ".comp" : "") + " is already being evicted" + from, LL_INFO); } else { SSubmissionItem sub_item; sub_item.key = key; sub_item.action = SubmissionAction_Evict; sub_item.transid = basetrans; sub_item.size = file_base->Size(); sub_item.compressed = base_compressed; submission_items.insert(std::make_pair(std::make_pair(basetrans, key), submission_queue_add(sub_item, false))); evict_cond.notify_one(); } } else { Server->Log("Base trans item at " + basepath2() + cachefs->fileSep() + hexpath(key) + (compressed ? ".comp" : "") + " does not exist", LL_ERROR); addSystemEvent("cache_err_fatal", "Base trans item does not exist on cache", "Base trans item at " + basepath2() + cachefs->fileSep() + hexpath(key) + (compressed ? ".comp" : "") + " does not exist", LL_ERROR); abort(); } } } } bool last = evict_it!=nullptr && *evict_it == cache_eviction_iterator_finish(target_cache, cache_lock); if (!dirty || it_untouched != nosubmit_untouched_items.end()) { if (dirty) { Server->Log("Evicting dirty but untouched cache item " + transpath2() + cachefs->fileSep() + hexpath(key) + (compressed ? ".comp" : "")+ from, LL_INFO); } else { Server->Log("Evicting non-dirty cache item " + transpath2() + cachefs->fileSep() + hexpath(key) + (compressed ? ".comp" : "")+ from, LL_INFO); } std::unique_ptr file_src; SMemFile* memf; { std::scoped_lock memfile_lock(memfiles_mutex); memf = memfiles.get(std::make_pair(transid, key), false); } IFsFile* file; if (memf != nullptr) { if (memf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memf->key), LL_ERROR); abort(); } file = memf->file.get(); } else { file = cachefs->openFile(transpath2() + cachefs->fileSep() + hexpath(key) + (compressed ? ".comp" : ""), MODE_READ); file_src.reset(file); } assert(file != nullptr); int64 fsize = 0; if (file != nullptr) { fsize = file->Size(); sub_cachesize(fsize); if (compressed) { comp_bytes -= fsize; } } file_src.reset(); delete_item(nullptr, key, compressed, cache_lock); check_deleted(transid, key, compressed); if (dirty) { removeDirtyItem(transid, key); rm_dirty_bytes(transid, key, fsize, true); } freed_space += fsize; std::string item_key = key; if (!last && evict_it!=nullptr) { --(*evict_it); } cache_del(target_cache, key, cache_lock); } else { Server->Log("Evicting dirty cache item " + transpath2() + cachefs->fileSep() + hexpath(key) + (compressed ? ".comp" : "") + " via submission"+ from, LL_INFO); std::unique_ptr file_src; SMemFile* memf; { std::scoped_lock memfile_lock(memfiles_mutex); memf = memfiles.get(std::make_pair(transid, key), false); } IFsFile* file; if (memf != nullptr) { if (memf->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(memf->key), LL_ERROR); abort(); } assert(!memf->cow); file = memf->file.get(); } else { file = cachefs->openFile(transpath2() + cachefs->fileSep() + hexpath(key) + (compressed ? ".comp" : ""), MODE_READ); file_src.reset(file); } assert(file != nullptr); std::scoped_lock lock(submission_mutex); SSubmissionItem sub_item; sub_item.key = key; sub_item.action = SubmissionAction_Evict; sub_item.transid = transid; sub_item.size = file->Size(); sub_item.compressed = compressed; submission_items.insert(std::make_pair(std::make_pair(transid, key), submission_queue_add(sub_item, memf!=nullptr))); if (memf != nullptr) { ++submitted_memfiles; submitted_memfile_size += memf->size; if (evict_it != nullptr) { memf->evicted = true; } } submitted_bytes += sub_item.size; evict_cond.notify_one(); if (!last && evict_it!=nullptr) { --(*evict_it); } cache_del(target_cache, key, cache_lock); } } bool TransactionalKvStore::evict_memfiles(std::unique_lock& cache_lock, bool evict_dirty) { bool has_eviction = false; if (memfile_size - submitted_memfile_size > (max_memfile_size * 2) / 3 || !evict_dirty) { if (evict_dirty) { Server->Log("Having " + PrettyPrintBytes(memfile_size - submitted_memfile_size) + " of memfiles... Starting eviction..."); } else { Server->Log("Having " + PrettyPrintBytes(memfile_size - submitted_memfile_size) + " of memfiles... Evicting all non-dirty..."); } std::unique_lock memfile_lock(memfiles_mutex); auto it = memfiles.eviction_iterator_start(); if (it == memfiles.eviction_iterator_finish()) { return has_eviction; } --it; do { while (true) { const std::string& key = it->first->second; const int64& ttransid = it->first->first; if (ttransid != transid || it->second.evicted) { //nobr } else if (open_files.find(key) != open_files.end() || in_retrieval.find(key) != in_retrieval.end()) { //nobr } else { if (is_sync_wait_item(key)) { //nobr } else if (ttransid == transid && num_mem_files[transid] < 10) { //nobr } else { break; } } if (it == memfiles.eviction_iterator_finish()) { return has_eviction; } --it; } SCacheVal* dirty = cache_get(lru_cache, it->first->second, cache_lock, false); if (dirty != nullptr && dirty->chances > 0) { --dirty->chances; auto it_prev = it; bool last = it == memfiles.eviction_iterator_finish(); if (!last) { --it; } memfiles.bring_to_front(it_prev); if (last) { return has_eviction; } else { assert(it->first->second != std::string()); } } else if (dirty != nullptr) { int64 freed_space; if (evict_dirty || !dirty->dirty) { it->second.evicted = true; } std::string key = it->first->second; bool last = it == memfiles.eviction_iterator_finish(); if (!last) { --it; } std::string next_key = it->first->second; if (evict_dirty || !dirty->dirty) { memfile_lock.unlock(); assert(!fd_cache.has_key(key)); evict_item(key, dirty->dirty, lru_cache, nullptr, cache_lock, " [ememfiles]", freed_space); memfile_lock.lock(); has_eviction = true; } if (last) { return has_eviction; } else { if (it->first->second != next_key) { Server->Log("memfile iter error", LL_ERROR); abort(); } assert(it->first->second != std::string()); } } else { assert(false); if (it == memfiles.eviction_iterator_finish()) { return has_eviction; } --it; } } while (memfile_size - submitted_memfile_size > (max_memfile_size*2)/3 || !evict_dirty); } return has_eviction; } void TransactionalKvStore::check_deleted(int64 transid, const std::string & key, bool comp) { #ifndef NDEBUG std::string ipath = "trans_" + convert(transid) + cachefs->fileSep() + hexpath(key) + (comp ? ".comp" : ""); { IScopedLock lock(del_file_mutex.get()); if (del_file_queue.find(ipath) != del_file_queue.end()) { return; } } assert(!cacheFileExists(ipath)); #endif } bool TransactionalKvStore::compress_one(std::unique_lock& cache_lock, std::list >::iterator& compress_it) { while(true) { if(open_files.find(*compress_it->first)!=open_files.end() || in_retrieval.find(*compress_it->first)!= in_retrieval.end()) { if (compress_it == cache_eviction_iterator_finish(lru_cache, cache_lock)) { return false; } --compress_it; } else { break; } } SFdKey* res = fd_cache.get(*compress_it->first); if(res!=nullptr) { if (cachefs->filePath(res->fd) != keypath2(*compress_it->first, transid)) { Server->Log("Fd cache filename not as expected. Got " + cachefs->filePath(res->fd) + " Expected " + keypath2(*compress_it->first, transid), LL_ERROR); abort(); } assert(dynamic_cast(res->fd) == nullptr); Server->destroy(res->fd); fd_cache.del(*compress_it->first); } Server->Log("Compressing cache item "+transpath2() + cachefs->fileSep() + hexpath(*compress_it->first), LL_INFO); SMemFile* memf; { std::scoped_lock memfile_lock(memfiles_mutex); memf = memfiles.get(std::make_pair(transid, *compress_it->first), false); } int64 fsize=0; if (memf != nullptr) { if (memf->key != *compress_it->first) { Server->Log("Memfile key wrong. Expected " + hexpath(*compress_it->first) + " got " + hexpath(memf->key), LL_ERROR); abort(); } fsize = memf->file->Size(); } else { std::unique_ptr file(cachefs->openFile(transpath2() + cachefs->fileSep() + hexpath(*compress_it->first), MODE_READ)); assert(file.get() != nullptr); if (file.get() != nullptr) { fsize = file->Size(); } } std::scoped_lock lock(submission_mutex); SSubmissionItem sub_item; sub_item.key = *compress_it->first; sub_item.action = SubmissionAction_Compress; sub_item.transid = transid; sub_item.size = 0; sub_item.compressed = compress_it->second.dirty; sub_item.size=fsize; submission_items.insert(std::make_pair(std::make_pair(transid, *compress_it->first), submission_queue_add(sub_item, memf!=nullptr))); if (memf != nullptr) { memf->evicted = true; ++submitted_memfiles; submitted_memfile_size += memf->size; } check_submission_items(); evict_cond.notify_one(); std::string key = *compress_it->first; bool last = compress_it == cache_eviction_iterator_finish(lru_cache, cache_lock); if (!last) { --compress_it; } cache_del(lru_cache, key, cache_lock); return !last; } std::list::iterator TransactionalKvStore::next_submission_item(bool no_compress, bool prefer_non_delete, bool prefer_mem, std::string& path, bool& p_do_stop, SMemFile*& memf) { std::unique_lock lock(submission_mutex); bool skip_wait = false; while(true) { int first_wait = 0; while(!skip_wait && submission_queue.empty() && !do_stop) { if (first_wait==0) { first_wait = 1; evict_cond.wait_for(lock, 10s); } else if (first_wait == 1) { lock.unlock(); first_wait = 2; Server->mallocFlushTcache(); lock.lock(); } else { if (submission_queue.empty()) evict_cond.wait(lock); else evict_cond.wait_for(lock, 10s); } } if(do_stop) { p_do_stop=do_stop; return submission_queue.end(); } skip_wait = false; bool has_delete = false; std::list::iterator it = submission_queue.begin(); if (prefer_mem && submitted_memfiles > 0 && submission_queue_memfile_first!=submission_queue.end()) { it = submission_queue_memfile_first; } for(;it!=submission_queue.end();++it) { if (no_compress && it->action == SubmissionAction_Compress) { continue; } if (prefer_non_delete && it->action == SubmissionAction_Delete) { has_delete = true; continue; } if(it->action==SubmissionAction_Evict || it->action==SubmissionAction_Dirty || it->action==SubmissionAction_Compress ) { if (it->action == SubmissionAction_Compress || !it->compressed) { std::scoped_lock memfile_lock(memfiles_mutex); memf = memfiles.get(std::make_pair(it->transid, it->key), false); } if (prefer_mem && memf == nullptr && submitted_memfiles > 0) { continue; } if(it->action==SubmissionAction_Dirty) it->action=SubmissionAction_Working_Dirty; if (it->action == SubmissionAction_Evict) { it->action = SubmissionAction_Working_Evict; if(it->transid==transid) dirty_evicted_items.insert(it->key); assert(memf == nullptr || (!memf->cow && memf->evicted)); } if(it->action==SubmissionAction_Compress) it->action=SubmissionAction_Working_Compress; path = keypath2(it->key, it->transid); if ((it->action == SubmissionAction_Working_Evict || it->action == SubmissionAction_Working_Dirty) && it->compressed) { path += ".comp"; } if (memf != nullptr) { if (memf->key != it->key) { Server->Log("Memfile key wrong. Expected " + hexpath(it->key) + " got " + hexpath(memf->key), LL_ERROR); abort(); } assert(submitted_memfiles > 0); --submitted_memfiles; if (submitted_memfiles == 0) { submission_queue_memfile_first = submission_queue.end(); } else { submission_queue_memfile_first = it; ++submission_queue_memfile_first; } } return it; } else if(it->action==SubmissionAction_Delete) { it->action=SubmissionAction_Working_Delete; return it; } } if (prefer_non_delete && has_delete) { skip_wait = true; prefer_non_delete = false; continue; } if (!submission_queue.empty()) { evict_cond.notify_one(); } lock.unlock(); Server->wait(100); lock.lock(); } } bool TransactionalKvStore::item_submitted( std::list::iterator it, bool can_delete, bool is_memf) { int64 curr_transid; { std::unique_lock lock(cache_mutex); if (is_memf) { #ifndef NDEBUG if (it->action == SubmissionAction_Working_Evict && it->transid == transid) { assert(cache_get(lru_cache, it->key, lock, false) == nullptr); assert(cache_get(compressed_items, it->key, lock, false) == nullptr); } #endif //TODO: if !can_delete move this to regular submit bundle and resubmit if sync() fails if (rm_mem_file(nullptr, it->transid, it->key, true) && it->transidsync(); cachefs->rename("trans_" + convert(it->transid) + cachefs->fileSep() + "dirty.mem", "trans_" + convert(it->transid) + cachefs->fileSep() + "dirty"); lock.lock(); cleanup(false); } } curr_transid = transid; if (it->action == SubmissionAction_Working_Delete) { std::scoped_lock dirty_lock(dirty_item_mutex); --num_delete_items[it->transid]; if (num_delete_items[it->transid] == 0) { cachefs->deleteFile("trans_" + convert(it->transid) + cachefs->fileSep() + "deleted"); } } if(it->action!=SubmissionAction_Working_Evict || it->transid==curr_transid) { removeDirtyItem(it->transid, it->key); rm_dirty_bytes(it->transid, it->key, it->size, true); submitted_bytes-=it->size; total_submitted_bytes += it->size; if(it->transid==curr_transid && it->compressed) { comp_bytes -= it->size; } } std::unique_lock dirty_lock(dirty_item_mutex); if (it->action != SubmissionAction_Working_Evict && it->transidtransid] == 0 && num_delete_items[it->transid] == 0 ) { //TODO: if !can_delete move this to regular_submit_bundle #ifdef DIRTY_ITEM_CHECK assert(dirty_items[it->transid].empty()); dirty_items.erase(dirty_items.find(it->transid)); assert(dirty_items_size[it->transid].empty()); dirty_items_size.erase(dirty_items_size.find(it->transid)); #endif num_dirty_items.erase(num_dirty_items.find(it->transid)); num_delete_items.erase(num_delete_items.find(it->transid)); bool has_nosubmit = nosubmit_dirty_items.find(it->transid) != nosubmit_dirty_items.end(); dirty_lock.unlock(); std::unique_ptr invalid(cachefs->openFile("trans_" + convert(it->transid) + cachefs->fileSep() + "invalid", MODE_READ)); lock.unlock(); if (invalid.get() == nullptr) { std::unique_ptr nosubmit(cachefs->openFile("trans_" + convert(it->transid) + cachefs->fileSep() + "dirty.nosubmit", MODE_READ)); if (nosubmit.get() == nullptr && !has_nosubmit) { online_kv_store->sync(); if (online_kv_store->transaction_finalize(it->transid, true, true)) { lock.lock(); std::unique_ptr tmp(cachefs->openFile("trans_" + convert(it->transid) + cachefs->fileSep() + "commited", MODE_WRITE)); cachefs->deleteFile("trans_" + convert(it->transid) + cachefs->fileSep() + "dirty"); tmp.reset(); cleanup(false); } else { lock.lock(); Server->Log("Error marking transaction " + convert(it->transid) + " as complete", LL_ERROR); addDirtyItem(it->transid, it->key, false); return false; } } } else { lock.lock(); Server->Log("Removing invalid transaction " + std::to_string(it->transid)+ " (1)", LL_INFO); remove_transaction(it->transid); } } } bool do_submit_bundle = false; bool submit_evict = false; bool run_del_item = false; if(!it->key.empty() && it->transidaction== SubmissionAction_Working_Dirty) { if (can_delete) { if (with_submitted_files) { add_submitted_item(it->transid, it->key, nullptr); } } else { do_submit_bundle = true; } } if (it->action != SubmissionAction_Working_Delete && it->action != SubmissionAction_Working_Evict) { std::lock_guard lock(submission_mutex); submission_items.erase(submission_items.find(std::make_pair(it->transid, it->key))); } if(it->action==SubmissionAction_Working_Evict) { std::unique_lock lock(cache_mutex, std::defer_lock); if(it->transid==curr_transid) { lock.lock(); sub_cachesize(it->size); } else { add_evicted_item(it->transid, it->key); lock.lock(); } { std::lock_guard lock(submission_mutex); submission_items.erase(submission_items.find(std::make_pair(it->transid, it->key))); } //TODO: if !can_delete move this to regular_submit_bundle std::unique_lock dirty_lock(dirty_item_mutex); std::map >::iterator it_ld = nosubmit_dirty_items.find(it->transid); if(it_ld!=nosubmit_dirty_items.end()) { std::set::iterator it2 = it_ld->second.find(it->key); if(it2!=it_ld->second.end()) { it_ld->second.erase(it2); if(it_ld->second.empty() && it->transidtransid)== num_dirty_items.end() && num_delete_items.find(it->transid)== num_delete_items.end()) { nosubmit_dirty_items.erase(it_ld); dirty_lock.unlock(); //TODO: Sync this with memfile dirty submission so transaction also commits if memfiles are submitted later than the evicted items std::unique_ptr invalid(cachefs->openFile("trans_"+convert(it->transid)+ cachefs->fileSep() + "invalid", MODE_READ)); lock.unlock(); if(invalid.get()==nullptr) { online_kv_store->sync(); if(online_kv_store->transaction_finalize(it->transid, true, true)) { lock.lock(); std::unique_ptr tmp(cachefs->openFile("trans_"+convert(it->transid)+ cachefs->fileSep() + "commited", MODE_WRITE)); cachefs->deleteFile("trans_"+convert(it->transid)+ cachefs->fileSep() + "dirty"); cachefs->deleteFile("trans_"+convert(it->transid)+ cachefs->fileSep() + "dirty.nosubmit"); tmp.reset(); } else { lock.lock(); Server->Log("Error marking finalized transaction "+convert(it->transid)+" as complete", LL_ERROR); } } else { lock.lock(); Server->Log("Removing invalid transaction " + std::to_string(it->transid) + " (2)", LL_INFO); remove_transaction(it->transid); } } } } dirty_lock.unlock(); delete_item(nullptr, it->key, it->compressed, lock, 0, it->transid!=curr_transid ? curr_transid : 0); run_del_item = true; } else if (it->action == SubmissionAction_Working_Dirty && it->transid < curr_transid) { std::unique_lock lock(cache_mutex); std::string item_path = keypath2(it->key, it->transid); if (it->compressed) { item_path += ".comp"; } common::lrucache* target_cache = &lru_cache; common::lrucache* other_target_cache = &compressed_items; if(it->compressed) { target_cache = &compressed_items; other_target_cache = &lru_cache; } bool compressed = it->compressed; SCacheVal* dirty = cache_get(*target_cache, it->key, lock, false); if(dirty==nullptr) { compressed = !compressed; dirty = cache_get(*other_target_cache, it->key, lock, false); target_cache = other_target_cache; } bool del_item = false; if( do_evict && dirty!=nullptr && !dirty->dirty && it->keys.empty() && open_files.find(it->key)==open_files.end() && in_retrieval.find(it->key)==in_retrieval.end() ) { bool in_submission=false; { std::lock_guard lock(submission_mutex); std::scoped_lock dirty_lock(dirty_item_mutex); for(std::map::iterator itd=num_dirty_items.begin();itd!=num_dirty_items.end();++itd) { if(itd->second>0) { if(submission_items.find(std::make_pair(itd->first, it->key))!=submission_items.end()) { in_submission=true; } } } } if(!in_submission && can_delete ) { Server->Log("Evicting non-dirty cache item "+transpath2() + cachefs->fileSep() + hexpath(it->key)+(compressed?".comp":"")+" after submission", LL_INFO); int64 fsize = -1; if(!compressed) { SFdKey* res = fd_cache.get(it->key); if(res!=nullptr) { if (cachefs->filePath(res->fd) != keypath2(it->key, transid)) { Server->Log("Fd cache filename not as expected. Got " + cachefs->filePath(res->fd) + " Expected " + keypath2(it->key, transid), LL_ERROR); abort(); } assert(dynamic_cast(res->fd) == nullptr); fsize = res->fd->Size(); Server->destroy(res->fd); fd_cache.del(it->key); } } if(fsize==-1 && compressed) { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(memfiles_mutex); memfile_nf = memfiles.get(std::make_pair(transid, it->key), false); } IFile* fd; if (memfile_nf != nullptr) { if (memfile_nf->key != it->key) { Server->Log("Memfile key wrong. Expected " + hexpath(it->key) + " got " + hexpath(memfile_nf->key), LL_ERROR); abort(); } fd = memfile_nf->file.get(); } else { fd = cachefs->openFile(keypath2(it->key, transid) + (compressed ? ".comp" : ""), MODE_READ); } if (fd != nullptr) { fsize = fd->Size(); Server->destroy(fd); } } if (fsize == -1) { fsize = it->size; } sub_cachesize(fsize); if(compressed) { comp_bytes-= fsize; } delete_item(nullptr, it->key, compressed, lock); run_del_item = true; cache_del(*target_cache, it->key, lock); } else if (!in_submission) { del_item = true; submit_evict = true; } else { del_item = true; } } else if (do_evict || (dirty == nullptr && allow_evict) ) { del_item = true; } if(del_item) { if (can_delete) { lock.unlock(); if (!is_memf) { cachefs->deleteFile(item_path); } } else { do_submit_bundle = true; } } } if (do_submit_bundle) { std::scoped_lock lock(cache_mutex); submit_bundle.push_back(std::make_pair(*it, submit_evict)); IScopedLock bundle_lock(submit_bundle_item_mutex.get()); curr_submit_bundle_items->insert(std::make_pair(it->key, it->transid)); if (submit_bundle_starttime == 0) { submit_bundle_starttime = Server->getTimeMS(); } } if (run_del_item) { run_del_file_queue(); } std::lock_guard lock(submission_mutex); submission_queue_rm(it); check_submission_items(); return true; } bool TransactionalKvStore::item_compressed( std::list::iterator it, bool compression_ok, int64 size_diff, int64 add_comp_bytes, bool is_memf) { ++total_compress_ops; std::unique_lock lock(cache_mutex); std::unique_lock submission_lock(submission_mutex); bool has_delete_item=false; if (!it->finish) { Server->Log("Not finishing submission item " + hex(it->key) + " transid " + convert(it->transid)+" (was deleted)", LL_INFO); } if(compression_ok && it->finish) { if(it->transid==transid) { has_delete_item=true; cache_put(compressed_items, it->key, cache_val(it->key, it->compressed), lock); if(it->compressed) { add_dirty_bytes(transid, it->key, size_diff); } if(size_diff>=0) { add_cachesize(size_diff); } else { sub_cachesize(-1*size_diff); } comp_bytes+=add_comp_bytes; } else { assert(false); cachefs->deleteFile(keypath2(it->key, it->transid)+".comp"); } } else if(it->finish) { cache_put(lru_cache, it->key, cache_val(it->key, it->compressed), lock); } std::string local_key = it->key; int64 curr_basetrans = basetrans; int64 curr_transid = it->transid; bool compressed = it->compressed; bool curr_finish = it->finish; bool run_del_item = false; if(has_delete_item) { bool nosubmit_untouched = nosubmit_untouched_items.find(local_key)!=nosubmit_untouched_items.end(); if( !compressed || nosubmit_untouched ) { submission_lock.unlock(); lock.unlock(); if (!compressed && comp_percent>0 && resubmit_compressed_ratio<1) { int64 submitted_comp_bytes = get_compsize(it->key, it->transid); if (submitted_comp_bytes > 0 && (submitted_comp_bytes - add_comp_bytes) > 10 * 1024) { float better_ratio = static_cast(add_comp_bytes) / submitted_comp_bytes; if (better_ratio <= resubmit_compressed_ratio) { Server->Log("Result of background compression is only " + convert(better_ratio*100.f) + "% of submitted size (submitted=" + PrettyPrintBytes(submitted_comp_bytes) + " now=" + PrettyPrintBytes(add_comp_bytes) + ". Dirtying item " + hex(it->key) + " transid " + convert(it->transid) + " for resubmission.", LL_INFO); std::unique_lock lock2(cache_mutex); if (cache_get(compressed_items, it->key, lock2, false) != nullptr) { cache_put(compressed_items, it->key, cache_val(it->key, true), lock2); addDirtyItem(it->transid, it->key); add_dirty_bytes(it->transid, it->key, add_comp_bytes); } } } } if (with_prev_link && !is_memf) { if (nosubmit_untouched) { Server->Log("Linking untouched dirty item to " + keypath2(local_key, curr_basetrans) + ".comp"); } else { Server->Log("Linking non-dirty item to " + keypath2(local_key, curr_basetrans) + ".comp"); } std::unique_ptr base_file(cachefs->openFile(keypath2(local_key, curr_basetrans) + ".comp", MODE_READ)); if (base_file.get() == nullptr) { std::string path = keypath2(local_key, curr_basetrans) + ".comp"; if (!sync_link(cachefs, keypath2(local_key, curr_transid) + ".comp", path)) { nosubmit_untouched = false; } } else { nosubmit_untouched = false; } } else { nosubmit_untouched = false; } lock.lock(); submission_lock.lock(); submission_items.erase(submission_items.find(std::make_pair(it->transid, it->key))); submission_queue_rm(it); delete_item(nullptr, local_key, false, lock, nosubmit_untouched ? curr_basetrans : 0, 0, DeleteImm::None, 0, true); run_del_item = true; } else { submission_items.erase(submission_items.find(std::make_pair(it->transid, it->key))); submission_queue_rm(it); delete_item(nullptr, local_key, false, lock, 0, 0, DeleteImm::None, 0, true); run_del_item = true; } } else { submission_items.erase(submission_items.find(std::make_pair(it->transid, it->key))); submission_queue_rm(it); } if (!curr_finish) { cachefs->deleteFile(keypath2(local_key, curr_transid) + ".comp"); delete_item(nullptr, local_key, false, lock, 0, 0, DeleteImm::None, 0, true); run_del_item = true; } check_submission_items(); if (run_del_item) { submission_lock.unlock(); lock.unlock(); run_del_file_queue(); } return true; } void TransactionalKvStore::stop() { regular_submit_bundle_thread.quit(); throttle_thread.quit(); metadata_update_thread.quit(); memfd_del_thread.quit(); std::unique_lock lock(cache_mutex); wait_for_all_retrievals(lock); std::unique_lock sub_lock(submission_mutex); do_stop=true; evict_cond.notify_all(); sub_lock.unlock(); lock.unlock(); Server->getThreadPool()->waitFor(threads); } bool TransactionalKvStore::checkpoint(bool do_submit, size_t checkpoint_retry_n) { assert(fuse_io_context::is_sync_thread()); bool allow_log_events = checkpoint_retry_n > retry_log_n; std::unique_lock lock(cache_mutex); wait_for_retrieval_poll(lock, std::string()); std::string empty_key; { RetrievalOperationNoLock retrieval_op(*this, empty_key); while (!open_files.empty()) { lock.unlock(); Server->wait(10); lock.lock(); } } wait_for_all_retrievals(lock); assert(in_retrieval.empty()); assert(open_files.empty()); RetrievalOperationNoLock retrieval_op(*this, empty_key); std::pair evt; do { evt = fd_cache.evict_one(); if (evt != std::pair()) { assert(dynamic_cast(evt.second.fd) == nullptr); drop_cache(evt.second.fd); Server->destroy(evt.second.fd); } } while (evt!=std::pair()); run_del_file_queue(); Server->Log("Removing compression/eviction submissions..."); remove_compression_evicition_submissions(lock); Server->Log("Waiting for active compressions/evicitions to finish..."); wait_for_compressions_evictions(lock); run_del_file_queue(); if (!online_kv_store->sync()) { Server->Log("Error syncing online kv store", LL_ERROR); return false; } int64 new_trans = online_kv_store->new_transaction(allow_log_events); if(new_trans==0) { Server->Log("Error requesting new transaction", LL_ERROR); return false; } if(!cachefs->createSnapshot("trans_"+std::to_string(transid), "trans_" + std::to_string(new_trans))) { Server->Log("Error creating new transaction subvolume", LL_ERROR); return false; } else { update_transactions(); clean_snapshot(new_trans); } update_transactions(); if(!write_to_deleted_file("trans_" + convert(transid)+ cachefs->fileSep()+"deleted", do_submit)) { return false; } if (!do_submit) { size_t retry_n = 0; while (!online_kv_store->transaction_finalize(transid, false, retry_n>retry_log_n)) { retryWait(++retry_n); } } while (!cachefs->sync("") || !cachefs->sync("trans_"+std::to_string(transid))) { Server->Log("Syncing fs failed. Retrying..."); Server->wait(1000); } size_t num_memf_dirty=0; std::map memf_dirty_items; std::map memf_dirty_items_size; if(!write_to_dirty_file(lock, "trans_" + convert(transid)+ cachefs->fileSep()+"dirty", do_submit, new_trans, num_memf_dirty, memf_dirty_items, memf_dirty_items_size)) { return false; } if(do_submit) { while(!cachefs->createDir("trans_" + convert(transid)+ cachefs->fileSep()+"submitted")) { Server->Log("Error creating submitted dir. Retrying..."); Server->wait(1000); } } else { while(!cachefs->createDir("trans_" + convert(transid)+ cachefs->fileSep()+"evicted")) { Server->Log("Error creating evicted dir. Retrying..."); Server->wait(1000); } } basetrans=transid; transid = new_trans; if(!do_submit) { { std::scoped_lock dirty_lock(dirty_item_mutex); std::map::iterator dirty_it = num_dirty_items.find(basetrans); if (dirty_it != num_dirty_items.end()) { #ifdef DIRTY_ITEM_CHECK std::map >::iterator dirty2_it = dirty_items.find(basetrans); assert(dirty2_it != dirty_items.end()); dirty_items[new_trans] = dirty2_it->second; dirty_items.erase(dirty2_it); std::map >::iterator dirty3_it = dirty_items_size.find(basetrans); assert(dirty3_it != dirty_items_size.end()); dirty_items_size[new_trans] = dirty3_it->second; dirty_items_size.erase(dirty3_it); #endif num_dirty_items[new_trans] = dirty_it->second; num_dirty_items.erase(dirty_it); } if (num_memf_dirty > 0) { num_dirty_items[basetrans] = num_memf_dirty; DIRTY_ITEM(dirty_items[basetrans] = memf_dirty_items); DIRTY_ITEM(dirty_items_size[basetrans] = memf_dirty_items_size); for (auto it : memf_dirty_items) { for(size_t i=0;ifileSep() + hexpath(key); } bool TransactionalKvStore::read_submitted_evicted_files(const std::string & fn, std::set& sub_evicted) { std::unique_ptr f(cachefs->openFile(fn, MODE_READ)); if (f.get() == nullptr) { return true; } std::vector msg; while (true) { unsigned short psize; bool has_error = false; if (f->Read(reinterpret_cast(&psize), sizeof(psize), &has_error) == sizeof(psize)) { if (psize < sizeof(unsigned short)) { return false; } psize -= sizeof(unsigned short); if (msg.size() < psize) { msg.resize(psize); } _u32 rc = f->Read(msg.data(), psize, &has_error); if (rc != psize) { return !has_error; } CRData rdata(msg.data(), msg.size()); std::string tkey; if (rdata.getStr2(&tkey)) { sub_evicted.insert(tkey); } } else { return !has_error; } } } bool TransactionalKvStore::read_from_dirty_file(std::unique_lock& cache_lock, const std::string& fn, int64 transaction_id, bool do_submit, int64 nosubmit_transid) { std::list::iterator submit_end = submission_queue.end(); std::unique_ptr file(cachefs->openFile(fn, MODE_READ)); if(file.get()==nullptr) { Server->Log("Cannot open \""+fn+"\". " + os_last_error_str(), LL_ERROR); return false; } if (nosubmit_transid != 0) { nosubmit_untouched_items.clear(); } std::set evicted_tkeys; read_submitted_evicted_files(fn + ".evicted", evicted_tkeys); std::set submitted_tkeys; read_submitted_evicted_files(fn + ".submitted", submitted_tkeys); std::string dirty_list; while(true) { char compressed=0; if(file->Read(&compressed, sizeof(compressed))!=sizeof(compressed)) { break; } unsigned int key_size; if(file->Read(reinterpret_cast(&key_size), sizeof(key_size))!=sizeof(key_size)) { Server->Log("Error reading key length submission file", LL_ERROR); return false; } std::string key; key.resize(key_size); if(file->Read(&key[0], key_size)!=key_size) { Server->Log("Error reading key from submission file", LL_ERROR); return false; } if(!dirty_list.empty()) dirty_list+=", "; dirty_list+=hex(key); if(submitted_tkeys.find(key)!= submitted_tkeys.end()) { continue; } if(evicted_tkeys.find(key)!=evicted_tkeys.end()) { Server->Log("Dirty evicted item " + hex(key) + " transid " + convert(transaction_id)); dirty_evicted_items.insert(key); continue; } std::unique_ptr file; if (compressed != compressed_evicted_dirty) { file.reset(cachefs->openFile(keypath2(key, transaction_id) + (compressed != 0 ? ".comp" : ""), MODE_READ)); if(file.get()==nullptr) { file.reset(cachefs->openFile(keypath2(key, transaction_id) + (compressed == 0 ? ".comp" : ""), MODE_READ)); if(file.get()!=nullptr) { compressed = compressed == 0 ? 1 : 0; } } if (!file.get()) { Server->Log("Error opening file to submit (" + keypath2(key, transaction_id) + (compressed != 0 ? ".comp)" : ")"), LL_ERROR); bool not_found; int64 get_transid = 0; std::string tmpfn = "submit.test.file"; IFsFile* tmpl_file = cachefs->openFile(tmpfn, CREATE_DIRECT); if (tmpl_file == nullptr) { Server->Log("Cannot open submit.test.file "+cachefs->lastError(), LL_ERROR); addSystemEvent("cache_err_fatal", "Cannot open file", "Cannot open submit.test.file " + cachefs->lastError(), LL_ERROR); abort(); } IFsFile* f = online_kv_store->get(key, transaction_id, false, tmpl_file, true, not_found, &get_transid); if (f == nullptr) { Server->Log("Cannot get file not_found="+convert(not_found), LL_ERROR); addSystemEvent("cache_err_fatal", "Cannot get file", "Cannot get file not_found=" + convert(not_found), LL_ERROR); abort(); } else { Server->destroy(f); cachefs->deleteFile(tmpfn); Server->Log("Get ok", LL_WARNING); } if (get_transid != transaction_id) { Server->Log("Item from backend has transaction id " + convert(get_transid) + " expected " + convert(transaction_id), LL_ERROR); addSystemEvent("cache_err_fatal", "Item from backend has wrong transaction id", "Item from backend has transaction id " + convert(get_transid) + " expected " + convert(transaction_id), LL_ERROR); abort(); } if(do_submit) { Server->Log("Assuming file was already submitted", LL_WARNING); } else { Server->Log("Assuming file was evicted", LL_WARNING); dirty_evicted_items.insert(key); } continue; } } if(do_submit) { if (compressed != compressed_evicted_dirty) { SSubmissionItem item; item.key = key; item.transid = transaction_id; item.action = SubmissionAction_Dirty; item.size = file->Size(); item.compressed = compressed != 0; Server->Log("Dirty submission item " + hex(key) + " transid " + convert(transaction_id) + " compressed=" + convert(item.compressed)); submit_end = submission_queue_insert(item, false, submit_end); submission_items.insert(std::make_pair(std::make_pair(transaction_id, key), submit_end)); add_dirty_bytes(transaction_id, key, item.size); submitted_bytes += item.size; addDirtyItem(transaction_id, key, false); } else { Server->Log("Already evicted submission item " + hex(key) + " transid " + convert(transaction_id)); } } else { if (compressed == compressed_evicted_dirty) { Server->Log("Dirty evicted item " + hex(key) + " transid " + convert(transaction_id)); dirty_evicted_items.insert(key); } else { Server->Log("Dirty item " + hex(key) + " transid " + convert(transaction_id)+" compressed="+ convert(compressed != 0)); if (compressed != 0) { cache_put(compressed_items, key, cache_val(key, true), cache_lock); } else { cache_put(lru_cache, key, cache_val(key, true), cache_lock); } add_dirty_bytes(transaction_id, key, file->Size()); addDirtyItem(transaction_id, key, false); if (nosubmit_transid != 0) { std::scoped_lock dirty_lock(dirty_item_mutex); nosubmit_dirty_items[nosubmit_transid].insert(key); nosubmit_untouched_items.insert(key); } } } } Server->Log("List of read dirty items for transid "+convert(transaction_id)+": "+dirty_list); if(do_submit) { submit_dummy(transaction_id); } check_submission_items(); return true; } bool TransactionalKvStore::write_to_dirty_file(std::unique_lock& cache_lock, const std::string& fn, bool do_submit, int64 new_trans, size_t& num_memf_dirty, std::map& memf_dirty_items, std::map& memf_dirty_items_size) { std::unique_ptr file(cachefs->openFile(fn+".new", MODE_WRITE)); int64 file_pos=0; if(file.get()==nullptr) { Server->Log("Error opening submission file for writing", LL_ERROR); return false; } std::lock_guard sub_lock(submission_mutex); std::list::iterator submit_end = submission_queue.end(); nosubmit_untouched_items.clear(); bool compressed=false; std::string dirty_list; bool has_memf = false; { std::scoped_lock dirty_lock(dirty_item_mutex); std::scoped_lock memfile_lock(memfiles_mutex); for (common::lrucache::list_t::iterator it = lru_cache.get_list().begin(); !compressed || it != compressed_items.get_list().end();) { if (compressed || it != lru_cache.get_list().end()) { if (it->second.dirty) { if (!dirty_list.empty()) dirty_list += ", "; dirty_list += hex(*it->first); SMemFile* memf = nullptr; if (!compressed) { memf = memfiles.get(std::make_pair(transid, *it->first), false); } if (memf == nullptr) { char ch_compressed = compressed ? 1 : 0; while (file->Write(file_pos, &ch_compressed, sizeof(ch_compressed)) != sizeof(ch_compressed)) { Server->Log("Error writing compressed flag. Retrying...", LL_ERROR); Server->wait(1000); } file_pos += sizeof(ch_compressed); unsigned int key_size = static_cast(it->first->size()); while (file->Write(file_pos, reinterpret_cast(&key_size), sizeof(key_size)) != sizeof(key_size)) { Server->Log("Error writing key size. Retrying...", LL_ERROR); Server->wait(1000); } file_pos += sizeof(key_size); while (file->Write(file_pos, it->first->c_str(), key_size) != key_size) { Server->Log("Error writing key. Retrying...", LL_ERROR); Server->wait(1000); } file_pos += key_size; } else { if (memf->key != *it->first) { Server->Log("Memfile key wrong. Expected " + hexpath(*it->first) + " got " + hexpath(memf->key), LL_ERROR); abort(); } assert(!memf->cow); has_memf = true; SMemFile new_memf = *memf; new_memf.evicted = false; new_memf.cow = true; memfiles.put_after(std::make_pair(transid, *it->first), std::make_pair(new_trans, *it->first), new_memf); ++num_mem_files[new_trans]; } if (do_submit || memf != nullptr) { int64 fsize; if (memf == nullptr) { std::unique_ptr input_file(cachefs->openFile(keypath2(*it->first, transid) + (compressed ? ".comp" : ""), MODE_READ)); if (input_file.get() == nullptr) { Server->Log("Error opening file to submit", LL_ERROR); assert(false); continue; } fsize = input_file->Size(); } else { assert(!cacheFileExists(keypath2(*it->first, transid) + (compressed ? ".comp" : ""))); fsize = memf->file->Size(); ++num_memf_dirty; ++memf_dirty_items[*it->first]; memf_dirty_items_size[*it->first] += fsize; } SSubmissionItem item; item.key = *it->first; item.transid = transid; item.action = SubmissionAction_Dirty; item.size = fsize; item.compressed = compressed; submit_end = submission_queue_insert(item, memf != nullptr, submit_end); submission_items.insert(std::make_pair(std::make_pair(transid, *it->first), submit_end)); if (memf != nullptr) { submitted_memfiles++; submitted_memfile_size += memf->size; } submitted_bytes += item.size; it->second.dirty = false; } else { nosubmit_dirty_items[transid].insert(*it->first); nosubmit_untouched_items.insert(*it->first); } } else if (!compressed) { if (memfiles.change_key(std::make_pair(transid, *it->first), std::make_pair(new_trans, *it->first))) { assert(num_mem_files[transid] > 0); auto it = num_mem_files.find(transid); --it->second; if (it->second == 0) { num_mem_files.erase(it); } ++num_mem_files[new_trans]; } } ++it; } if (!compressed && it == lru_cache.get_list().end()) { it = compressed_items.get_list().begin(); compressed = true; } } } #ifndef NDEBUG { std::scoped_lock memfile_lock(memfiles_mutex); memfiles.check(); } #endif for (std::set::iterator it = dirty_evicted_items.begin(); it != dirty_evicted_items.end(); ++it) { if(!dirty_list.empty()) dirty_list+=", "; dirty_list+=hex(*it); char ch_compressed = compressed_evicted_dirty; while (file->Write(file_pos, &ch_compressed, sizeof(ch_compressed)) != sizeof(ch_compressed)) { Server->Log("Error writing compressed flag (2). Retrying...", LL_ERROR); Server->wait(1000); } file_pos += sizeof(ch_compressed); unsigned int key_size = static_cast(it->size()); while (file->Write(file_pos, reinterpret_cast(&key_size), sizeof(key_size)) != sizeof(key_size)) { Server->Log("Error writing key size (2). Retrying...", LL_ERROR); Server->wait(1000); } file_pos += sizeof(key_size); while (file->Write(file_pos, it->c_str(), key_size) != key_size) { Server->Log("Error writing key (2). Retrying...", LL_ERROR); Server->wait(1000); } file_pos += key_size; } file->Sync(); Server->Log("List of written dirty items for transid "+convert(transid)+": "+dirty_list); if(do_submit) { submit_dummy(transid); dirty_evicted_items.clear(); } else { while(true) { std::unique_ptr file_nosubmit(cachefs->openFile(fn+".nosubmit", MODE_WRITE)); if(file_nosubmit.get()) { break; } else { Server->Log("Error creating "+fn+".nosubmit. Retrying...", LL_WARNING); Server->wait(1000); } } } file.reset(); if(!cachefs->rename(fn+".new", has_memf ? (fn + ".mem") : fn)) { Server->Log("Error renaming "+fn+" to "+fn+".new", LL_ERROR); return false; } evict_cond.notify_all(); check_submission_items(); return true; } bool TransactionalKvStore::read_from_deleted_file( const std::string& fn, int64 transaction_id, bool do_submit ) { std::unique_ptr file(cachefs->openFile(fn, MODE_READ)); if(file.get()==nullptr) { Server->Log("Cannot open \""+fn+"\". " + os_last_error_str(), LL_INFO); return true; } std::vector del_keys; while(true) { unsigned int key_size; if(file->Read(reinterpret_cast(&key_size), sizeof(key_size))!=sizeof(key_size)) { break; } std::string key; key.resize(key_size); if(file->Read(&key[0], key_size)!=key_size) { Server->Log("Error reading key from deleted file", LL_ERROR); return false; } if(do_submit) { del_keys.push_back(key); if(del_keys.size()>online_kv_store->max_del_size()) { SSubmissionItem item; item.keys = del_keys; item.transid = transaction_id; item.action = SubmissionAction_Delete; item.size = 0; item.compressed=false; submission_queue_add(item, false); addDirtyItem(transaction_id, item.key, false); { std::scoped_lock dirty_lock(dirty_item_mutex); ++num_delete_items[transaction_id]; } del_keys.clear(); } } else { queued_dels.insert(key); } } if(do_submit && !del_keys.empty()) { SSubmissionItem item; item.keys = del_keys; item.transid = transaction_id; item.action = SubmissionAction_Delete; item.size = 0; item.compressed=false; submission_queue_add(item, false); addDirtyItem(transaction_id, item.key, false); std::scoped_lock dirty_lock(dirty_item_mutex); ++num_delete_items[transaction_id]; } return true; } bool TransactionalKvStore::write_to_deleted_file(const std::string& fn, bool do_submit) { std::unique_ptr file(cachefs->openFile(fn, MODE_WRITE)); int64 file_pos=0; if(file.get()==nullptr) { Server->Log("Error opening deleted file for writing", LL_ERROR); return false; } std::lock_guard sub_lock(submission_mutex); std::vector del_keys; for(std::set::iterator it=queued_dels.begin();it!=queued_dels.end();++it) { unsigned int key_size = static_cast(it->size()); while(file->Write(file_pos, reinterpret_cast(&key_size), sizeof(key_size))!=sizeof(key_size)) { Server->Log("Error writing key size. Retrying...", LL_ERROR); Server->wait(1000); } file_pos+=sizeof(key_size); while(file->Write(file_pos, it->c_str(), key_size)!=key_size) { Server->Log("Error writing key. Retrying...", LL_ERROR); Server->wait(1000); } file_pos+=key_size; if(do_submit) { del_keys.push_back(*it); if(del_keys.size()>online_kv_store->max_del_size()) { SSubmissionItem item; item.keys = del_keys; item.transid = transid; item.action = SubmissionAction_Delete; item.size=0; item.compressed=false; submission_queue_add(item, false); addDirtyItem(transid, item.key, false); { std::scoped_lock dirty_lock(dirty_item_mutex); ++num_delete_items[transid]; } del_keys.clear(); } } } if(do_submit && !del_keys.empty()) { SSubmissionItem item; item.keys = del_keys; item.transid = transid; item.action = SubmissionAction_Delete; item.size=0; item.compressed=false; submission_queue_add(item, false); addDirtyItem(transid, item.key, false); std::scoped_lock dirty_lock(dirty_item_mutex); ++num_delete_items[transid]; } file->Sync(); if(do_submit) { queued_dels.clear(); } return true; } void TransactionalKvStore::delete_item(fuse_io_context* io, const std::string& key, bool compressed_item, std::unique_lock& cache_lock, int64 force_delete_transid, int64 skip_transid, DeleteImm del_imm, int64 delete_only, bool rm_submitted, int64 ignore_sync_wait_transid) { assert(cache_lock.owns_lock()); assert(io == nullptr || del_imm== DeleteImm::None); Server->Log("Deleting object "+hex(key)+" compressed="+convert(compressed_item)+ " force_delete_transid="+convert(force_delete_transid), LL_INFO); std::vector del_queue_local; std::unique_lock submission_lock(submission_mutex); for(size_t i=0;isecond.find(key) != it_ld->second.end()) { nosubmit_dirty = true; } } if(submission_items.find(std::make_pair(ctransid, key))==submission_items.end() && !nosubmit_dirty && (ctransid == transid || ctransid == ignore_sync_wait_transid || !is_sync_wait_item(key, ctransid) ) ) { if (!compressed_item && ctransid==transid) { rm_mem_file(io, ctransid, key, rm_submitted); } if (ctransid == transid && fd_cache.has_key(key)) { Server->Log("Found key " + keypath2(key, transid) + " in fd cache during deletion", LL_ERROR); abort(); } std::string del_path = transactions[i].name + cachefs->fileSep() + hexpath(key) + (compressed_item ? ".comp" : ""); if (del_imm!=DeleteImm::None) { del_queue_local.push_back(del_path); } else { IScopedLock lock(del_file_mutex.get()); del_file_queue.insert(del_path); } } } } if (!del_queue_local.empty()) { submission_lock.unlock(); if (del_imm == DeleteImm::Unlock) { cache_lock.unlock(); } for (size_t i = 0; i < del_queue_local.size(); ++i) { cachefs->deleteFile(del_queue_local[i]); } if (del_imm == DeleteImm::Unlock) { cache_lock.lock(); } } } void TransactionalKvStore::run_del_file_queue() { IScopedLock lock_single(del_file_single_mutex.get()); IScopedLock lock(del_file_mutex.get()); while (!del_file_queue.empty()) { std::string fn; bool is_begin; if (!prio_del_file.empty() && del_file_queue.find(prio_del_file) != del_file_queue.end()) { fn = prio_del_file; is_begin = false; } else { fn = *del_file_queue.begin(); is_begin = true; } lock.relock(nullptr); cachefs->deleteFile(fn); lock.relock(del_file_mutex.get()); if (is_begin && *del_file_queue.begin() == fn) { del_file_queue.erase(del_file_queue.begin()); } else { del_file_queue.erase(fn); } if (!is_begin) { prio_del_file_cond->notify_all(); } } } #ifdef HAS_ASYNC fuse_io_context::io_uring_task TransactionalKvStore::run_del_file_queue_async(fuse_io_context& io) { co_await io.run_in_threadpool([this]() { this->run_del_file_queue(); }, "rdel file"); } #endif void TransactionalKvStore::wait_for_del_file(const std::string & fn) { IScopedLock lock(del_file_mutex.get()); bool did_wait = false; while (del_file_queue.find(fn)!= del_file_queue.end()) { prio_del_file = fn; did_wait = true; prio_del_file_cond->wait(&lock, 1); } if (did_wait && prio_del_file == fn) { prio_del_file.clear(); } } void TransactionalKvStore::cleanup(bool init) { std::vector files = cachefs->listFiles(std::string()); int64 maxsubmitted=-1; for(size_t i=0;i commited(cachefs->openFile(files[i].name + cachefs->fileSep() + "commited", MODE_READ)); std::unique_ptr dirty(cachefs->openFile(files[i].name+ cachefs->fileSep()+"dirty", MODE_READ)); std::unique_ptr nosubmit(cachefs->openFile(files[i].name+ cachefs->fileSep()+"dirty.nosubmit", MODE_READ)); std::unique_ptr dirty_mem(cachefs->openFile(files[i].name + cachefs->fileSep() + "dirty.mem", MODE_READ)); if( (dirty.get()!=nullptr || nosubmit.get()!=nullptr || commited.get()!=nullptr) && dirty_mem.get()==nullptr) { maxsubmitted = (std::max)(maxsubmitted, ctransid); } } } for(size_t i=0;i dirty(cachefs->openFile(files[i].name+ cachefs->fileSep()+"dirty", MODE_READ)); std::unique_ptr dirty_mem(cachefs->openFile(files[i].name + cachefs->fileSep() + "dirty.mem", MODE_READ)); std::unique_ptr invalid(cachefs->openFile(files[i].name+ cachefs->fileSep()+"invalid", MODE_READ)); if(ctransid==maxsubmitted || ctransid==transid) { continue; } { std::scoped_lock dirty_lock(dirty_item_mutex); std::map::iterator it_di = num_dirty_items.find(ctransid); if (it_di != num_dirty_items.end()) { continue; } } assert(num_mem_files.find(ctransid) == num_mem_files.end()); if( (dirty.get()==nullptr && dirty_mem.get()==nullptr) || invalid.get()!=nullptr ) { if(invalid) Server->Log("Removing invalid transaction " + std::to_string(ctransid) + " (3)", LL_INFO); remove_transaction(ctransid); } else if(ctransid commited(cachefs->openFile(files[i].name + cachefs->fileSep() + "commited", MODE_READ)); std::unique_ptr nosubmit(cachefs->openFile(files[i].name + cachefs->fileSep() + "dirty.nosubmit", MODE_READ)); if (dirty.get() == nullptr || nosubmit.get() != nullptr || commited.get() != nullptr) { remove_transaction(ctransid); } } else { remove_transaction(ctransid); } } } } } update_transactions(); } void TransactionalKvStore::clean_snapshot( int64 new_trans ) { cachefs->deleteFile("trans_"+convert(new_trans)+ cachefs->fileSep() + "deleted"); cachefs->deleteFile("trans_"+convert(new_trans)+ cachefs->fileSep() + "dirty"); cachefs->deleteFile("trans_" + convert(new_trans) + cachefs->fileSep() + "dirty.evicted"); cachefs->deleteFile("trans_" + convert(new_trans) + cachefs->fileSep() + "dirty.submitted"); cachefs->deleteFile("trans_"+convert(new_trans)+ cachefs->fileSep() + "dirty.nosubmit"); cachefs->deleteFile("trans_"+convert(new_trans)+ cachefs->fileSep() + "commited"); if(cachefs->directoryExists("trans_"+convert(new_trans)+ cachefs->fileSep() + "submitted")) { cachefs->removeDirRecursive( "trans_"+convert(new_trans)+ cachefs->fileSep() + "submitted"); } if(cachefs->directoryExists( "trans_"+convert(new_trans)+ cachefs->fileSep() + "evicted")) { cachefs->removeDirRecursive( "trans_"+convert(new_trans)+ cachefs->fileSep() + "evicted"); } } int64 TransactionalKvStore::get_dirty_bytes() { return dirty_bytes; } int64 TransactionalKvStore::get_submitted_bytes() { return submitted_bytes; } int64 TransactionalKvStore::get_total_submitted_bytes() { return total_submitted_bytes; } std::map TransactionalKvStore::get_num_dirty_items() { std::scoped_lock lock(dirty_item_mutex); return num_dirty_items; } std::map TransactionalKvStore::get_num_memfile_items() { std::scoped_lock lock(cache_mutex); return num_mem_files; } void TransactionalKvStore::submit_dummy(int64 transaction_id) { SSubmissionItem item; item.key = std::string(); item.transid = transaction_id; item.action = SubmissionAction_Dirty; item.size=0; item.compressed=false; submission_items.insert(std::make_pair(std::make_pair(transaction_id, std::string()), submission_queue_add(item, false))); addDirtyItem(transaction_id, item.key, false); } int64 TransactionalKvStore::get_cache_size() { return cachesize; } void TransactionalKvStore::reset() { assert(fuse_io_context::is_sync_thread()); std::unique_lock lock(cache_mutex); wait_for_all_retrievals(lock); std::unique_lock sub_lock(submission_mutex); assert(open_files.empty()); fd_cache.clear(); queued_dels.clear(); std::unique_lock dirty_lock(dirty_item_mutex); bool compressed=false; size_t curr_dirty_items=num_dirty_items[transid]; #ifdef DIRTY_ITEM_CHECK std::map curr_dirty_item_keys = dirty_items[transid]; #endif for(common::lrucache::list_t::iterator it=lru_cache.get_list().begin(); !compressed || it!=lru_cache.get_list().end();) { if(compressed || it!=lru_cache.get_list().end()) { if(it->second.dirty) { assert(curr_dirty_items > 0); --curr_dirty_items; #ifdef DIRTY_ITEM_CHECK assert(curr_dirty_item_keys[*it->first] > 0); --curr_dirty_item_keys[*it->first]; if (curr_dirty_item_keys[*it->first] == 0) { curr_dirty_item_keys.erase(curr_dirty_item_keys.find(*it->first)); } #endif std::unique_ptr input_file(cachefs->openFile(keypath2(*it->first, transid) + (compressed?".comp":""), MODE_READ)); if(input_file.get()==nullptr) { Server->Log("Error opening file to reset", LL_ERROR); continue; } dirty_bytes-=input_file->Size(); } ++it; } if(it==lru_cache.get_list().end()) { it=compressed_items.get_list().begin(); compressed=true; } } if(curr_dirty_items==0) { num_dirty_items.erase(num_dirty_items.find(transid)); #ifdef DIRTY_ITEM_CHECK assert(curr_dirty_item_keys.empty()); dirty_items.erase(dirty_items.find(transid)); #endif } else { num_dirty_items[transid] = curr_dirty_items; #ifdef DIRTY_ITEM_CHECK dirty_items[transid] = curr_dirty_item_keys; #endif } lru_cache.clear(); compressed_items.clear(); for(std::list::iterator it=submission_queue.begin(); it!=submission_queue.end();) { if(it->action==SubmissionAction_Evict || it->action==SubmissionAction_Dirty || it->action==SubmissionAction_Delete || it->action==SubmissionAction_Compress ) { if(it->action!=SubmissionAction_Delete) { submission_items.erase(submission_items.find(std::make_pair(it->transid, it->key))); } removeDirtyItem(it->transid, it->key); std::map::iterator di = num_dirty_items.find(it->transid); if(di!=num_dirty_items.end()) { if(di->second==0) { num_dirty_items.erase(di); #ifdef DIRTY_ITEM_CHECK assert(dirty_items[it->transid].empty()); dirty_items.erase(dirty_items.find(it->transid)); #endif } } SMemFile* f; { std::scoped_lock memfile_lock(memfiles_mutex); f = memfiles.get(std::make_pair(it->transid, it->key), false); } if (f != nullptr) { assert(submitted_memfiles > 0); --submitted_memfiles; rm_mem_file(nullptr, it->transid, it->key, true); } dirty_bytes-=it->size; submitted_bytes-=it->size; std::list::iterator it_del = it; ++it; submission_queue_rm(it_del); } else { ++it; } } dirty_lock.unlock(); check_submission_items(); int64 maxtrans = set_active_transactions(lock, false); Server->Log("Resetting to transaction "+convert(maxtrans)); assert(maxtrans>=0); size_t retry_n=0; do { transid = online_kv_store->new_transaction(retry_n>retry_log_n); if(transid==0) { Server->Log("Error requesting new transaction. Retrying...", LL_WARNING); retryWait(++retry_n); } } while(transid==0); retry_n=0; while(!cachefs->createSnapshot("trans_"+std::to_string(maxtrans), "trans_"+std::to_string(transid))) { Server->Log("Error creating new transaction subvolume. Retrying...", LL_ERROR); retryWait(++retry_n); } update_transactions(); clean_snapshot(transid); cachesize=0; read_keys(lock, transpath2(), false); basetrans = maxtrans; retry_n=0; while(!read_dirty_items(lock, basetrans, transid)) { Server->Log("Error reading dirty items. Retrying..."); retryWait(++retry_n); } } bool TransactionalKvStore::is_congested() { std::scoped_lock lock(cache_mutex); return is_congested_nolock(); } bool TransactionalKvStore::is_congested_nolock() { return remaining_gets != std::string::npos; } bool TransactionalKvStore::is_congested_async() { return remaining_gets != std::string::npos; } int64 TransactionalKvStore::get_memfile_bytes() { return memfile_size; } int64 TransactionalKvStore::get_submitted_memfile_bytes() { return submitted_memfile_size; } bool TransactionalKvStore::is_memfile_complete(int64 ttransid) { std::scoped_lock lock(cache_mutex); return num_mem_files.find(ttransid) == num_mem_files.end(); } std::string TransactionalKvStore::meminfo() { assert(fuse_io_context::is_sync_thread()); std::string ret; { std::unique_lock cache_lock(cache_mutex); ret += "##TransactionalKvStore:\n"; #define MEMINFO_ITEM_SIZE(name, itemsize) ret += " " #name ": " + convert(name.size()) + " * " + PrettyPrintBytes(itemsize)+" = "+PrettyPrintBytes(name.size()*(itemsize)) + "\n" MEMINFO_ITEM_SIZE(lru_cache, sizeof(std::string) + sizeof(bool) + 3 * sizeof(void*)); size_t lru_second_chances = 0; for (auto it : lru_cache.get_list()) { if (it.second.chances > 0) { ++lru_second_chances; } } ret += " lru_cache items with more chances: " + convert(lru_second_chances) + (lru_cache.size()>0 ? (" ("+ convert(lru_second_chances*100/lru_cache.size())+"%)\n") :"\n"); MEMINFO_ITEM_SIZE(compressed_items, sizeof(std::string) + sizeof(bool) + 3 * sizeof(void*)); lru_second_chances = 0; for (auto it : compressed_items.get_list()) { if (it.second.chances > 0) { ++lru_second_chances; } } ret += " compressed_items items with more chances: " + convert(lru_second_chances) + (compressed_items.size()>0 ? (" (" + convert(lru_second_chances * 100 / compressed_items.size()) + "%)\n") : "\n"); MEMINFO_ITEM_SIZE(open_files, sizeof(std::string) + sizeof(SFdKey)); MEMINFO_ITEM_SIZE(read_only_open_files, sizeof(IFsFile*) + sizeof(ReadOnlyFileWrapper*) + sizeof(ReadOnlyFileWrapper)); MEMINFO_ITEM_SIZE(preload_once_items, sizeof(std::string)+sizeof(int)); MEMINFO_ITEM_SIZE(preload_once_delayed_removal, sizeof(std::string)+sizeof(int64)); { std::scoped_lock submission_lock(submission_mutex); MEMINFO_ITEM_SIZE(submission_queue, sizeof(SSubmissionItem)); MEMINFO_ITEM_SIZE(submission_items, sizeof(int64) + sizeof(std::string) + sizeof(std::list::iterator)); MEMINFO_ITEM_SIZE(dirty_evicted_items, sizeof(std::string)); std::scoped_lock dirty_lock(dirty_item_mutex); MEMINFO_ITEM_SIZE(nosubmit_dirty_items, sizeof(int64) + sizeof(std::set)); for (auto it : nosubmit_dirty_items) { ret+= " nosubmit_dirty_items["+convert(it.first)+"]: "+convert(it.second.size())+" * " + PrettyPrintBytes(sizeof(std::string)) + " = " + PrettyPrintBytes(it.second.size()*sizeof(std::string))+"\n"; } } MEMINFO_ITEM_SIZE(nosubmit_untouched_items, sizeof(std::string)); { std::scoped_lock dirty_lock(dirty_item_mutex); MEMINFO_ITEM_SIZE(num_dirty_items, sizeof(int64) + sizeof(size_t)); #ifdef DIRTY_ITEM_CHECK for (auto it : dirty_items) { ret += " dirty_items[" + convert(it.first) + "]: " + convert(it.second.size()) + " * " + PrettyPrintBytes(sizeof(std::string) + sizeof(size_t)) + " = "+PrettyPrintBytes(it.second.size()*(sizeof(std::string) + sizeof(size_t)))+"\n"; } MEMINFO_ITEM_SIZE(dirty_items, sizeof(int64) + sizeof(size_t)); #endif MEMINFO_ITEM_SIZE(num_delete_items, sizeof(int64) + sizeof(size_t)); } ret += " fd_cache: " + convert(fd_cache.size()) + "/" + convert(fd_cache_size) + " * " + PrettyPrintBytes(sizeof(std::string) + sizeof(SFdKey) + 3 * sizeof(void*)) + " = " + PrettyPrintBytes(fd_cache.size()*(sizeof(std::string) + sizeof(SFdKey) + 3 * sizeof(void*))) + "\n"; MEMINFO_ITEM_SIZE(queued_dels, sizeof(std::string)); MEMINFO_ITEM_SIZE(in_retrieval, sizeof(std::string)); MEMINFO_ITEM_SIZE(transactions, sizeof(SFile)); { std::scoped_lock memfile_lock(memfiles_mutex); MEMINFO_ITEM_SIZE(memfiles, sizeof(int64) + sizeof(std::string) + sizeof(SMemFile) + 3 * sizeof(void*)); ret += " memfile used size: " + PrettyPrintBytes(memfile_size) + "\n"; int64 memfile_size_check = 0; for (auto it : memfiles.get_list()) { if(it.second.file!=nullptr) memfile_size_check+=it.second.file->Size(); } ret += " memfile_size_check: " + PrettyPrintBytes(memfile_size_check) + "\n"; int64 memfile_size_non_dirty = 0; int64 memfile_size_dirty = 0; for (auto it : memfiles.get_list()) { if (it.second.file != nullptr) { SCacheVal* dirty = cache_get(lru_cache, it.first->second, cache_lock, false); if (dirty != nullptr && !dirty->dirty) { memfile_size_non_dirty += it.second.file->Size(); } else if (dirty != nullptr && dirty->dirty) { memfile_size_dirty+= it.second.file->Size(); } } } ret += " memfile_size_non_dirty: " + PrettyPrintBytes(memfile_size_non_dirty) + "\n"; ret += " memfile_size_dirty: " + PrettyPrintBytes(memfile_size_dirty) + "\n"; ret += " memfile_stat_bitmaps: " + convert(memfile_stat_bitmaps.capacity()) + " * " + PrettyPrintBytes(sizeof(int64) + sizeof(Bitmap) + sizeof(std::unique_ptr)) + "\n"; for (auto& it : memfile_stat_bitmaps) { ret += " memfile_stat_bitmaps["+convert(it.first)+"]: "+ PrettyPrintBytes(it.second->meminfo()) + "\n"; } MEMINFO_ITEM_SIZE(num_mem_files, sizeof(int64) + sizeof(size_t)); } ret += " submit_bundle: " + convert(submit_bundle.capacity()) + " * " + PrettyPrintBytes(sizeof(SSubmissionItem)+sizeof(bool)) + " = " + PrettyPrintBytes(submit_bundle.capacity()*(sizeof(SSubmissionItem) + sizeof(bool)))+"\n"; IScopedLock bundle_lock(submit_bundle_item_mutex.get()); MEMINFO_ITEM_SIZE(submit_bundle_items_a, sizeof(std::string) + sizeof(int64)); MEMINFO_ITEM_SIZE(submit_bundle_items_b, sizeof(std::string) + sizeof(int64)); } { IScopedLock lock(del_file_mutex.get()); MEMINFO_ITEM_SIZE(del_file_queue, sizeof(std::string)); } ret += online_kv_store->meminfo(); return ret; } void TransactionalKvStore::shrink_mem() { assert(fuse_io_context::is_sync_thread()); std::scoped_lock cache_lock(cache_mutex); evict_non_dirty_memfiles = true; common::lrucache::list_t::iterator it = fd_cache.eviction_iterator_start(); if (it == fd_cache.eviction_iterator_finish()) { return; } --it; while (true) { bool last = it == fd_cache.eviction_iterator_finish(); if (open_files.find(*it->first) == open_files.end()) { assert(dynamic_cast(it->second.fd) == nullptr); Server->destroy(it->second.fd); std::string item_key = *it->first; if (!last) --it; fd_cache.del(item_key); } else { if (!last) --it; } if (last) break; } } int64 TransactionalKvStore::get_total_cache_miss_backend() { return total_cache_miss_backend; } int64 TransactionalKvStore::get_total_cache_miss_decompress() { return total_cache_miss_decompress; } int64 TransactionalKvStore::get_total_dirty_ops() { return total_dirty_ops; } int64 TransactionalKvStore::get_total_put_ops() { return total_put_ops; } int64 TransactionalKvStore::get_total_compress_ops() { return total_compress_ops; } void TransactionalKvStore::disable_compression(int64 disablems) { std::scoped_lock lock(cache_mutex); compression_starttime = Server->getTimeMS() + disablems; } void TransactionalKvStore::set_num_second_chances_callback(INumSecondChancesCallback * cb) { std::scoped_lock lock(cache_mutex); num_second_chances_cb = cb; if (num_second_chances_cb == nullptr) return; for (auto& it : lru_cache.get_list()) { it.second = cache_val(*it.first, it.second.dirty); } for (auto& it : compressed_items.get_list()) { it.second = cache_val(*it.first, it.second.dirty); } } int64 TransactionalKvStore::get_total_hits() { std::scoped_lock lock(cache_mutex); return total_hits; } int64 TransactionalKvStore::get_total_hits_async() { return total_hits; } int64 TransactionalKvStore::get_total_memory_hits() { std::scoped_lock lock(cache_mutex); return total_memory_hits; } int64 TransactionalKvStore::get_total_memory_hits_async() { return total_memory_hits; } int64 TransactionalKvStore::set_active_transactions(std::unique_lock& cache_lock, bool continue_incomplete) { std::vector files = cachefs->listFiles(std::string()); std::vector active_transactions; int64 maxtrans=-1; int64 mintrans = -1; for (size_t i = 0; i < files.size(); ++i) { if (files[i].isdir && files[i].name.find("trans_") == 0) { int64 ctransid = watoi64(getafter("trans_", files[i].name)); std::unique_ptr dirty_mem(cachefs->openFile(files[i].name + cachefs->fileSep() + "dirty.mem", MODE_READ)); if (dirty_mem.get() != nullptr && (mintrans == -1 || ctransid < mintrans) ) { mintrans = ctransid; } } } for(size_t i=0;i dirty(cachefs->openFile(files[i].name+ cachefs->fileSep()+"dirty", MODE_READ)); std::unique_ptr commited(cachefs->openFile(files[i].name+ cachefs->fileSep()+"commited", MODE_READ)); std::unique_ptr invalid(cachefs->openFile(files[i].name+ cachefs->fileSep()+"invalid", MODE_READ)); std::unique_ptr dirty_mem(cachefs->openFile(files[i].name + cachefs->fileSep() + "dirty.mem", MODE_READ)); if (dirty_mem.get() != nullptr) { continue; } if(dirty.get()!=nullptr && commited.get()==nullptr && continue_incomplete && invalid.get()==nullptr && (mintrans==-1 || ctransid nosubmit(cachefs->openFile(files[i].name+ cachefs->fileSep()+"dirty.nosubmit", MODE_READ)); if(nosubmit.get()==nullptr) { dirty.reset(); while(!read_from_deleted_file(files[i].name+ cachefs->fileSep()+"deleted", ctransid, true)) { Server->Log("Error reading from deleted file. Retrying...", LL_ERROR); Server->wait(10000); } while(!read_from_dirty_file(cache_lock, files[i].name+ cachefs->fileSep()+"dirty", ctransid, true, 0)) { Server->Log("Error reading from dirty file. Retrying...", LL_ERROR); Server->wait(10000); } } maxtrans = (std::max)(ctransid, maxtrans); active_transactions.push_back(ctransid); } else if(commited.get()!=nullptr && invalid.get()==nullptr && (mintrans == -1 || ctransidLog("Removing transaction "+convert(ctransid)+" (no dirty items 1)"); remove_transaction(ctransid); } else { std::unique_ptr invalid(cachefs->openFile(files[i].name+ cachefs->fileSep()+"invalid", MODE_WRITE)); } } } } for (size_t i = 0; i < files.size(); ++i) { if (files[i].isdir && files[i].name.find("trans_") == 0) { int64 ctransid = watoi64(getafter("trans_", files[i].name)); std::unique_ptr dirty_mem(cachefs->openFile(files[i].name + cachefs->fileSep() + "dirty.mem", MODE_READ)); if (dirty_mem.get() != nullptr) { std::scoped_lock dirty_lock(dirty_item_mutex); if (num_dirty_items.find(ctransid) == num_dirty_items.end()) { DIRTY_ITEM(assert(dirty_items.find(ctransid) == dirty_items.end())); Server->Log("Removing transaction " + convert(ctransid)+" (no dirty items 2)"); remove_transaction(ctransid); } else { std::unique_ptr invalid(cachefs->openFile(files[i].name + cachefs->fileSep() + "invalid", MODE_WRITE)); } } } } if(!online_kv_store->set_active_transactions(active_transactions)) { throw std::runtime_error("Error setting active transactions"); } return maxtrans; } bool TransactionalKvStore::wait_for_retrieval(std::unique_lock& lock, const std::string& key ) { bool waited=false; std::map::iterator it; while((it=in_retrieval.find(key))!=in_retrieval.end() || (it=in_retrieval.find(std::string())) != in_retrieval.end()) { ++it->second; ++retrieval_waiters_sync; retrieval_cond.wait(lock); --retrieval_waiters_sync; waited=true; } return waited; } bool TransactionalKvStore::wait_for_retrieval_poll(std::unique_lock& lock, const std::string& key) { bool waited = false; std::map::iterator it; while ((it = in_retrieval.find(key)) != in_retrieval.end() || (it = in_retrieval.find(std::string())) != in_retrieval.end()) { lock.unlock(); Server->wait(100); lock.lock(); waited = true; } return waited; } #ifdef HAS_ASYNC fuse_io_context::io_uring_task TransactionalKvStore::wait_for_retrieval_async(fuse_io_context& io, std::unique_lock lock, const std::string key) { RetrievalRes res; res.lock = std::move(lock); res.waited = false; std::map::iterator it; while ( (it=in_retrieval.find(key)) != in_retrieval.end() || (it=in_retrieval.find(std::string())) != in_retrieval.end()) { ++it->second; ++retrieval_waiters_async; res.lock.unlock(); co_await RetrievalAwaiter(*this); res.lock.lock(); --retrieval_waiters_async; res.waited = true; } co_return res; } #endif void TransactionalKvStore::initiate_retrieval( const std::string& key ) { assert(in_retrieval.find(key) == in_retrieval.end()); in_retrieval.insert(std::make_pair(key, 0)); } void TransactionalKvStore::finish_retrieval(const std::string& key ) { auto it = in_retrieval.find(key); if (it == in_retrieval.end()) abort(); assert(it != in_retrieval.end()); size_t r_waiters = it->second; in_retrieval.erase(it); assert(retrieval_waiters_async == 0); if (r_waiters > 0 ) { if (retrieval_waiters_async>0) abort(); retrieval_cond.notify_all(); } } #ifdef HAS_ASYNC void TransactionalKvStore::finish_retrieval_async(fuse_io_context& io, std::unique_lock lock, const std::string& key) { auto it = in_retrieval.find(key); if (it == in_retrieval.end()) abort(); assert(it != in_retrieval.end()); size_t r_waiters = it->second; in_retrieval.erase(it); assert(retrieval_waiters_sync == 0); if (r_waiters > 0 ) { if (retrieval_waiters_sync > 0) abort(); lock.unlock(); resume_retrieval_awaiters(); } } #endif void TransactionalKvStore::wait_for_all_retrievals(std::unique_lock& lock ) { while(!in_retrieval.empty()) { lock.unlock(); Server->wait(10); lock.lock(); } } bool TransactionalKvStore::compress_item( const std::string& key, int64 transaction_id, IFsFile* src, int64& size_diff, int64& dst_size, bool sync) { if (!with_prev_link) { sync = false; } else if(!sync) { std::scoped_lock lock(cache_mutex); if (nosubmit_untouched_items.find(key) != nosubmit_untouched_items.end()) { sync = true; } } wait_for_del_file(keypath2(key, transaction_id) + ".comp"); std::unique_ptr src_del; if (src == nullptr) { src_del.reset(cachefs->openFile(keypath2(key, transaction_id), MODE_READ_SEQUENTIAL)); src = src_del.get(); } else { src->Seek(0); } if(src==nullptr) { Server->Log("Error opening source file "+keypath2(key, transaction_id)+" in compress_item. "+os_last_error_str(), LL_ERROR); assert(false); return false; } IFsFile* dst = cachefs->openFile(keypath2(key, transaction_id)+".comp", MODE_WRITE); if(dst==nullptr) { if (!cachefs->directoryExists(ExtractFilePath(keypath2(key, transaction_id) + ".comp"))) { cachefs->createDir(ExtractFilePath(keypath2(key, transaction_id) + ".comp")); dst = cachefs->openFile(keypath2(key, transaction_id) + ".comp", MODE_WRITE); } if (dst == nullptr) { Server->Log("Error opening dest file " + keypath2(key, transaction_id) + ".comp in compress_item. " + os_last_error_str(), LL_ERROR); assert(false); return false; } } ScopedDeleteFile del_comp_f(dst); std::unique_ptr compress_encrypt(compress_encrypt_factory->createCompressAndEncrypt(encryption_key, src, online_kv_store, background_comp_method)); std::vector buf; buf.resize(32768); char dig[16] = {}; if(dst->Write(dig, sizeof(dig))!=sizeof(dig)) { Server->Log("Error writing md5 placeholder", LL_WARNING); assert(false); return false; } while(true) { size_t read = compress_encrypt->read(buf.data(), static_cast<_u32>(buf.size())); if(read!=0 && read!=std::string::npos) { if(dst->Write(buf.data(), static_cast<_u32>(read))!= static_cast<_u32>(read)) { Server->Log("Error writing compressed data", LL_WARNING); assert(false); return false; } } else if (read == std::string::npos) { Server->Log("Error compressing data", LL_WARNING); assert(false); return false; } else { break; } } std::string md5sum = compress_encrypt->md5sum(); assert(md5sum.size()==sizeof(dig)); if(dst->Write(0, md5sum.data(), sizeof(dig))!=sizeof(dig)) { Server->Log("Error writing md5sum", LL_WARNING); assert(false); return false; } size_diff = dst->Size() - src->Size(); dst_size = dst->Size(); if(sync) { dst->Sync(); } del_comp_f.release(); Server->destroy(dst); return true; } bool TransactionalKvStore::decompress_item( const std::string& key, int64 transaction_id, IFsFile* tmpl_file, int64& size_diff, int64& src_size, bool sync) { std::unique_ptr src(cachefs->openFile(keypath2(key, transaction_id)+".comp", MODE_READ_SEQUENTIAL)); if(!src.get()) { Server->Log("Error opening source file "+keypath2(key, transaction_id)+".comp in decompress_item. "+os_last_error_str(), LL_ERROR); return false; } src_size = src->Size(); IFsFile* dst; if (tmpl_file == nullptr) { dst = cachefs->openFile(keypath2(key, transaction_id), MODE_WRITE); } else { dst = tmpl_file; } if(dst==nullptr) { std::string errmsg; int64 err = os_last_error(errmsg); Server->Log("Error opening dest file "+keypath2(key, transaction_id)+" in decompress_item. "+errmsg+" (code "+convert(err)+")", LL_ERROR); return false; } ScopedDeleteFile del_dst_f(nullptr); if (tmpl_file == nullptr) { del_dst_f.reset(dst); } std::unique_ptr decompress_decrypt(compress_encrypt_factory->createDecryptAndDecompress(encryption_key, dst)); std::vector buf; buf.resize(32768); char dig[16] = {}; if(src->Read(dig, sizeof(dig))!=sizeof(dig)) { Server->Log("Error reading md5 placeholder", LL_WARNING); return false; } int64 read_size=0; while(true) { _u32 read = src->Read(buf.data(), static_cast<_u32>(buf.size())); if(read>0) { read_size+=read; if(!decompress_decrypt->put(buf.data(), read)) { Server->Log("Error during decompression (of "+ keypath2(key, transaction_id) + ".comp)", LL_WARNING); return false; } } else { if(!decompress_decrypt->finalize()) { Server->Log("Error during decompression finalize (of "+ keypath2(key, transaction_id) + ".comp)", LL_WARNING); return false; } break; } } std::string md5sum = decompress_decrypt->md5sum(); if(hex(std::string(dig, sizeof(dig)))!=md5sum) { Server->Log("Checksum wrong after decompression. Decompressed " +convert(read_size)+" bytes to "+convert(dst->Size())+" bytes (of "+ keypath2(key, transaction_id) + ".comp)", LL_WARNING); return false; } int64 dst_size = dst->Size(); size_diff = dst_size - src->Size(); if(sync) { dst->Sync(); } #ifdef HAS_FILE_SIZE_CACHE if(tmpl_file!=nullptr) dst->setCachedSize(dst_size); #endif del_dst_f.release(); if (tmpl_file == nullptr) { Server->destroy(dst); } return true; } int64 TransactionalKvStore::get_comp_bytes() { return comp_bytes; } void TransactionalKvStore::remove_compression_evicition_submissions(std::unique_lock& cache_lock) { std::scoped_lock submission_lock(submission_mutex); for(std::list::iterator it=submission_queue.begin(); it!=submission_queue.end();) { if( (it->action==SubmissionAction_Compress || it->action==SubmissionAction_Evict) && it->transid==transid ) { submission_items.erase(submission_items.find(std::make_pair(it->transid, it->key))); Server->Log("Removed submission (" + convert(it->transid) + "," + hex(it->key)+", " + (it->action == SubmissionAction_Compress ? "compression" : "eviction" ) + + ", compressed="+convert(it->compressed)+")"); if (it->action == SubmissionAction_Evict) { submitted_bytes -= it->size; } if (it->action == SubmissionAction_Evict && it->compressed) { cache_put_back(compressed_items, it->key, cache_val_nc(true), cache_lock); } else if(it->action == SubmissionAction_Evict) { cache_put_back(lru_cache, it->key, cache_val_nc(true), cache_lock); } else { cache_put_back(lru_cache, it->key, cache_val_nc(it->compressed), cache_lock); } SMemFile* memf; { std::scoped_lock memfile_lock(memfiles_mutex); memf = memfiles.get(std::make_pair(it->transid, it->key)); } if (memf != nullptr) { if (memf->key != it->key) { Server->Log("Memfile key wrong. Expected " + hexpath(it->key) + " got " + hexpath(memf->key), LL_ERROR); abort(); } assert(submitted_memfiles > 0); --submitted_memfiles; submitted_memfile_size -= memf->size; memf->evicted = false; } std::list::iterator it_del = it; ++it; submission_queue_rm(it_del); } else if( it->action==SubmissionAction_Evict && it->transid==basetrans ) { SMemFile* memf; { std::scoped_lock memfile_lock(memfiles_mutex); memf = memfiles.get(std::make_pair(it->transid, it->key)); } bool nosubmit_untouched = nosubmit_untouched_items.find(it->key) != nosubmit_untouched_items.end(); Server->Log("Removed basetrans eviction (" + convert(it->transid) + "," + hex(it->key) + ", compressed=" + convert(it->compressed) + ", nosubmit_untouched="+convert(nosubmit_untouched)+")"); if (nosubmit_untouched) { wait_for_del_file(transpath2() + cachefs->fileSep() + hexpath(it->key) + (it->compressed ? ".comp" : "")); //During eviction the dirty in basetrans but untouched in current trans item got deleted in the current trans //We need to copy it back such that it gets submitted with the current trans bool ok = false; if (memf == nullptr) { ok = sync_link(cachefs, basepath2() + cachefs->fileSep() + hexpath(it->key) + (it->compressed ? ".comp" : ""), transpath2() + cachefs->fileSep() + hexpath(it->key) + (it->compressed ? ".comp" : "")); if (!ok) { ok = cachefs->copyFile(basepath2() + cachefs->fileSep() + hexpath(it->key) + (it->compressed ? ".comp" : ""), transpath2() + cachefs->fileSep() + hexpath(it->key) + (it->compressed ? ".comp" : "")); } } else { if (memf->key != it->key) { Server->Log("Memfile key wrong. Expected " + hexpath(it->key) + " got " + hexpath(memf->key), LL_ERROR); abort(); } std::unique_ptr fdst(cachefs->openFile(transpath2() + cachefs->fileSep() + hexpath(it->key) + (it->compressed ? ".comp" : ""), MODE_WRITE)); if (fdst.get() != nullptr) { ok = copy_file(memf->file.get(), fdst.get()); } } if (ok) { submission_items.erase(submission_items.find(std::make_pair(it->transid, it->key))); int64 fsize = it->size; add_cachesize(fsize); if (it->compressed) { comp_bytes += fsize; cache_put_back(compressed_items, it->key, cache_val_nc(true), cache_lock); } else { cache_put_back(lru_cache, it->key, cache_val_nc(true), cache_lock); } if (memf != nullptr) { assert(submitted_memfiles > 0); --submitted_memfiles; submitted_memfile_size -= memf->size; memf->evicted = false; } addDirtyItem(transid, it->key); add_dirty_bytes(transid, it->key, fsize); std::list::iterator it_del = it; ++it; submission_queue_rm(it_del); } else { std::string syserr = os_last_error_str(); Server->Log("Cannot synclink base item "+ basepath2() + cachefs->fileSep() + hexpath(it->key) + (it->compressed ? ".comp" : "")+ " to " + transpath2() + cachefs->fileSep() + hexpath(it->key) + (it->compressed ? ".comp" : "") +" in current trans. "+ syserr, LL_ERROR); addSystemEvent("cache_err_fatal", "Cannot synclink base item", "Cannot synclink base item " + basepath2() + cachefs->fileSep() + hexpath(it->key) + (it->compressed ? ".comp" : "") + " to " + transpath2() + cachefs->fileSep() + hexpath(it->key) + (it->compressed ? ".comp" : "") + " in current trans. " + syserr, LL_ERROR); abort(); ++it; } } else { if (memf != nullptr) { assert(submitted_memfiles > 0); --submitted_memfiles; rm_mem_file(nullptr, it->transid, it->key, true); } //Transaction of eviction will be deleted. Item is dirty in current trans and will be submitted //with current trans submission_items.erase(submission_items.find(std::make_pair(it->transid, it->key))); std::list::iterator it_del = it; ++it; submission_queue_rm(it_del); } } else { ++it; } } check_submission_items(); } void TransactionalKvStore::wait_for_compressions_evictions(std::unique_lock& lock) { curr_submit_compress_evict = false; std::unique_lock lock_submit(submission_mutex); bool retry = true; while (retry) { retry = false; for (std::map, std::list::iterator >::iterator it = submission_items.begin(); it != submission_items.end(); ++it) { if (it->second->action == SubmissionAction_Working_Compress || it->second->action == SubmissionAction_Working_Evict ) { Server->Log(std::string("Waiting for ") + (it->second->action == SubmissionAction_Working_Compress ? "compression" : "eviction" ) + " of (" + convert(it->second->transid) + "," + hex(it->second->key) + ") to finish..."); lock_submit.unlock(); { RetrievalOperationUnlockOnly retrieve_op(lock); Server->wait(50); } lock_submit.lock(); retry = true; break; } } } curr_submit_compress_evict = true; } void TransactionalKvStore::addDirtyItem(int64 transid, std::string key, bool with_stats) { Server->Log("Incr dirty item " + hex(key) + " transid " + convert(transid)); if (!key.empty() && with_stats) { ++total_dirty_ops; } std::scoped_lock lock(dirty_item_mutex); ++num_dirty_items[transid]; DIRTY_ITEM(++dirty_items[transid][key];) } void TransactionalKvStore::removeDirtyItem(int64 transid, std::string key) { Server->Log("Decr dirty item " + hex(key) + " transid " + convert(transid)); std::scoped_lock lock(dirty_item_mutex); --num_dirty_items[transid]; #ifdef DIRTY_ITEM_CHECK std::map::iterator it = dirty_items[transid].find(key); assert(it != dirty_items[transid].end()); assert(it->second > 0); --it->second; if (it->second == 0) { dirty_items[transid].erase(it); } #endif } void TransactionalKvStore::add_dirty_bytes(int64 transid, std::string key, int64 b) { dirty_bytes += b; #ifdef DIRTY_ITEM_CHECK if (key.empty()) return; Server->Log("Add dirty bytes " + hex(key) + " transid " + convert(transid) + " b " + convert(b)); std::scoped_lock lock(dirty_item_mutex); dirty_items_size[transid][key] += b; #endif } void TransactionalKvStore::rm_dirty_bytes(int64 transid, std::string key, int64 b, bool rm, bool change_size) { if(change_size) dirty_bytes -= b; #ifdef DIRTY_ITEM_CHECK if (rm) { if (key.empty()) { assert(b == 0); return; } std::scoped_lock lock(dirty_item_mutex); std::map::iterator it = dirty_items_size[transid].find(key); if (it == dirty_items_size[transid].end()) Server->Log("Dirty item " + hex(key) + " transid " + convert(transid) + " not found", LL_ERROR); assert(it != dirty_items_size[transid].end()); it->second -= b; if (it->second != 0) { Server->Log("Rm_dirty_bytes failed. Has " + convert(it->second) + " bytes " "transid=" + convert(transid) + " key=" + hex(key)+" rmb="+convert(b)+ " change_size="+convert(change_size), LL_ERROR); assert(false); } dirty_items_size[transid].erase(it); } #endif } void TransactionalKvStore::add_cachesize(int64 toadd) { if (toadd > 0) { TRACE_CACHESIZE(Server->Log("Cachesize +" + convert(toadd) + " cachesize=" + PrettyPrintBytes(cachesize), LL_DEBUG)); cachesize += toadd; } } void TransactionalKvStore::sub_cachesize(int64 tosub) { if (tosub > 0) { TRACE_CACHESIZE(Server->Log("Cachesize -" + convert(tosub) + " cachesize=" + PrettyPrintBytes(cachesize), LL_DEBUG)); cachesize -= tosub; } } bool TransactionalKvStore::is_sync_wait_item(const std::string & key) { IScopedLock bundle_lock(submit_bundle_item_mutex.get()); auto it1 = curr_submit_bundle_items->lower_bound(std::make_pair(key, -1)); if (it1 != curr_submit_bundle_items->end() && it1->first == key) { return true; } auto it2 = other_submit_bundle_items->lower_bound(std::make_pair(key, -1)); if (it2 != other_submit_bundle_items->end() && it2->first == key) { return true; } return false; } bool TransactionalKvStore::is_sync_wait_item(const std::string & key, int64 transid) { IScopedLock bundle_lock(submit_bundle_item_mutex.get()); return curr_submit_bundle_items->find(std::make_pair(key, transid)) != curr_submit_bundle_items->end() || other_submit_bundle_items->find(std::make_pair(key, transid)) != other_submit_bundle_items->end(); } void TransactionalKvStore::check_submission_items() { #ifndef NDEBUG size_t max_check = 0; for (auto it = submission_items.begin(); it != submission_items.end(); ++it) { assert(it->first.first == it->second->transid); assert(it->first.second == it->second->key); if (++max_check > 10) { break; } } max_check = 0; for (auto it = submission_items.rbegin(); it != submission_items.rend(); ++it) { assert(it->first.first == it->second->transid); assert(it->first.second == it->second->key); if (++max_check > 10) { break; } } #endif } void TransactionalKvStore::update_transactions() { std::scoped_lock submission_lock(submission_mutex); transactions = cachefs->listFiles(std::string()); } void TransactionalKvStore::drop_cache(IFsFile * fd) { #ifndef _WIN32 //This causes Linux to immediately do blocking writes if file is dirty -- not good //posix_fadvise64(fd->getOsHandle(), 0, 0, POSIX_FADV_DONTNEED); #endif } #ifdef HAS_ASYNC fuse_io_context::io_uring_task TransactionalKvStore::drop_cache_async(fuse_io_context& io, IFsFile* fd) { /* io_uring_sqe* sqe = io.get_sqe(); io_uring_prep_fadvise(sqe, get_file_fd(fd->getOsHandle()), 0, 0, POSIX_FADV_DONTNEED); co_return co_await io.complete(sqe);*/ co_return; } #endif void TransactionalKvStore::set_read_random(IFsFile * fd) { #ifndef _WIN32 posix_fadvise64(fd->getOsHandle(), 0, 0, POSIX_FADV_RANDOM); #endif } #ifdef HAS_ASYNC fuse_io_context::io_uring_task TransactionalKvStore::set_read_random_async(fuse_io_context& io, IFsFile* fd) { io_uring_sqe* sqe = io.get_sqe(); io_uring_prep_fadvise(sqe, get_file_fd(fd->getOsHandle()), 0, 0, POSIX_FADV_RANDOM); co_await io.complete(sqe); } #endif bool TransactionalKvStore::add_submitted_item(int64 transid, std::string tkey, std::unique_ptr* fd_cache) { return add_item("trans_" + convert(transid) + cachefs->fileSep() + "dirty.submitted", transid, tkey, fd_cache); } bool TransactionalKvStore::add_evicted_item(int64 transid, std::string tkey) { return add_item("trans_" + convert(transid) + cachefs->fileSep() + "dirty.evicted", transid, tkey, nullptr); } bool TransactionalKvStore::add_item(std::string fn, int64 transid, std::string tkey, std::unique_ptr* fd_cache) { IScopedLock lock(evicted_mutex.get()); IFile* evicted_f; std::unique_ptr evicted_f_uptr; if (fd_cache != nullptr) { if (fd_cache->get() == nullptr) { fd_cache->reset(cachefs->openFile(fn, MODE_RW_CREATE)); } evicted_f = fd_cache->get(); } else { evicted_f_uptr.reset(cachefs->openFile(fn, MODE_RW_CREATE)); evicted_f = evicted_f_uptr.get(); } if (evicted_f != nullptr) { CWData wdata; wdata.addUShort(0); wdata.addString2(tkey); unsigned short msize = static_cast(wdata.getDataSize()); memcpy(wdata.getDataPtr(), &msize, sizeof(msize)); if (evicted_f->Write(evicted_f->Size(), wdata.getDataPtr(), wdata.getDataSize()) == wdata.getDataSize()) { return true; } } return false; } IFsFile * TransactionalKvStore::get_mem_file(const std::string & key, int64 size_hint, bool for_read) { #ifndef NDEBUG { std::scoped_lock memfile_lock(memfiles_mutex); assert(memfiles.get(std::make_pair(transid, key), false) == nullptr); } #endif if (size_hint <= 0) { return nullptr; } if (memfile_size + size_hint >= max_memfile_size) { return nullptr; } if (for_read && disable_read_memfiles) { return nullptr; } if (!for_read && disable_write_memfiles) { return nullptr; } if ( (for_read && memfile_size > (max_memfile_size * 3) / 4)) { return nullptr; } if (!only_memfiles && has_memfile_stat(key)) { return nullptr; } add_memfile_stat(key); IMemFile* ret; #ifdef HAS_LINUX_MEMORY_FILE ret = new LinuxMemoryFile(key, memcache_path); #else ret = Server->openMemoryFile(key, false); #endif memfile_size += size_hint; ret->Resize(size_hint); { std::scoped_lock memfile_lock(memfiles_mutex); memfiles.put(std::make_pair(transid, key), SMemFile(ret, key, size_hint)); } assert(num_mem_files.find(transid) == num_mem_files.end() || num_mem_files[transid] > 0); ++num_mem_files[transid]; return ret; } bool TransactionalKvStore::has_memfile_stat(const std::string & key) { std::string md5 = Server->GenerateBinaryMD5(key); int64 val; memcpy(&val, md5.data(), sizeof(val)); if (val < 0) val *= -1; for (auto& it : memfile_stat_bitmaps) { if (it.second->get(val % it.second->size())) { return true; } } return false; } void TransactionalKvStore::add_memfile_stat(const std::string & key) { if (only_memfiles) return; if (memfile_stat_bitmaps.empty() || (Server->getTimeMS() - (memfile_stat_bitmaps.end() - 1)->first) > 6 * 60 * 60 * 1000) { memfile_stat_bitmaps.push_back(std::make_pair(Server->getTimeMS(), std::unique_ptr(new Bitmap((5 * 1024 * 1024) * 8, false)))); while (memfile_stat_bitmaps.size() > 8) { memfile_stat_bitmaps.erase(memfile_stat_bitmaps.begin()); } } std::string md5 = Server->GenerateBinaryMD5(key); int64 val; memcpy(&val, md5.data(), sizeof(val)); if (val < 0) val *= -1; Bitmap* bm = (memfile_stat_bitmaps.end() - 1)->second.get(); bm->set(val % bm->size(), true); } bool TransactionalKvStore::rm_mem_file(fuse_io_context* io, int64 transid, const std::string & key, bool rm_submitted) { bool is_last = false; std::scoped_lock memfile_lock(memfiles_mutex); SMemFile* f = memfiles.get(std::make_pair(transid, key), false); if (f != nullptr) { if (f->key != key) { Server->Log("Memfile key wrong. Expected " + hexpath(key) + " got " + hexpath(f->key), LL_ERROR); abort(); } if (rm_submitted) { submitted_memfile_size -= f->size; } assert(num_mem_files[transid] > 0); auto it = num_mem_files.find(transid); --it->second; if (it->second == 0) { num_mem_files.erase(it); is_last = true; } if (f->file.use_count()<=1) { memfile_size -= f->size; if (io != nullptr) { #ifdef HAS_ASYNC cachefs->closeAsync(*io, f->file.get()); #else assert(false); #endif } else { memfd_del_thread.del(std::move(f->file)); } } memfiles.del(std::make_pair(transid, key)); } return is_last; } void TransactionalKvStore::rm_submission_item(std::map, std::list::iterator>::iterator it) { SMemFile* memf; { std::scoped_lock memfile_lock(memfiles_mutex); memf = memfiles.get(std::make_pair(it->first.first, it->first.second), false); } if (memf != nullptr) { if (memf->key != it->first.second) { Server->Log("Memfile key wrong. Expected " + hexpath(it->first.second) + " got " + hexpath(memf->key), LL_ERROR); abort(); } assert(submitted_memfiles > 0); --submitted_memfiles; submitted_memfile_size -= memf->size; memf->evicted = false; } submission_queue_rm(it->second); submission_items.erase(it); } bool TransactionalKvStore::cow_mem_file(SMemFile * memf, bool with_old_file) { assert(memf->cow); if (memf->file.use_count() == 1) { memf->cow = false; return true; } if (memfile_size + memf->size > max_memfile_size + max_memfile_size / 2) { return false; } #ifdef HAS_LINUX_MEMORY_FILE IMemFile* nf = new LinuxMemoryFile(memf->key, memcache_path); #else IMemFile* nf = Server->openMemoryFile(memf->key, false); #endif int64 nf_size = memf->file->Size(); nf->Resize(nf_size); nf->unprotect_mem(); #ifdef HAS_LINUX_MEMORY_FILE off_t offset = 0; ssize_t rc = sendfile(nf->getOsHandle(), memf->file->getOsHandle(), &offset, nf_size); if (rc != nf_size) { nf->protect_mem(); assert(false); Server->destroy(nf); return false; } #else if (memf->file->Read(0, nf->getDataPtr(), static_cast<_u32>(nf_size)) != nf_size) { nf->protect_mem(); assert(false); Server->destroy(nf); return false; } #endif nf->protect_mem(); memfile_size += memf->size; if (with_old_file) { //mem file could be open read-only... so keep the old one around memf->old_file = memf->file; } memf->file = std::shared_ptr(nf); memf->cow = false; return true; } #ifdef HAS_ASYNC fuse_io_context::io_uring_task TransactionalKvStore::cow_mem_file_async(fuse_io_context& io, SMemFile* memf, bool with_old_file) { assert(memf->cow); if (memf->file.use_count() == 1) { memf->cow = false; co_return true; } if (memfile_size + memf->size > max_memfile_size + max_memfile_size / 2) { co_return false; } IMemFile* nf = co_await io.run_in_threadpool([memf, this]() { #ifdef HAS_LINUX_MEMORY_FILE //TODO: Open via uring std::unique_ptr nf = std::make_unique(memf->key, this->memcache_path); #else std::unique_ptr nf(Server->openMemoryFile(memf->key, false)); #endif int64 nf_size = memf->file->Size(); nf->Resize(nf_size); nf->unprotect_mem(); #ifdef HAS_LINUX_MEMORY_FILE off_t offset = 0; //TODO: Use uring splice here ssize_t rc = sendfile(nf->getOsHandle(), memf->file->getOsHandle(), &offset, nf_size); nf->protect_mem(); if (rc < 0) { assert(false); nf.reset(); } #else _u32 rc = memf->file->Read(0, nf->getDataPtr(), static_cast<_u32>(nf_size)); nf->protect_mem(); #endif if (rc < nf_size) { assert(false); nf.reset(); } return nf.release(); }, "cow memf"); if (nf == nullptr) { assert(false); co_return false; } memfile_size += memf->size; if (with_old_file) { //mem file could be open read-only... so keep the old one around memf->old_file = memf->file; } memf->file = std::shared_ptr(nf); memf->cow = false; co_return true; } #endif void TransactionalKvStore::remove_missing(const std::string & key) { auto missing_it = missing_items.find(key); if (missing_it != missing_items.end()) { cachefs->deleteFile("missing_" + hex(key)); missing_items.erase(missing_it); } } int64 TransactionalKvStore::get_compsize(const std::string & key, int64 transid) { { std::scoped_lock lock(memfiles_mutex); SMemFile* f = memfiles.get(std::make_pair(transid, key), false); if (f != nullptr && f->compsize>=0) { return f->compsize; } } return get_compsize_file(cachefs, keypath2(key, transid)); } void TransactionalKvStore::only_memfiles_throttle(const std::string& key, std::unique_lock& lock) { if (!only_memfiles) return; bool cache_check = false; while (memfile_size > (max_memfile_size * 3) / 4) { if (!cache_check && cache_get(lru_cache, key, lock, false)!=nullptr) { return; } else { cache_check = true; } ; lock.unlock(); Server->wait(100); lock.lock(); } } #ifdef HAS_ASYNC fuse_io_context::io_uring_task TransactionalKvStore::only_memfiles_throttle_async(fuse_io_context& io, const std::string key) { if (!only_memfiles) co_return; bool cache_check = false; std::unique_lock lock(cache_mutex); while (memfile_size > (max_memfile_size * 3) / 4) { if (!cache_check && cache_get(lru_cache, key, lock, false) != nullptr) { co_return; } else { cache_check = true; } lock.unlock(); co_await io.sleep_ms(100); lock.lock(); } } #endif std::string TransactionalKvStore::readCacheFile(const std::string& fn) { std::unique_ptr f(cachefs->openFile(fn, MODE_READ)); if (!f) return std::string(); char buf[4096]; _u32 r; std::string ret; while ((r = f->Read(buf, sizeof(buf))) > 0) { ret.append(buf, r); if (r < sizeof(buf)) break; } return ret; } bool TransactionalKvStore::cacheFileExists(const std::string& fn) { std::unique_ptr f(cachefs->openFile(fn, MODE_READ)); return f.get() != nullptr; } bool TransactionalKvStore::writeToCacheFile(const std::string& str, const std::string& fn) { std::unique_ptr f(cachefs->openFile(fn, MODE_WRITE)); if (!f) return false; return f->Write(str) == str.size(); } TransactionalKvStore::SCacheVal TransactionalKvStore::cache_val(const std::string & key, bool dirty) { unsigned int chances = 0; if (num_second_chances_cb != nullptr) chances = num_second_chances_cb->get_num_second_chances(key); TransactionalKvStore::SCacheVal ret; ret.dirty = dirty; if (chances >= 128) ret.chances = 127; else ret.chances = chances; return ret; } TransactionalKvStore::SCacheVal TransactionalKvStore::cache_val_nc(bool dirty) { TransactionalKvStore::SCacheVal ret; ret.dirty = dirty; return ret; } std::list::iterator TransactionalKvStore::submission_queue_add(SSubmissionItem & item, bool memfile) { submission_queue.push_back(item); auto it=--submission_queue.end(); if (memfile && submitted_memfiles==0) { submission_queue_memfile_first = it; } return it; } std::list::iterator TransactionalKvStore::submission_queue_insert(SSubmissionItem & item, bool memfile, std::list::iterator it) { it = submission_queue.insert(it, item); if (memfile) { submission_queue_memfile_first = submission_queue.end(); } return it; } void TransactionalKvStore::submission_queue_rm(std::list::iterator it) { if (submission_queue_memfile_first != submission_queue.end() && it == submission_queue_memfile_first) { submission_queue_memfile_first = submission_queue.end(); } submission_queue.erase(it); } int64 TransactionalKvStore::cache_free_space() { int64 free_space = cachefs->freeSpace(); if (free_space > critical_free_size && metadata_cache_free!=-1 && metadata_cache_free < min_metadata_cache_free) { free_space = critical_free_size - 500 * 1024 * 1024; } return free_space; } void TransactionalKvStore::RegularSubmitBundleThread::regular_submit_bundle(std::unique_lock& cache_lock) { if (!kv_store->submit_bundle.empty() && (Server->getTimeMS() - kv_store->submit_bundle_starttime > 5 * 60 * 1000 || kv_store->submit_bundle.size()>20000)) { std::unique_ptr submitted_fd_cache; int64 submitted_fd_cache_transid = 0; { IScopedLock bundle_lock(kv_store->submit_bundle_item_mutex.get()); std::swap(kv_store->curr_submit_bundle_items, kv_store->other_submit_bundle_items); } size_t submit_bundle_n = kv_store->submit_bundle.size(); cache_lock.unlock(); if (!kv_store->online_kv_store->sync()) { Server->Log("Kv store sync failed. Not doing regular submit bundle...", LL_ERROR); submit_bundle_n = 0; IScopedLock bundle_lock(kv_store->submit_bundle_item_mutex.get()); if (!kv_store->curr_submit_bundle_items->empty()) { kv_store->other_submit_bundle_items->insert(kv_store->curr_submit_bundle_items->begin(), kv_store->curr_submit_bundle_items->end()); kv_store->curr_submit_bundle_items->clear(); } std::swap(kv_store->curr_submit_bundle_items, kv_store->other_submit_bundle_items); //TODO: Resubmit all submit bundle items } cache_lock.lock(); bool run_del_path = false; for (size_t i = 0; i < submit_bundle_n; ++i) { SSubmissionItem* it = &kv_store->submit_bundle[i].first; std::string key = it->key; int64 transid = it->transid; { cache_lock.unlock(); if (run_del_path) { run_del_path = false; kv_store->run_del_file_queue(); } if (kv_store->with_submitted_files) { if (submitted_fd_cache_transid != 0 && submitted_fd_cache_transid != transid) { submitted_fd_cache.reset(); } kv_store->add_submitted_item(transid, key, &submitted_fd_cache); submitted_fd_cache_transid = transid; } cache_lock.lock(); } it = &kv_store->submit_bundle[i].first; if (kv_store->submit_bundle[i].second) { common::lrucache* target_cache = &kv_store->lru_cache; common::lrucache* other_target_cache = &kv_store->compressed_items; if (it->compressed) { target_cache = &kv_store->compressed_items; other_target_cache = &kv_store->lru_cache; } bool compressed = it->compressed; SCacheVal* dirty = cache_get(*target_cache, it->key, cache_lock, false); if (dirty == nullptr) { compressed = !compressed; dirty = cache_get(*other_target_cache, it->key, cache_lock, false); target_cache = other_target_cache; } bool del_item = false; if (dirty != nullptr && !dirty->dirty && kv_store->open_files.find(it->key) == kv_store->open_files.end() && kv_store->in_retrieval.find(it->key) == kv_store->in_retrieval.end()) { bool in_submission = false; { std::scoped_lock lock(kv_store->submission_mutex); std::scoped_lock dirty_lock(kv_store->dirty_item_mutex); for (std::map::iterator itd = kv_store->num_dirty_items.begin(); itd != kv_store->num_dirty_items.end(); ++itd) { if (itd->second>0) { if (kv_store->submission_items.find(std::make_pair(itd->first, it->key)) != kv_store->submission_items.end()) { in_submission = true; } } } } if (!in_submission) { Server->Log("Evicting non-dirty cache item " + kv_store->transpath2() + kv_store->cachefs->fileSep() + kv_store->hexpath(it->key) + (compressed ? ".comp" : "") + " after submission (bundled)", LL_INFO); int64 fsize = -1; if (!compressed) { SFdKey* res = kv_store->fd_cache.get(it->key); if (res != nullptr) { if (kv_store->cachefs->filePath(res->fd) != kv_store->keypath2(it->key, kv_store->transid)) { Server->Log("Fd cache filename not as expected. Got " + kv_store->cachefs->filePath(res->fd) + " Expected " + kv_store->keypath2(it->key, kv_store->transid), LL_ERROR); abort(); } assert(dynamic_cast(res->fd) == nullptr); if(res->fd!=nullptr) fsize = res->fd->Size(); Server->destroy(res->fd); kv_store->fd_cache.del(it->key); } } if (fsize == -1 && compressed) { SMemFile* memfile_nf; { std::scoped_lock memfile_lock(kv_store->memfiles_mutex); memfile_nf = kv_store->memfiles.get(std::make_pair(kv_store->transid, it->key), false); } IFile* fd; if (memfile_nf != nullptr) { if (memfile_nf->key != it->key) { Server->Log("Memfile key wrong. Expected " + kv_store->hexpath(it->key) + " got " + kv_store->hexpath(memfile_nf->key), LL_ERROR); abort(); } fd = memfile_nf->file.get(); } else { fd = kv_store->cachefs->openFile(kv_store->keypath2(it->key, kv_store->transid) + (compressed ? ".comp" : ""), MODE_READ); } if (fd != nullptr) { fsize = fd->Size(); Server->destroy(fd); } } if (fsize == -1) { fsize = it->size; } kv_store->sub_cachesize(fsize); if (compressed) { kv_store->comp_bytes -= fsize; } kv_store->delete_item(nullptr, it->key, compressed, cache_lock, 0, 0, TransactionalKvStore::DeleteImm::None, 0, false, it->transid); run_del_path = true; cache_del(*target_cache, it->key, cache_lock); } else { del_item = true; } } else { del_item = true; } if (del_item) { std::string item_path = kv_store->keypath2(it->key, it->transid); if (it->compressed) { item_path += ".comp"; } IScopedLock del_file_lock(kv_store->del_file_mutex.get()); kv_store->del_file_queue.insert(item_path); run_del_path = true; } } } if (submit_bundle_n > 0) { IScopedLock bundle_lock(kv_store->submit_bundle_item_mutex.get()); kv_store->other_submit_bundle_items->clear(); } kv_store->submit_bundle.erase(kv_store->submit_bundle.begin(), kv_store->submit_bundle.begin() + submit_bundle_n); if (kv_store->submit_bundle.empty()) { kv_store->submit_bundle_starttime = 0; } else { kv_store->submit_bundle_starttime = Server->getTimeMS(); } if (run_del_path) { cache_lock.unlock(); kv_store->run_del_file_queue(); cache_lock.lock(); } } } void TransactionalKvStore::RegularSubmitBundleThread::operator()() { std::unique_lock lock(kv_store->cache_mutex); while (!do_quit) { cond.wait_for(lock, 1000ms); if (do_quit) break; regular_submit_bundle(lock); } } void TransactionalKvStore::RegularSubmitBundleThread::quit() { std::scoped_lock lock(kv_store->cache_mutex); do_quit = true; cond.notify_all(); } void TransactionalKvStore::ThrottleThread::operator()() { std::unique_lock lock(kv_store->cache_mutex); while (!do_quit) { cond.wait_for(lock, 100ms); if (do_quit) break; lock.unlock(); int64 free_space = kv_store->cache_free_space(); lock.lock(); if (kv_store->unthrottled_gets>0) { if (kv_store->remaining_gets == std::string::npos) { if (kv_store->unthrottled_gets_avg == 0) { kv_store->unthrottled_gets_avg = static_cast(kv_store->unthrottled_gets); } else { kv_store->unthrottled_gets_avg = 0.9*kv_store->unthrottled_gets_avg + 0.1*static_cast(kv_store->unthrottled_gets); } } kv_store->unthrottled_gets = 0; } if (kv_store->cachesizemin_cachesize) { kv_store->has_new_remaining_gets = true; kv_store->new_remaining_gets = std::string::npos; continue; } if (!kv_store->curr_submit_compress_evict) { kv_store->has_new_remaining_gets = true; kv_store->new_remaining_gets = std::string::npos; continue; } if (free_space >= kv_store->throttle_free_size && kv_store->cachesizemax_cachesize) { kv_store->has_new_remaining_gets = true; kv_store->new_remaining_gets = std::string::npos; } if (!kv_store->allow_evict) { if (free_space < kv_store->critical_free_size) { Server->Log("Critical free space. Not allowed to evict. Throttling... Please free some space.", LL_ERROR); kv_store->has_new_remaining_gets = true; kv_store->new_remaining_gets = 0; } if (kv_store->cachesize > kv_store->max_cachesize) { Server->Log("Critical cache size. Not allowed to evict. Throttling... Please free some space.", LL_ERROR); kv_store->has_new_remaining_gets = true; kv_store->new_remaining_gets = 0; } } int64 max_cachesize_lower = (std::max)(kv_store->min_cachesize, kv_store->max_cachesize - max_cachesize_throttle_size); if (kv_store->allow_evict && free_space < kv_store->throttle_free_size && kv_store->curr_submit_compress_evict) { if (kv_store->remaining_gets == std::string::npos) { Server->Log("Critical free space (" + PrettyPrintBytes(free_space) + "). Beginning throttling...", LL_WARNING); } double throttle_pc = 0; if (free_space > kv_store->critical_free_size) { throttle_pc = 1.0 - static_cast(kv_store->throttle_free_size - free_space) / (kv_store->throttle_free_size - kv_store->critical_free_size); } kv_store->has_new_remaining_gets = true; kv_store->new_remaining_gets = static_cast(kv_store->unthrottled_gets_avg*throttle_pc + 0.5); Server->Log("Throttling to " + convert(kv_store->remaining_gets) + " gets/s from avg " + convert(kv_store->unthrottled_gets_avg) + " gets/s"); } else if (kv_store->allow_evict && kv_store->cachesize > max_cachesize_lower && kv_store->curr_submit_compress_evict) { if (kv_store->remaining_gets == std::string::npos) { Server->Log("Critical cache size (" + PrettyPrintBytes(kv_store->cachesize) + " max "+PrettyPrintBytes(max_cachesize_lower)+"). Beginning throttling...", LL_WARNING); } double throttle_pc = 0; int64 cachesize_over = kv_store->cachesize - max_cachesize_lower; if (cachesize_over (cachesize_over) / max_cachesize_throttle_size; } kv_store->has_new_remaining_gets = true; kv_store->new_remaining_gets = static_cast(kv_store->unthrottled_gets_avg*throttle_pc + 0.5); Server->Log("Throttling to " + convert(kv_store->remaining_gets) + " gets/s from avg " + convert(kv_store->unthrottled_gets_avg) + " gets/s b/c max cachesize"); } } } void TransactionalKvStore::ThrottleThread::quit() { std::scoped_lock lock(kv_store->cache_mutex); do_quit = true; cond.notify_all(); } int64 TransactionalKvStore::get_transid() { std::scoped_lock lock(cache_mutex); return transid; } int64 TransactionalKvStore::get_basetransid() { std::scoped_lock lock(cache_mutex); return basetrans; } void TransactionalKvStore::set_max_cachesize(int64 ns) { max_cachesize = ns; } int64 TransactionalKvStore::cache_total_space() { return cachefs->totalSpace(); } void TransactionalKvStore::set_disable_read_memfiles(bool b) { std::scoped_lock lock(cache_mutex); disable_read_memfiles = b; } void TransactionalKvStore::set_disable_write_memfiles(bool b) { std::scoped_lock lock(cache_mutex); disable_write_memfiles = b; } namespace { relaxed_atomic last_metadata_balance_enospc(0); relaxed_atomic last_data_balance_enospc(0); relaxed_atomic last_metadata_balance_enospc_cnt(0); relaxed_atomic last_data_balance_enospc_cnt(0); class RebalanceThread : public IThread { bool metadata; IBackupFileSystem* cachefs; public: RebalanceThread(IBackupFileSystem* cachefs, bool metadata) : cachefs(cachefs), metadata(metadata) {} void operator()() { if (metadata && cachefs->forceAllocMetadata()) { delete this; return; } for (int i = 1; i < 50; ++i) { bool enospc; size_t relocated; if (!cachefs->balance(i, 1, metadata, enospc, relocated)) { if (enospc) { if (metadata) { last_metadata_balance_enospc = Server->getTimeMS(); ++last_metadata_balance_enospc_cnt; } else { last_data_balance_enospc = Server->getTimeMS(); ++last_data_balance_enospc_cnt; } } break; } if (relocated==0) continue; if (metadata) { last_metadata_balance_enospc = 0; last_metadata_balance_enospc_cnt = 0; } else { last_data_balance_enospc = 0; last_data_balance_enospc_cnt = 0; } if (metadata && !cachefs->forceAllocMetadata()) { --i; continue; } else { break; } } delete this; } }; } void TransactionalKvStore::MetadataUpdateThread::operator()() { task_set_less_throttle(); bool throttle_warn = false; THREADPOOL_TICKET rebalance_tt = ILLEGAL_THREADPOOL_TICKET; std::unique_lock lock(kv_store->cache_mutex); while (!do_quit) { cond.wait_for(lock, 60s); if (do_quit) break; lock.unlock(); int64 unallocated_space = kv_store->cachefs->unallocatedSpace(); int64 free_space = kv_store->cachefs->freeMetadataSpace(); if (free_space < 0) { lock.lock(); continue; } Server->Log("Free metadata space: " + PrettyPrintBytes(free_space)); if (free_space < kv_store->min_metadata_cache_free) { if (rebalance_tt == ILLEGAL_THREADPOOL_TICKET || Server->getThreadPool()->waitFor(rebalance_tt, 0)) { rebalance_tt = Server->getThreadPool()->execute(new RebalanceThread(kv_store->cachefs, true), "ch dat rebalance"); } if (!throttle_warn) { Server->Log(PrettyPrintBytes(free_space) + " free metdata space on " + kv_store->cachefs->getName() + ". Throttling...", LL_WARNING); throttle_warn = true; } } else { if (free_space < kv_store->min_metadata_cache_free + (std::min)(kv_store->min_metadata_cache_free / 4, 500LL * 1024 * 1024) && (rebalance_tt == ILLEGAL_THREADPOOL_TICKET || Server->getThreadPool()->waitFor(rebalance_tt, 0)) && Server->getTimeMS()-last_metadata_balance_enospc > last_metadata_balance_enospc_cnt * 60*60*1000) { rebalance_tt = Server->getThreadPool()->execute(new RebalanceThread(kv_store->cachefs, true), "chp dat rebalance"); } else if(unallocated_space>=0 && free_space-unallocated_space >(std::max)(kv_store->min_metadata_cache_free*4, 1LL * 1024 * 1024 * 1024) && Server->getTimeMS() - last_data_balance_enospc > last_data_balance_enospc_cnt * 60 * 60 * 1000) { rebalance_tt = Server->getThreadPool()->execute(new RebalanceThread(kv_store->cachefs, false), "chp met rebalance"); } throttle_warn = false; } kv_store->metadata_cache_free = free_space; lock.lock(); } task_unset_less_throttle(); } void TransactionalKvStore::MetadataUpdateThread::quit() { std::scoped_lock lock(kv_store->cache_mutex); do_quit = true; cond.notify_all(); } void TransactionalKvStore::MemfdDelThread::operator()() { std::unique_lock lock(mutex); while (!do_quit) { cond.wait(lock, [&]() {return do_quit || !del_fds.empty(); }); if (do_quit) break; std::shared_ptr fd = std::move(del_fds.back()); del_fds.pop_back(); lock.unlock(); fd.reset(); lock.lock(); } } bool TransactionalKvStore::set_cache_file_compression(const std::string& key, const std::string& fpath) { if (meta_cache_comp != CompressionNone && num_second_chances_cb != nullptr && num_second_chances_cb->is_metadata(key)) { return cachefs->setXAttr(fpath, "btrfs.compression", CompressionMethodToBtrfsString(meta_cache_comp)); } else if (cache_comp != CompressionNone) { return cachefs->setXAttr(fpath, "btrfs.compression", CompressionMethodToBtrfsString(cache_comp)); } return true; }