/*************************************************************************
*    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 <http://www.gnu.org/licenses/>.
**************************************************************************/

#ifndef _WIN32
#include <sys/mount.h>
#endif
#include "TransactionalKvStore.h"
#include "../urbackupcommon/os_functions.h"
#include "../stringtools.h"
#include "../Interface/File.h"
#include <assert.h>
#include "../Interface/Server.h"
#include "../Interface/ThreadPool.h"
#include "../Interface/Thread.h"
#include <stdexcept>
#include <math.h>
#include <memory.h>
#include <limits.h>
#include <chrono>
#include "ICompressEncrypt.h"
#include "CloudFile.h"
#include "../common/data.h"
#ifdef HAS_LINUX_MEMORY_FILE
#include "LinuxMemFile.h"
#endif
#ifndef _WIN32
#include <sys/types.h>
#include <sys/xattr.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/file.h>
#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<int>(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<unsigned int>(1000.*pow(2., static_cast<double>(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<IFile> 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<IFile> 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<char*>(&compsize);
		std::string val(buf, buf+sizeof(compsize));
		return cachefs->setXAttr(path, "user.cs", val);
	}

	TransactionalKvStore::SCacheVal* cache_get(common::lrucache<std::string, TransactionalKvStore::SCacheVal>& lru_cache,
		const std::string& key, std::unique_lock<cache_mutex_t>& lock, bool bring_front = true)
	{
		assert(lock.owns_lock());
		return lru_cache.get(key, bring_front);
	}

	void cache_put(common::lrucache<std::string, TransactionalKvStore::SCacheVal>& lru_cache,
		const std::string& key, TransactionalKvStore::SCacheVal val, std::unique_lock<cache_mutex_t>& lock)
	{
		assert(lock.owns_lock());
		lru_cache.put(key, val);
	}

	void cache_put_back(common::lrucache<std::string, TransactionalKvStore::SCacheVal>& lru_cache,
		const std::string& key, TransactionalKvStore::SCacheVal val, std::unique_lock<cache_mutex_t>& lock)
	{
		assert(lock.owns_lock());
		lru_cache.put_back(key, val);
	}

	void cache_del(common::lrucache<std::string, TransactionalKvStore::SCacheVal>& lru_cache,
		const std::string& key, std::unique_lock<cache_mutex_t>& lock)
	{
		assert(lock.owns_lock());
		lru_cache.del(key);
	}

	std::pair<std::string, TransactionalKvStore::SCacheVal> cache_eviction_candidate(common::lrucache<std::string, TransactionalKvStore::SCacheVal>& lru_cache,
		std::unique_lock<cache_mutex_t>& lock, size_t skip = 0)
	{
		assert(lock.owns_lock());
		return lru_cache.eviction_candidate(skip);
	}

	std::list<std::pair<std::string const *, TransactionalKvStore::SCacheVal> >::iterator cache_eviction_iterator_start(common::lrucache<std::string, TransactionalKvStore::SCacheVal>& lru_cache,
		std::unique_lock<cache_mutex_t>& lock)
	{
		assert(lock.owns_lock());
		return lru_cache.eviction_iterator_start();
	}

	std::list<std::pair<std::string const *, TransactionalKvStore::SCacheVal> >::iterator cache_eviction_iterator_finish(common::lrucache<std::string, TransactionalKvStore::SCacheVal>& lru_cache,
		std::unique_lock<cache_mutex_t>& 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<size_t>(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 (; start<end; ++start)
			{
				if (get(start) != v)
				{
					set(start, v);
					++set_bits;
				}
			}
			return set_bits;
		}

		char getb(int64 i) const
		{
			size_t bitmap_byte = (size_t)(i / 8);
			return data[bitmap_byte];
		}

		bool get(int64 i) const
		{
			size_t bitmap_byte = (size_t)(i / 8);
			size_t bitmap_bit = i % 8;

			unsigned char b = data[bitmap_byte];

			bool has_bit = ((b & (1 << (7 - bitmap_bit)))>0);

			return has_bit;
		}

		size_t count_bits()
		{
			size_t set_count = 0;
			for (int64 i = 0; i<total_size;)
			{
				if (i % 8 == 0
					&& getb(i) == 0)
				{
					i += 8;
					continue;
				}

				if (get(i))
				{
					++set_count;
				}

				++i;
			}

			return set_count;
		}

		bool get_range(int64 start, int64 end) const
		{
			for (; start<end; ++start)
			{
				if (get(start))
				{
					return true;
				}
			}
			return false;
		}

		int64 size()
		{
			return total_size;
		}

		size_t meminfo()
		{
			return bitmap_size;
		}


	private:
		size_t bitmap_size;
		int64 total_size;
		std::vector<unsigned char> 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<SSubmissionItem>::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<IFsFile> 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<cache_mutex_t>& 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<cache_mutex_t>& lock;
	TransactionalKvStore& kv_store;
	const std::string& key;
};

class TransactionalKvStore::RetrievalOperationUnlockOnly
{
public:

	explicit RetrievalOperationUnlockOnly(std::unique_lock<cache_mutex_t>& lock)
		: lock(lock)
	{
		lock.unlock();
	}

	~RetrievalOperationUnlockOnly()
	{
		stop();
	}

	void stop() {
		if (lock.owns_lock())
			return;
		lock.lock();
	}

private:
	std::unique_lock<cache_mutex_t>& 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<SFileExtent> 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<IMemFile*>(it.second.fd)==nullptr);
				Server->destroy(it.second.fd);
			}
		}
	}

	while (!fd_cache.empty())
	{
		std::pair<std::string, SFdKey> candidate = fd_cache.evict_one();
		assert(dynamic_cast<IMemFile*>(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<IFsFile*> 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<std::string, SFdKey>::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<IFsFile*, ReadOnlyFileWrapper*>::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<IMemFile*>(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<std::string>::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<IMemFile*>(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<IMemFile*>(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<IFsFile*> cache_free_list;
	if (cache_fd && it_open_file == open_files.end()
		&& fd_cache_size > 0)
	{
		assert(dynamic_cast<IMemFile*>(nf) == nullptr);
		SFdKey fd_cache_val(nf, -1, read_only);
		fd_cache.put(key, fd_cache_val);

		int retries = static_cast<int>(fd_cache_size - 1);
		while (fd_cache.size() > fd_cache_size)
		{
			std::pair<std::string, SFdKey> 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<IFsFile*, ReadOnlyFileWrapper*>::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<IMemFile*>(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<std::string, SFdKey>::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<IFsFile*, ReadOnlyFileWrapper*>::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<IMemFile*>(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<std::string>::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<IMemFile*>(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<IMemFile*>(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<IFsFile*> cache_free_list;
	if(cache_fd && it_open_file==open_files.end()
		&& fd_cache_size>0)
	{
		assert(dynamic_cast<IMemFile*>(nf) == nullptr);
		SFdKey fd_cache_val(nf, -1, read_only);
		fd_cache.put(key, fd_cache_val);

		int retries=static_cast<int>(fd_cache_size-1);
		while(fd_cache.size()>fd_cache_size)
		{
			std::pair<std::string, SFdKey> 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<IFsFile*, ReadOnlyFileWrapper*>::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<IMemFile*>(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_mutex_t>& 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<std::string>::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::pair<int64, std::string>, std::list<SSubmissionItem>::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<int64, std::set<std::string> >::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<IFsFile> 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<int64, size_t>::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<std::string>::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<IFsFile> 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<IMemFile*>(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<IMemFile*>(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<bool> 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<IMemFile*>(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<const std::string*, SCacheVal>& item : lru_cache.get_list())
		{
			if (!item.second.dirty)
			{
				int64 fsize = 0;
				std::string key = *item.first;
				std::map<std::string, SFdKey>::iterator it_open_file = open_files.find(key);

				SFdKey* res;
				if (it_open_file != open_files.end())
				{
					if (dynamic_cast<IMemFile*>(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<IFile> nf(cachefs->openFile(keypath2(key, transid), OPEN_DIRECT));

						if (nf != nullptr)
						{
							fsize = nf->Size();
						}
					}
				}

				item.second.dirty = true;
				std::set<std::string>::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<size_t>(10), static_cast<size_t>(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<IFsFile> 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(&regular_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<size_t>(os_get_num_cpus()*cpu_multiplier+0.5f);

	if(num_cpus>10000 || num_cpus<1)
	{
		num_cpus = 1;
	}

	for(size_t i=0;i<num_cpus;++i)
	{
		threads.push_back(Server->getThreadPool()->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<const unsigned char*>(key.c_str()), key.size());
}

std::string TransactionalKvStore::hexpath( const std::string& key )
{
	std::string hexkey = bytesToHex(reinterpret_cast<const unsigned char*>(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<std::string, SCacheVal>* evict_target_cache = &lru_cache;

		if (comp_bytes>0)
		{
			evict_target_cache = &compressed_items;
		}

		std::list<std::pair<std::string const *, SCacheVal> >::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_free_size
			&& cachesize>=min_cachesize) 
			|| free_space<critical_free_size
			|| free_space<throttle_free_size
			|| curr_cachesize>max_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::pair<int64, std::string>, std::list<SSubmissionItem>::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<std::list<std::pair<std::string const *, SCacheVal> >::iterator> cache_move_front;
			bool used_chance;
			if(n_evicting<evict_queue_depth)
			{
				Server->Log("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_pc<comp_percent
			&& cachesize>comp_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::pair<int64, std::string>, std::list<SSubmissionItem>::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::pair<int64, std::string>, std::list<SSubmissionItem>::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<std::pair<std::string const *, SCacheVal> >::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_mutex_t>& cache_lock, int64 transaction_id, int64 attibute_to_trans_id)
{
	std::unique_ptr<IFsFile> 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<std::string, SFdKey>::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<IMemFile*>(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<IFsFile*, ReadOnlyFileWrapper*>::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<IFsFile> 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<IMemFile*>(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<void> TransactionalKvStore::release_async(fuse_io_context& io, const std::string& key)
{
	std::unique_lock lock(cache_mutex);

	std::map<std::string, SFdKey>::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<IMemFile*>(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<IFsFile*, ReadOnlyFileWrapper*>::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<IFsFile> 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<IMemFile*>(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_mutex_t>& cache_lock, const std::string& tpath, bool verify)
{
	std::vector<SFile> files = cachefs->listFiles(tpath);
	for(size_t i=0;i<files.size();++i)
	{
		if(files[i].isdir)
		{
			read_keys(cache_lock, tpath+ cachefs->fileSep() +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<SFile> 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<int64, std::set<std::string> >::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_mutex_t>& cache_lock, bool break_on_skip, bool only_non_dirty,
	std::list<std::pair<std::string const *, SCacheVal> >::iterator& evict_it,
	common::lrucache<std::string, SCacheVal>& target_cache, bool use_chances, int64& freed_space,
	bool& run_del_items, std::vector<std::list<std::pair<std::string const *, SCacheVal> >::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<int64, size_t>::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<IMemFile*>(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<std::string, SCacheVal>& target_cache, std::vector<std::list<std::pair<std::string const*, SCacheVal>>::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<std::string, SCacheVal>& target_cache,
	std::list<std::pair<std::string const *, SCacheVal> >::iterator* evict_it,
	std::unique_lock<cache_mutex_t>& cache_lock, const std::string& from, int64& freed_space)
{
	bool compressed = (&target_cache == &compressed_items);

	std::set<std::string>::iterator it_untouched = nosubmit_untouched_items.find(key);

	if (dirty)
	{
		std::unique_lock dirty_lock(dirty_item_mutex);

		std::map<int64, std::set<std::string> >::iterator it_di = nosubmit_dirty_items.find(basetrans);

		if (it_di != nosubmit_dirty_items.end())
		{
			std::set<std::string>::iterator it2 = it_di->second.find(key);

			if (it2 != it_di->second.end())
			{
				dirty_lock.unlock();

				std::unique_ptr<IFsFile> 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<IFsFile> 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<IFsFile> 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_mutex_t>& 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_mutex_t>& cache_lock,
	std::list<std::pair<std::string const *, SCacheVal> >::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<IMemFile*>(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<IFsFile> 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<SSubmissionItem>::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<SSubmissionItem>::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<SSubmissionItem>::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->transid<transid)
			{
				lock.unlock();
				online_kv_store->sync();

				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->transid<transid
				&& num_dirty_items[it->transid] == 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<IFsFile> invalid(cachefs->openFile("trans_" + convert(it->transid) + cachefs->fileSep() + "invalid", MODE_READ));
			lock.unlock();

			if (invalid.get() == nullptr)
			{
				std::unique_ptr<IFsFile> 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<IFsFile> 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->transid<curr_transid 
		&& it->action== 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<int64, std::set<std::string> >::iterator it_ld = nosubmit_dirty_items.find(it->transid);
		
		if(it_ld!=nosubmit_dirty_items.end())
		{
			std::set<std::string>::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->transid<transid
					&& num_dirty_items.find(it->transid)== 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<IFsFile> 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<IFsFile> 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<std::string, SCacheVal>* target_cache = &lru_cache;
		common::lrucache<std::string, SCacheVal>* 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<int64, size_t>::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<IMemFile*>(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<SSubmissionItem>::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<float>(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<IFsFile> 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<std::string, SFdKey> evt;
	do 
	{
		evt = fd_cache.evict_one();
		if (evt != std::pair<std::string, SFdKey>())
		{
			assert(dynamic_cast<IMemFile*>(evt.second.fd) == nullptr);
			drop_cache(evt.second.fd);
			Server->destroy(evt.second.fd);
		}

	} while (evt!=std::pair<std::string, SFdKey>());

	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<std::string, size_t> memf_dirty_items;
	std::map<std::string, int64> 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<int64, size_t>::iterator dirty_it = num_dirty_items.find(basetrans);

			if (dirty_it != num_dirty_items.end())
			{
#ifdef DIRTY_ITEM_CHECK
				std::map<int64, std::map<std::string, size_t> >::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<int64, std::map<std::string, int64> >::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;i<it.second;++i)
						removeDirtyItem(new_trans, it.first);
				}
				for (auto it : memf_dirty_items_size)
				{
					rm_dirty_bytes(new_trans, it.first, it.second, true, false);
				}
			}
		}
	}
	else
	{
		std::scoped_lock lock(submission_mutex);
		dirty_evicted_items.clear();
	}

	cleanup(false);

	return true;
}

std::string TransactionalKvStore::keypath2( const std::string& key, int64 transaction_id )
{
	return "trans_" + std::to_string(transaction_id)
		+ cachefs->fileSep() + hexpath(key);
}

bool TransactionalKvStore::read_submitted_evicted_files(const std::string & fn, std::set<std::string>& sub_evicted)
{
	std::unique_ptr<IFile> f(cachefs->openFile(fn, MODE_READ));
	if (f.get() == nullptr)
	{
		return true;
	}

	std::vector<char> msg;
	while (true)
	{
		unsigned short psize;
		bool has_error = false;
		if (f->Read(reinterpret_cast<char*>(&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_mutex_t>& cache_lock, const std::string& fn, int64 transaction_id, bool do_submit, int64 nosubmit_transid)
{
	std::list<SSubmissionItem>::iterator submit_end = submission_queue.end();

	std::unique_ptr<IFsFile> 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<std::string> evicted_tkeys;
	read_submitted_evicted_files(fn + ".evicted", evicted_tkeys);

	std::set<std::string> 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<char*>(&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<IFsFile> 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_mutex_t>& cache_lock, const std::string& fn, bool do_submit, int64 new_trans,
	size_t& num_memf_dirty, std::map<std::string, size_t>& memf_dirty_items,
	std::map<std::string, int64>& memf_dirty_items_size)
{	
	std::unique_ptr<IFsFile> 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<SSubmissionItem>::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<std::string, SCacheVal>::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<unsigned int>(it->first->size());
						while (file->Write(file_pos, reinterpret_cast<char*>(&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<IFsFile> 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<std::string>::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<unsigned int>(it->size());
		while (file->Write(file_pos, reinterpret_cast<char*>(&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<IFsFile> 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<IFsFile> 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<std::string> del_keys;

	while(true)
	{
		unsigned int key_size;
		if(file->Read(reinterpret_cast<char*>(&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<IFsFile> 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<std::string> del_keys;

	for(std::set<std::string>::iterator it=queued_dels.begin();it!=queued_dels.end();++it)
	{
		unsigned int key_size = static_cast<unsigned int>(it->size());
		while(file->Write(file_pos, reinterpret_cast<char*>(&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_mutex_t>& 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<std::string> del_queue_local;

	std::unique_lock submission_lock(submission_mutex);

	for(size_t i=0;i<transactions.size();++i)
	{
		int64 ctransid = watoi64(getafter("trans_", transactions[i].name));

		if (skip_transid == ctransid)
		{
			continue;
		}

		if (delete_only != 0
			&& ctransid != delete_only)
		{
			continue;
		}

		if(transactions[i].isdir &&
			transactions[i].name.find("trans_")!=std::string::npos)
		{
			bool nosubmit_dirty = false;
			if (submission_items.find(std::make_pair(ctransid, key)) == submission_items.end()
				&& force_delete_transid != ctransid)
			{
				std::scoped_lock dirty_lock(dirty_item_mutex);
				auto it_ld = nosubmit_dirty_items.find(ctransid);
				if (it_ld != nosubmit_dirty_items.end()
					&& it_ld->second.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<void> 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<SFile> files = cachefs->listFiles(std::string());

	int64 maxsubmitted=-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));

			if(ctransid==transid)
			{
				continue;
			}

			std::unique_ptr<IFsFile> commited(cachefs->openFile(files[i].name + cachefs->fileSep() + "commited", MODE_READ));
			std::unique_ptr<IFsFile> dirty(cachefs->openFile(files[i].name+ cachefs->fileSep()+"dirty", MODE_READ));
			std::unique_ptr<IFsFile> nosubmit(cachefs->openFile(files[i].name+ cachefs->fileSep()+"dirty.nosubmit", MODE_READ));
			std::unique_ptr<IFsFile> 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<files.size();++i)
	{
		if(files[i].isdir &&
			files[i].name.find("trans_")==0)
		{
			int64 ctransid = watoi64(getafter("trans_", files[i].name));

			std::unique_ptr<IFsFile> dirty(cachefs->openFile(files[i].name+ cachefs->fileSep()+"dirty", MODE_READ));
			std::unique_ptr<IFsFile> dirty_mem(cachefs->openFile(files[i].name + cachefs->fileSep() + "dirty.mem", MODE_READ));
			std::unique_ptr<IFsFile> 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<int64, size_t>::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<maxsubmitted
				&& dirty_mem.get()==nullptr)
			{
				std::unique_lock dirty_lock(dirty_item_mutex);

				if ((num_dirty_items.find(ctransid) == num_dirty_items.end()
					|| num_dirty_items[ctransid] == 0)
					&& (num_delete_items.find(ctransid) == num_delete_items.end()
						|| num_delete_items[ctransid] == 0))
				{
					dirty_lock.unlock();

					if (!init)
					{
						std::unique_ptr<IFsFile> commited(cachefs->openFile(files[i].name + cachefs->fileSep() + "commited", MODE_READ));
						std::unique_ptr<IFsFile> 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<int64, size_t> TransactionalKvStore::get_num_dirty_items()
{
	std::scoped_lock lock(dirty_item_mutex);

	return num_dirty_items;	
}

std::map<int64, size_t> 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<std::string, size_t> curr_dirty_item_keys = dirty_items[transid];
#endif
	for(common::lrucache<std::string, SCacheVal>::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<IFsFile> 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<SSubmissionItem>::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<int64, size_t>::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<SSubmissionItem>::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<SSubmissionItem>::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<std::string>));
			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<Bitmap>)) + "\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<std::string, SFdKey>::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<IMemFile*>(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_mutex_t>& cache_lock, bool continue_incomplete)
{
	std::vector<SFile> files = cachefs->listFiles(std::string());

	std::vector<int64> 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<IFsFile> 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<files.size();++i)
	{
		if(files[i].isdir &&
			files[i].name.find("trans_")==0)
		{
			int64 ctransid = watoi64(getafter("trans_", files[i].name));
			std::unique_ptr<IFsFile> dirty(cachefs->openFile(files[i].name+ cachefs->fileSep()+"dirty", MODE_READ));
			std::unique_ptr<IFsFile> commited(cachefs->openFile(files[i].name+ cachefs->fileSep()+"commited", MODE_READ));
			std::unique_ptr<IFsFile> invalid(cachefs->openFile(files[i].name+ cachefs->fileSep()+"invalid", MODE_READ));
			std::unique_ptr<IFsFile> 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<mintrans) )
			{
				std::unique_ptr<IFsFile> 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 || ctransid<mintrans) )
			{
				maxtrans = (std::max)(ctransid, maxtrans);
				active_transactions.push_back(ctransid);
			}
			else
			{
				std::unique_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()));
					dirty_lock.unlock();
					Server->Log("Removing transaction "+convert(ctransid)+" (no dirty items 1)");
					remove_transaction(ctransid);
				}
				else
				{
					std::unique_ptr<IFsFile> 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<IFsFile> 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<IFsFile> 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<cache_mutex_t>& lock, const std::string& key )
{
	bool waited=false;
	std::map<std::string, size_t>::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<cache_mutex_t>& lock, const std::string& key)
{
	bool waited = false;
	std::map<std::string, size_t>::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::RetrievalRes> TransactionalKvStore::wait_for_retrieval_async(fuse_io_context& io, 
	std::unique_lock<cache_mutex_t> lock, const std::string key)
{
	RetrievalRes res;
	res.lock = std::move(lock);
	res.waited = false;
	std::map<std::string, size_t>::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<cache_mutex_t> 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<cache_mutex_t>& 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<IFsFile> 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<ICompressAndEncrypt> compress_encrypt(compress_encrypt_factory->createCompressAndEncrypt(encryption_key, src, online_kv_store, background_comp_method));

	std::vector<char> 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<IFsFile> 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<IDecryptAndDecompress> decompress_decrypt(compress_encrypt_factory->createDecryptAndDecompress(encryption_key, dst));

	std::vector<char> 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_mutex_t>& cache_lock)
{
	std::scoped_lock submission_lock(submission_mutex);

	for(std::list<SSubmissionItem>::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<SSubmissionItem>::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<IFsFile> 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<SSubmissionItem>::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<SSubmissionItem>::iterator it_del = it;
				++it;
				submission_queue_rm(it_del);
			}
		}
		else
		{
			++it;
		}
	}

	check_submission_items();
}

void TransactionalKvStore::wait_for_compressions_evictions(std::unique_lock<cache_mutex_t>& lock)
{
	curr_submit_compress_evict = false;

	std::unique_lock lock_submit(submission_mutex);

	bool retry = true;
	while (retry)
	{
		retry = false;

		for (std::map<std::pair<int64, std::string>, std::list<SSubmissionItem>::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<std::string, size_t>::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<std::string, int64>::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<void> 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<void> 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<IFile>* 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<IFile>* fd_cache)
{
	IScopedLock lock(evicted_mutex.get());
	IFile* evicted_f;
	std::unique_ptr<IFile> 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<unsigned short>(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<Bitmap>(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::pair<int64, std::string>, std::list<SSubmissionItem>::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<IFsFile>(nf);
	memf->cow = false;

	return true;
}

#ifdef HAS_ASYNC
fuse_io_context::io_uring_task<bool> 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<IMemFile> nf = std::make_unique<LinuxMemoryFile>(memf->key, this->memcache_path);
#else
		std::unique_ptr<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;
		//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<IFsFile>(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<cache_mutex_t>& 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<void> 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<IFile> 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<IFile> f(cachefs->openFile(fn, MODE_READ));
	return f.get() != nullptr;
}

bool TransactionalKvStore::writeToCacheFile(const std::string& str, const std::string& fn)
{
	std::unique_ptr<IFile> 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<SSubmissionItem>::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<SSubmissionItem>::iterator TransactionalKvStore::submission_queue_insert(SSubmissionItem & item, bool memfile, std::list<SSubmissionItem>::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<SSubmissionItem>::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_mutex_t>& 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<IFile> 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<std::string, SCacheVal>* target_cache = &kv_store->lru_cache;
				common::lrucache<std::string, SCacheVal>* 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<int64, size_t>::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<IMemFile*>(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<double>(kv_store->unthrottled_gets);
				}
				else
				{
					kv_store->unthrottled_gets_avg = 0.9*kv_store->unthrottled_gets_avg + 0.1*static_cast<double>(kv_store->unthrottled_gets);
				}
			}

			kv_store->unthrottled_gets = 0;
		}

		if (kv_store->cachesize<kv_store->min_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->cachesize<kv_store->max_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<double>(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<size_t>(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 <max_cachesize_throttle_size)
			{
				throttle_pc = 1.0 - static_cast<double>(cachesize_over) / max_cachesize_throttle_size;
			}

			kv_store->has_new_remaining_gets = true;
			kv_store->new_remaining_gets = static_cast<size_t>(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<int64> last_metadata_balance_enospc(0);
	relaxed_atomic<int64> last_data_balance_enospc(0);

	relaxed_atomic<int64> last_metadata_balance_enospc_cnt(0);
	relaxed_atomic<int64> 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<IFsFile> 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;
}