mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
(cherry picked from commit 989e60f639e7add19c0a72667efa4fa9219f0b6a) # Conflicts: # Interface/BackupFileSystem.h # btrfs/btrfsplugin/BackupFileSystem.h # clouddrive/CloudFile.cpp # clouddrive/KvStoreBackendAzure.cpp # clouddrive/KvStoreBackendAzure.h # clouddrive/KvStoreBackendLocal.cpp # clouddrive/KvStoreBackendLocal.h # clouddrive/KvStoreBackendS3.cpp # clouddrive/KvStoreBackendS3.h # clouddrive/KvStoreFrontend.cpp # clouddrive/OnlineKvStore.cpp # clouddrive/OnlineKvStore.h # clouddrive/PassThroughFileSystem.cpp # clouddrive/dllmain.cpp
4807 lines
123 KiB
C++
4807 lines
123 KiB
C++
/*************************************************************************
|
|
* 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/>.
|
|
**************************************************************************/
|
|
|
|
#include "KvStoreFrontend.h"
|
|
#ifdef HAS_LOCAL_BACKEND
|
|
#include "KvStoreBackendLocal.h"
|
|
#endif
|
|
#include <stdexcept>
|
|
#include "KvStoreDao.h"
|
|
#include "../urbackupcommon/os_functions.h"
|
|
#include "../common/data.h"
|
|
#include "../stringtools.h"
|
|
#include "ObjectCollector.h"
|
|
#include "../Interface/ThreadPool.h"
|
|
#include "../Interface/Pipe.h"
|
|
#include "../Interface/DatabaseCursor.h"
|
|
#include "../urbackupcommon/WalCheckpointThread.h"
|
|
#include <set>
|
|
#include "CompressEncrypt.h"
|
|
#include "CloudFile.h"
|
|
#include <memory.h>
|
|
#include <assert.h>
|
|
#include <memory>
|
|
#include <math.h>
|
|
#include <limits.h>
|
|
#include "../urbackupcommon/events.h"
|
|
#include <zlib.h>
|
|
#include <memory>
|
|
#include "Auto.h"
|
|
#ifdef HAS_MIRROR
|
|
#include "../urbackupserver/server_settings.h"
|
|
#endif
|
|
#ifdef HAS_SCRUB
|
|
#include "../common/json/json.h"
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
#include <sys/file.h>
|
|
#endif
|
|
|
|
//#define ASSERT_CHECK_DELETED
|
|
//#define ASSERT_CHECK_DELETED_NO_LOCINFO
|
|
#define WITH_PUT_CURR_DEL
|
|
|
|
namespace
|
|
{
|
|
const DATABASE_ID CLOUDDRIVE_DB=40;
|
|
const int TASK_REMOVE_OLD_OBJECTS=1;
|
|
const int TASK_REMOVE_TRANSACTION=2;
|
|
|
|
void retryWait(std::string label, 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>20)
|
|
{
|
|
waittime = (unsigned int)30 * 60 * 1000;
|
|
}
|
|
Server->Log(label + ": Waiting " + PrettyPrintTime(waittime)+" for retry");
|
|
Server->wait(waittime);
|
|
}
|
|
|
|
const int64 generation_skip_update = 1000;
|
|
const int64 generation_skip_max = generation_skip_update*11;
|
|
|
|
namespace
|
|
{
|
|
class HasItemCallback : public IKvStoreBackend::IListCallback
|
|
{
|
|
public:
|
|
bool has_item;
|
|
bool has_callback;
|
|
std::string item_key;
|
|
HasItemCallback()
|
|
:has_item(false), has_callback(false) {}
|
|
virtual bool onlineItem(const std::string & key, const std::string & md5sum, int64 size, int64 last_modified) override
|
|
{
|
|
has_callback = true;
|
|
if (key == "cd_magic_file")
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (key.find('_') == std::string::npos)
|
|
{
|
|
std::string err = "Key " + key + " has unknown format";
|
|
Server->Log(err, LL_ERROR);
|
|
return true;
|
|
}
|
|
std::string key_part = key;
|
|
if (key.find('/') != std::string::npos)
|
|
{
|
|
size_t lslash = key.find_last_of('/');
|
|
if (key.size() <= lslash + 1)
|
|
{
|
|
std::string err = "Key " + key + " has unknown format (2)";
|
|
Server->Log(err, LL_ERROR);
|
|
return true;
|
|
}
|
|
key_part = key.substr(lslash + 1);
|
|
}
|
|
|
|
int64 transid = os_atoi64(getuntil("_", key_part).c_str());
|
|
std::string hexkey = getafter("_", key_part);
|
|
|
|
if (hexkey == bytesToHex("test_file"))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (getuntil("_", key_part) != convert(transid))
|
|
{
|
|
std::string err = "Key " + key + " has unknown format (3)";
|
|
Server->Log(err, LL_ERROR);
|
|
return true;
|
|
}
|
|
|
|
if (hexkey == "inactive"
|
|
|| hexkey == "complete"
|
|
|| hexkey == "finalized")
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bytesToHex(hexToBytes(hexkey)) != hexkey)
|
|
{
|
|
std::string err = "Key " + key + " has unknown format (4)";
|
|
Server->Log(err, LL_ERROR);
|
|
return true;
|
|
}
|
|
|
|
has_item = true;
|
|
item_key = key;
|
|
return false;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
bool KvStoreFrontend::scrub_sync_test1 = false;
|
|
bool KvStoreFrontend::scrub_sync_test2 = false;
|
|
|
|
KvStoreFrontend::KvStoreFrontend(const std::string& db_path,
|
|
IKvStoreBackend* backend, bool allow_import, const std::string& scrub_continue, const std::string& scrub_continue_position,
|
|
IKvStoreBackend* backend_mirror, std::string mirror_window, bool background_worker_manual_run,
|
|
bool background_worker_multi_trans_delete, IBackupFileSystem* cachefs)
|
|
: backend(backend), background_worker(backend, this, background_worker_manual_run,
|
|
background_worker_multi_trans_delete), with_prefix(false),
|
|
scrub_worker(nullptr), scrub_mutex(Server->createMutex()), gen_mutex(Server->createMutex()),
|
|
unsynced_keys_mutex(Server->createMutex()),
|
|
curr_unsynced_keys(&unsynced_keys_a), other_unsynced_keys(&unsynced_keys_b),
|
|
put_shared_mutex(Server->createSharedMutex()),
|
|
has_last_modified(false), total_balance_ops(0), total_del_ops(0),
|
|
db_path(db_path), put_db_worker(db_path), task_delay(0),
|
|
backend_mirror(backend_mirror), mirror_window(mirror_window),
|
|
mirror_del_log_mutex(Server->createMutex()),
|
|
mirror_worker(this, background_worker),
|
|
mirror_curr_pos(-1), mirror_state(-1), mirror_curr_total(-1), mirror_items(-1),
|
|
objects_total_size(0), objects_total_num(0), objects_init_complete(false),
|
|
objects_init_ticket(ILLEGAL_THREADPOOL_TICKET), allow_import(allow_import),
|
|
cachefs(cachefs)
|
|
{
|
|
backend->setFrontend(this, false);
|
|
|
|
std::string str_task_delay = getFile(ExtractFilePath(db_path) + os_file_sep() + "task_delay");
|
|
|
|
if (!str_task_delay.empty())
|
|
{
|
|
task_delay = watoi64(trim(str_task_delay));
|
|
|
|
Server->Log("Background task delay " + PrettyPrintTime(task_delay*1000), LL_WARNING);
|
|
}
|
|
|
|
setMountStatus("{\"state\": \"open_db\"}");
|
|
|
|
IDatabase* db = getDatabase();
|
|
str_map params;
|
|
params["wal_autocheckpoint"] = "0";
|
|
if (db == nullptr
|
|
&& !Server->openDatabase(db_path, CLOUDDRIVE_DB, params))
|
|
{
|
|
std::string err = "Error opening database at " + db_path;
|
|
Server->Log(err, LL_ERROR);
|
|
setMountStatusErr(err);
|
|
throw std::runtime_error(err);
|
|
}
|
|
|
|
KvStoreDao dao(getDatabase());
|
|
dao.createTables();
|
|
|
|
add_created_column();
|
|
add_cd_id_tasks_column();
|
|
|
|
if (backend_mirror != nullptr)
|
|
{
|
|
add_mirrored_column();
|
|
backend_mirror_del_log.reset(Server->openFile(ExtractFilePath(db_path) + os_file_sep() + "mirror_del.log", MODE_RW_CREATE));
|
|
|
|
if (backend_mirror_del_log.get() == nullptr)
|
|
{
|
|
std::string err = "Error opening mirror del log. " + os_last_error_str();
|
|
Server->Log(err, LL_ERROR);
|
|
setMountStatusErr(err);
|
|
throw std::runtime_error(err);
|
|
}
|
|
|
|
backend_mirror_del_log_wpos = backend_mirror_del_log->Size();
|
|
bool more_data;
|
|
if (backend_mirror_del_log_wpos > 0)
|
|
{
|
|
std::vector<IFsFile::SFileExtent> exts = backend_mirror_del_log->getFileExtents(0, 4096, more_data);
|
|
if (exts.empty())
|
|
{
|
|
std::string err = "Error opening mirror del log. Cannot find file exts. " + os_last_error_str();
|
|
Server->Log(err, LL_ERROR);
|
|
setMountStatusErr(err);
|
|
throw std::runtime_error(err);
|
|
}
|
|
|
|
backend_mirror_del_log_rpos = exts[0].offset;
|
|
}
|
|
else
|
|
{
|
|
backend_mirror_del_log_rpos = 0;
|
|
}
|
|
}
|
|
|
|
if (backend->prefer_sequential_read())
|
|
{
|
|
add_last_modified_column();
|
|
|
|
put_db_worker.set_with_last_modified(has_last_modified);
|
|
}
|
|
|
|
if(trim(getFile(ExtractFilePath(db_path) + os_file_sep() + "skip_backend_test"))!="1")
|
|
{
|
|
setMountStatus("{\"state\": \"dl_magic_file\"}");
|
|
|
|
IFsFile* tmp_f = Server->openTemporaryFile();
|
|
if (tmp_f == nullptr)
|
|
{
|
|
std::string err = "Error opening temporary file (magic file). " + os_last_error_str();
|
|
Server->Log(err, LL_ERROR);
|
|
setMountStatusErr(err);
|
|
throw std::runtime_error(err);
|
|
}
|
|
ScopedDeleteFile tmp_f_del(tmp_f);
|
|
IFsFile* ret_file = tmp_f;
|
|
std::string ret_md5sum;
|
|
unsigned int get_status;
|
|
if (!backend->get("cd_magic_file", std::string(), IKvStoreBackend::GetDecrypted,
|
|
false, ret_file, ret_md5sum, get_status))
|
|
{
|
|
KvStoreDao::CdSingleObject obj = dao.getSingleObject();
|
|
|
|
if (obj.exists)
|
|
{
|
|
Server->Log("Retrieving " + prefixKey(encodeKey(obj.tkey, obj.trans_id))
|
|
+ " (first object in cache database)");
|
|
if (!backend->get(prefixKey(encodeKey(obj.tkey, obj.trans_id)),
|
|
obj.md5sum, IKvStoreBackend::GetDecrypted,
|
|
false, ret_file, ret_md5sum, get_status))
|
|
{
|
|
Server->Log("Retrieving " + prefixKey(encodeKey(obj.tkey, obj.trans_id))
|
|
+ " failed. Trying with first listed item on backend storage...");
|
|
obj.exists = false;
|
|
}
|
|
}
|
|
|
|
if (!obj.exists)
|
|
{
|
|
Server->Log("Listing first object in bucket...", LL_INFO);
|
|
HasItemCallback has_item;
|
|
if (!backend->list(&has_item)
|
|
&& !has_item.has_callback)
|
|
{
|
|
std::string err = "Error listing objects on cloud drive";
|
|
Server->Log(err, LL_ERROR);
|
|
setMountStatusErr(err);
|
|
throw std::runtime_error(err);
|
|
}
|
|
|
|
if (has_item.has_item
|
|
&& !backend->get(has_item.item_key,
|
|
std::string(), IKvStoreBackend::GetDecrypted,
|
|
true, ret_file, ret_md5sum, get_status))
|
|
{
|
|
std::string err = "Error getting item " + has_item.item_key + " from cloud drive. Encryption key may be wrong.";
|
|
setMountStatusErr(err);
|
|
Server->Log(err, LL_ERROR);
|
|
throw std::runtime_error(err);
|
|
}
|
|
}
|
|
|
|
IFsFile* new_f = Server->openTemporaryFile();
|
|
if (new_f == nullptr)
|
|
{
|
|
std::string err = "Error opening temporary file (magic file 2). " + os_last_error_str();
|
|
Server->Log(err, LL_ERROR);
|
|
setMountStatusErr(err);
|
|
throw std::runtime_error(err);
|
|
}
|
|
ScopedDeleteFile new_f_del(new_f);
|
|
|
|
if (new_f->Write("CD_MAGIC") != 8)
|
|
{
|
|
std::string err = "Error writing to magic file. " + os_last_error_str();
|
|
Server->Log(err, LL_ERROR);
|
|
setMountStatusErr(err);
|
|
throw std::runtime_error(err);
|
|
}
|
|
|
|
current_generation = (int64)Server->getSecureRandomNumber() << 32 | Server->getSecureRandomNumber();
|
|
|
|
std::string md5sum;
|
|
int64 size;
|
|
if (!backend->put("cd_magic_file", new_f, 0, true, md5sum, size))
|
|
{
|
|
std::string err = "Error uploading magic file.";
|
|
setMountStatusErr(err);
|
|
Server->Log(err, LL_ERROR);
|
|
throw std::runtime_error(err);
|
|
}
|
|
}
|
|
else if (tmp_f->Read(static_cast<int64>(0), static_cast<_u32>(tmp_f->Size())) != "CD_MAGIC")
|
|
{
|
|
std::string content = tmp_f->Read(static_cast<int64>(0), static_cast<_u32>(tmp_f->Size()));
|
|
std::string err = "Magic file content is wrong. ("+ content+")";
|
|
Server->Log(err, LL_ERROR);
|
|
setMountStatusErr(err);
|
|
throw std::runtime_error(err);
|
|
}
|
|
}
|
|
|
|
if(allow_import
|
|
&& dao.getMiscValue("import_done").value!="1")
|
|
{
|
|
if(importFromBackend(dao))
|
|
{
|
|
dao.setMiscValue("import_done", "1");
|
|
}
|
|
else
|
|
{
|
|
std::string err = "Importing from backend failed";
|
|
Server->Log(err, LL_ERROR);
|
|
setMountStatusErr(err);
|
|
throw std::runtime_error(err);
|
|
}
|
|
}
|
|
|
|
if (dao.getMiscValue("has_prefix").value == "1")
|
|
{
|
|
with_prefix = true;
|
|
}
|
|
|
|
dao.getDb()->Write("PRAGMA journal_mode=WAL");
|
|
|
|
dao.getDb()->Write("INSERT OR IGNORE INTO misc (key, value) VALUES ('initial_val', '1')");
|
|
|
|
int wal_mode = MODE_READ;
|
|
#ifdef _WIN32
|
|
wal_mode = MODE_RW_DEVICE;
|
|
#endif
|
|
|
|
db_wal_file.reset(Server->openFile(db_path + "-wal", wal_mode));
|
|
|
|
if (db_wal_file.get() == nullptr)
|
|
{
|
|
std::string err = "Error opening database wal file at " + db_path + "-wal";
|
|
Server->Log(err, LL_ERROR);
|
|
throw std::runtime_error(err);
|
|
}
|
|
|
|
{
|
|
DBScopedSynchronous synchronous(dao.getDb());
|
|
KvStoreDao::CondInt64 generation = dao.getGeneration();
|
|
if (generation.exists)
|
|
{
|
|
current_generation = generation.value + generation_skip_max + 100;
|
|
last_persisted_generation = generation.value;
|
|
last_update_generation = generation.value;
|
|
}
|
|
else
|
|
{
|
|
current_generation = 1;
|
|
last_persisted_generation = 0;
|
|
last_update_generation = 0;
|
|
dao.insertGeneration(last_persisted_generation);
|
|
}
|
|
}
|
|
|
|
if (backend->has_transactions())
|
|
{
|
|
put_db_worker.set_do_synchonous(false);
|
|
put_db_worker.set_db_wal_file(db_wal_file.get());
|
|
}
|
|
|
|
put_db_worker_ticket = Server->getThreadPool()->execute(&put_db_worker, "bput db");
|
|
|
|
backend->setFrontend(this, true);
|
|
|
|
background_worker_ticket = Server->getThreadPool()->execute(&background_worker, "bkg worker");
|
|
|
|
static bool has_wal_checkpoint_thread = false;
|
|
|
|
if (!has_wal_checkpoint_thread)
|
|
{
|
|
has_wal_checkpoint_thread = true;
|
|
|
|
WalCheckpointThread* wal_checkpoint_thread = new WalCheckpointThread(100 * 1024 * 1024, 1000 * 1024 * 1024,
|
|
db_path, CLOUDDRIVE_DB);
|
|
Server->createThread(wal_checkpoint_thread, "db checkpoint");
|
|
}
|
|
|
|
setMountStatus("{\"state\": \"init_size\"}");
|
|
objects_init_ticket = Server->getThreadPool()->execute(this, "init sizes");
|
|
|
|
{
|
|
std::unique_ptr<IFile> empty_file(cachefs->openFile("empty.file", MODE_WRITE));
|
|
if (!empty_file)
|
|
{
|
|
std::string err = "Error creating empty file. " + os_last_error_str();
|
|
Server->Log(err, LL_ERROR);
|
|
setMountStatusErr(err);
|
|
throw std::runtime_error(err);
|
|
}
|
|
empty_file_path = "empty.file";
|
|
}
|
|
|
|
if (scrub_continue == "scrub")
|
|
{
|
|
start_scrub(ScrubAction::Scrub, scrub_continue_position);
|
|
}
|
|
else if (scrub_continue == "balance")
|
|
{
|
|
start_scrub(ScrubAction::Balance, scrub_continue_position);
|
|
}
|
|
else if (scrub_continue == "rebuild")
|
|
{
|
|
start_scrub(ScrubAction::Rebuild, scrub_continue_position);
|
|
}
|
|
|
|
if (backend_mirror != nullptr)
|
|
{
|
|
mirror_worker_ticket = Server->getThreadPool()->execute(&mirror_worker, "mirror worker");
|
|
}
|
|
else
|
|
{
|
|
mirror_worker_ticket = ILLEGAL_THREADPOOL_TICKET;
|
|
}
|
|
|
|
std::string scrub_obj = getFile(ExtractFilePath(db_path) + os_file_sep() + "scrub_obj");
|
|
|
|
if (!scrub_obj.empty())
|
|
{
|
|
IFsFile* tmpf = Server->openTemporaryFile();
|
|
ScopedDeleteFile del_tmpf(tmpf);
|
|
if(tmpf==nullptr)
|
|
abort();
|
|
|
|
std::string ret_md5sum;
|
|
unsigned int get_status;
|
|
if (!backend->get(trim(scrub_obj), std::string(), IKvStoreBackend::GetScrub, true, tmpf, ret_md5sum, get_status))
|
|
{
|
|
Server->Log("Get scrub of object " + scrub_obj + " failed", LL_ERROR);
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Get scrub of object " + scrub_obj + " succeeded. md5sum=" + bytesToHex(ret_md5sum) + " status=" + convert(get_status), LL_WARNING);
|
|
}
|
|
}
|
|
}
|
|
|
|
KvStoreFrontend::~KvStoreFrontend()
|
|
{
|
|
background_worker.quit();
|
|
put_db_worker.quit();
|
|
mirror_worker.quit();
|
|
|
|
Server->getThreadPool()->waitFor(background_worker_ticket);
|
|
Server->getThreadPool()->waitFor(put_db_worker_ticket);
|
|
if (mirror_worker_ticket != ILLEGAL_THREADPOOL_TICKET)
|
|
{
|
|
Server->getThreadPool()->waitFor(mirror_worker_ticket);
|
|
}
|
|
|
|
if (objects_init_ticket != ILLEGAL_THREADPOOL_TICKET)
|
|
{
|
|
Server->getThreadPool()->waitFor(objects_init_ticket);
|
|
}
|
|
|
|
Server->deleteFile(empty_file_path);
|
|
|
|
delete backend;
|
|
}
|
|
|
|
IFsFile* KvStoreFrontend::get(int64 cd_id, const std::string& key, int64 transid,
|
|
bool prioritize_read, IFsFile* tmpl_file,
|
|
bool allow_error_event, bool& not_found, int64* get_transid)
|
|
{
|
|
not_found=false;
|
|
if (tmpl_file == nullptr)
|
|
return nullptr;
|
|
|
|
bool is_unsynced = false;
|
|
SUnsyncedKey unsynced_key;
|
|
{
|
|
IScopedLock lock(unsynced_keys_mutex.get());
|
|
auto it_curr = curr_unsynced_keys->find(std::make_pair(cd_id, key));
|
|
auto it_other = other_unsynced_keys->find(std::make_pair(cd_id, key));
|
|
if (it_curr != curr_unsynced_keys->end()
|
|
|| it_other != other_unsynced_keys->end())
|
|
{
|
|
is_unsynced = true;
|
|
|
|
if (it_curr == curr_unsynced_keys->end()
|
|
|| it_other == other_unsynced_keys->end())
|
|
{
|
|
unsynced_key = it_curr != curr_unsynced_keys->end() ? it_curr->second : it_other->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
KvStoreDao dao(getDatabase());
|
|
|
|
bool get_unsynced = false;
|
|
bool use_unsynced_key = false;
|
|
if (is_unsynced)
|
|
{
|
|
if (backend->has_transactions()
|
|
&& backend->can_read_unsynced())
|
|
{
|
|
Server->Log("Item key=" + bytesToHex(key) + " not synced. Reading unsynced item...");
|
|
get_unsynced = true;
|
|
if (unsynced_key.transid!=transid)
|
|
{
|
|
if (unsynced_key.transid != 0
|
|
&& unsynced_key.transid!=-1)
|
|
{
|
|
KvStoreDao::CdObject cd_object = cd_id == 0 ? dao.getObject(transid, key) :
|
|
dao.getObjectCd(cd_id, transid, key);
|
|
|
|
if (!cd_object.exists)
|
|
{
|
|
use_unsynced_key = true;
|
|
Server->Log("Item key=" + bytesToHex(key) + " does not exist in db. Using unsynced item from transid "+convert(unsynced_key.transid));
|
|
}
|
|
else if (cd_object.trans_id == unsynced_key.transid)
|
|
{
|
|
use_unsynced_key = true;
|
|
Server->Log("Item key=" + bytesToHex(key) + " has current transid in db. Using unsynced item from transid " + convert(unsynced_key.transid));
|
|
}
|
|
else if (cd_object.trans_id > unsynced_key.transid)
|
|
{
|
|
get_unsynced = false;
|
|
Server->Log("Item key=" + bytesToHex(key) + " more recent in db. Not using unsynced item from transid " + convert(unsynced_key.transid));
|
|
}
|
|
else if(cd_object.trans_id < unsynced_key.transid
|
|
&& ((cd_id==0 && dao.isTransactionActive(unsynced_key.transid).exists)
|
|
|| (cd_id!=0 && dao.isTransactionActiveCd(cd_id, unsynced_key.transid).exists) ) )
|
|
{
|
|
use_unsynced_key = true;
|
|
Server->Log("Item key=" + bytesToHex(key) + " less recent in db. Using unsynced item from transid " + convert(unsynced_key.transid));
|
|
}
|
|
else
|
|
{
|
|
put_db_worker.flush();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
put_db_worker.flush();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Item key=" + bytesToHex(key) + " has unsynced item in current transid. Using unsynced item from (current) transid " + convert(unsynced_key.transid));
|
|
use_unsynced_key = true;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Item key=" + bytesToHex(key) + " not synced. Syncing now...");
|
|
if (!sync())
|
|
{
|
|
Server->Log("Syncing before get failed", LL_ERROR);
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
KvStoreDao::CdObject cd_object;
|
|
if (get_unsynced
|
|
&& use_unsynced_key)
|
|
{
|
|
cd_object.exists = true;
|
|
cd_object.md5sum = unsynced_key.md5sum;
|
|
cd_object.size = 0;
|
|
cd_object.trans_id = unsynced_key.transid;
|
|
assert(cd_object.trans_id > 0);
|
|
}
|
|
else
|
|
{
|
|
cd_object = cd_id == 0 ?
|
|
dao.getObject(transid, key) :
|
|
dao.getObjectCd(cd_id, transid, key);
|
|
}
|
|
|
|
if(!cd_object.exists
|
|
|| cd_object.size==-1)
|
|
{
|
|
not_found=true;
|
|
dao.getDb()->freeMemory();
|
|
return tmpl_file;
|
|
}
|
|
|
|
if (cd_object.md5sum.empty()
|
|
&& !backend->has_transactions())
|
|
{
|
|
put_db_worker.flush();
|
|
cd_object = cd_id==0 ?
|
|
dao.getObject(transid, key) :
|
|
dao.getObjectCd(cd_id, transid, key);
|
|
}
|
|
|
|
int flags = IKvStoreBackend::GetDecrypted;
|
|
|
|
if (prioritize_read)
|
|
{
|
|
flags |= IKvStoreBackend::GetPrioritize;
|
|
}
|
|
|
|
if (get_unsynced)
|
|
{
|
|
flags |= IKvStoreBackend::GetUnsynced;
|
|
}
|
|
|
|
if (get_transid != nullptr)
|
|
{
|
|
*get_transid = cd_object.trans_id;
|
|
}
|
|
|
|
IFsFile* ret = tmpl_file;
|
|
std::string ret_md5sum;
|
|
unsigned int get_status;
|
|
bool from_mirror = false;
|
|
|
|
std::string bkey = prefixKey(encodeKey(cd_id, key, cd_object.trans_id));
|
|
if (!backend->get(bkey, cd_object.md5sum,
|
|
flags, allow_error_event, ret, ret_md5sum, get_status))
|
|
{
|
|
not_found = (get_status & IKvStoreBackend::GetStatusNotFound)>0;
|
|
bool backend_not_found = not_found;
|
|
if (backend_mirror == nullptr
|
|
|| !backend_mirror->get(bkey,
|
|
get_md5sum(cd_object.md5sum),
|
|
flags, allow_error_event, ret, ret_md5sum, get_status))
|
|
{
|
|
not_found = (get_status & IKvStoreBackend::GetStatusNotFound)>0;
|
|
dao.getDb()->freeMemory();
|
|
assert(tmpl_file != nullptr || ret == nullptr);
|
|
not_found = backend_not_found && not_found;
|
|
if (not_found)
|
|
{
|
|
return tmpl_file;
|
|
}
|
|
return nullptr;
|
|
}
|
|
else if (backend_mirror != nullptr)
|
|
{
|
|
from_mirror = true;
|
|
}
|
|
}
|
|
|
|
if (ret_md5sum != cd_object.md5sum
|
|
&& !from_mirror)
|
|
{
|
|
DBScopedSynchronous synchronous(dao.getDb());
|
|
if(cd_id==0)
|
|
dao.updateObjectMd5sum(ret_md5sum, transid, key);
|
|
else
|
|
dao.updateObjectMd5sumCd(ret_md5sum, cd_id, transid, key);
|
|
}
|
|
|
|
dao.getDb()->freeMemory();
|
|
return ret;
|
|
}
|
|
|
|
int64 KvStoreFrontend::get_transid(int64 cd_id, const std::string & key, int64 transid)
|
|
{
|
|
bool is_unsynced = false;
|
|
SUnsyncedKey unsynced_key;
|
|
{
|
|
IScopedLock lock(unsynced_keys_mutex.get());
|
|
auto it_curr = curr_unsynced_keys->find(std::make_pair(cd_id, key));
|
|
auto it_other = other_unsynced_keys->find(std::make_pair(cd_id, key));
|
|
if (it_curr != curr_unsynced_keys->end()
|
|
|| it_other != other_unsynced_keys->end())
|
|
{
|
|
is_unsynced = true;
|
|
|
|
if (it_curr == curr_unsynced_keys->end()
|
|
|| it_other == other_unsynced_keys->end())
|
|
{
|
|
unsynced_key = it_curr != curr_unsynced_keys->end() ? it_curr->second : it_other->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
KvStoreDao dao(getDatabase());
|
|
if (is_unsynced)
|
|
{
|
|
if (unsynced_key.transid == transid)
|
|
{
|
|
Server->Log("Item (get transid) key=" + bytesToHex(key) + " res=" + convert(transid) + " (current)");
|
|
return transid;
|
|
}
|
|
else if (unsynced_key.transid != 0)
|
|
{
|
|
KvStoreDao::CdObject cd_object = cd_id==0 ?
|
|
dao.getObject(transid, key) :
|
|
dao.getObjectCd(cd_id, transid, key);
|
|
dao.getDb()->freeMemory();
|
|
|
|
if (!cd_object.exists)
|
|
{
|
|
Server->Log("Item (get transid) key=" + bytesToHex(key) + " does not exist in db. Using unsynced item from transid " + convert(unsynced_key.transid));
|
|
return unsynced_key.transid;
|
|
}
|
|
else if (cd_object.trans_id == unsynced_key.transid)
|
|
{
|
|
Server->Log("Item (get transid) key=" + bytesToHex(key) + " has current transid in db. Using unsynced item from transid " + convert(unsynced_key.transid));
|
|
return unsynced_key.transid;
|
|
}
|
|
else if (cd_object.trans_id > unsynced_key.transid)
|
|
{
|
|
Server->Log("Item (get transid) key=" + bytesToHex(key) + " more recent in db. Not using unsynced item from transid " + convert(unsynced_key.transid));
|
|
return cd_object.trans_id;
|
|
}
|
|
else if (cd_object.trans_id < unsynced_key.transid
|
|
&& ((cd_id==0 && dao.isTransactionActive(unsynced_key.transid).exists)
|
|
|| (cd_id!=0 && dao.isTransactionActiveCd(cd_id, unsynced_key.transid).exists) ) )
|
|
{
|
|
Server->Log("Item (get transid) key=" + bytesToHex(key) + " less recent in db. Using unsynced item from transid " + convert(unsynced_key.transid));
|
|
return unsynced_key.transid;
|
|
}
|
|
else
|
|
{
|
|
put_db_worker.flush();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
put_db_worker.flush();
|
|
}
|
|
}
|
|
|
|
KvStoreDao::CdObject cd_object = cd_id==0 ? dao.getObject(transid, key)
|
|
: dao.getObjectCd(cd_id, transid, key);
|
|
dao.getDb()->freeMemory();
|
|
|
|
return cd_object.exists ? cd_object.trans_id : 0;
|
|
}
|
|
|
|
bool KvStoreFrontend::reset(const std::string & key, int64 transid)
|
|
{
|
|
KvStoreDao dao(getDatabase());
|
|
KvStoreDao::CdObject cd_object = dao.getObject(transid, key);
|
|
|
|
if (!cd_object.exists)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (cd_object.size > 0)
|
|
objects_total_size -= cd_object.size;
|
|
|
|
--objects_total_num;
|
|
|
|
return dao.deleteObject(cd_object.trans_id, key);
|
|
}
|
|
|
|
bool KvStoreFrontend::put(int64 cd_id, const std::string& key, int64 transid,
|
|
int64 generation, IFsFile* src, unsigned int cflags,
|
|
bool allow_error_event, int64& compressed_size)
|
|
{
|
|
IScopedReadLock read_lock(put_shared_mutex.get());
|
|
|
|
std::string tkey = prefixKey(encodeKey(cd_id, key, transid));
|
|
|
|
#ifdef WITH_PUT_CURR_DEL
|
|
if (backend->need_curr_del())
|
|
{
|
|
//Careful, this makes it so that a backend->sync() at the wrong time causes the item to vanish on crash
|
|
// (put must not be used to overwrite items in transactions that are completed)
|
|
KvStoreDao dao(getDatabase());
|
|
KvStoreDao::CdObject obj = cd_id == 0 ?
|
|
dao.getObjectInTransid(transid, tkey) :
|
|
dao.getObjectInTransidCd(cd_id, transid, tkey);
|
|
if (obj.exists
|
|
&& !get_locinfo(obj.md5sum).empty())
|
|
{
|
|
size_t idx1 = 0;
|
|
size_t idx2 = 0;
|
|
bool b = backend->del([&tkey, &idx1](IKvStoreBackend::key_next_action_t action, std::string* key) {
|
|
if (action==IKvStoreBackend::key_next_action_t::reset) {
|
|
idx1 = 0;
|
|
return true;
|
|
}
|
|
if (action == IKvStoreBackend::key_next_action_t::clear) {
|
|
return true;
|
|
}
|
|
if (idx1>0)
|
|
return false;
|
|
*key = tkey;
|
|
++idx1;
|
|
return true;
|
|
},
|
|
[&obj, &idx2](IKvStoreBackend::key_next_action_t action, std::string* locinfo) {
|
|
if (action == IKvStoreBackend::key_next_action_t::reset) {
|
|
idx2 = 0;
|
|
return true;
|
|
}
|
|
if (action == IKvStoreBackend::key_next_action_t::clear) {
|
|
return true;
|
|
}
|
|
if (idx2>0)
|
|
return false;
|
|
*locinfo = get_locinfo(obj.md5sum);
|
|
++idx2;
|
|
return true;
|
|
},false);
|
|
assert(b);
|
|
}
|
|
if (obj.size > 0)
|
|
objects_total_size -= obj.size;
|
|
|
|
if(obj.exists
|
|
&& obj.size>=0)
|
|
--objects_total_num;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
KvStoreDao dao(getDatabase());
|
|
KvStoreDao::CdObject obj = cd_id == 0 ?
|
|
dao.getObjectInTransid(transid, tkey) :
|
|
dao.getObjectInTransidCd(cd_id, transid, tkey);
|
|
if (obj.exists
|
|
&& obj.size > 0)
|
|
{
|
|
objects_total_size -= obj.size;
|
|
}
|
|
if(obj.exists
|
|
&& obj.size >= 0)
|
|
--objects_total_num;
|
|
}
|
|
|
|
int64 object_id;
|
|
if(!backend->has_transactions())
|
|
{
|
|
object_id = put_db_worker.add(cd_id, transid, key, generation);
|
|
}
|
|
|
|
int64 last_modified = 0;
|
|
if (has_last_modified)
|
|
{
|
|
#ifdef HAS_LOCAL_BACKEND
|
|
last_modified = curr_last_modified();
|
|
#endif
|
|
}
|
|
unsigned int put_flags = 0;
|
|
if (cflags & IOnlineKvStore::PutAlreadyCompressedEncrypted)
|
|
put_flags |= IKvStoreBackend::PutAlreadyCompressedEncrypted;
|
|
if (cflags & IOnlineKvStore::PutMetadata)
|
|
put_flags |= IKvStoreBackend::PutMetadata;
|
|
std::string md5sum;
|
|
compressed_size = 0;
|
|
bool ret = backend->put(tkey, src, put_flags, allow_error_event, md5sum, compressed_size);
|
|
|
|
if(ret)
|
|
{
|
|
objects_total_size += compressed_size;
|
|
bool init_complete = objects_init_complete;
|
|
int64 total_num = objects_total_num++;
|
|
|
|
if (total_num>0
|
|
&& total_num % 1000 == 0
|
|
&& init_complete)
|
|
{
|
|
update_total_num(total_num);
|
|
}
|
|
|
|
if (!backend->has_transactions())
|
|
{
|
|
put_db_worker.update(cd_id, object_id, compressed_size, md5sum, last_modified);
|
|
}
|
|
else
|
|
{
|
|
put_db_worker.add(cd_id, transid, key, md5sum, compressed_size, last_modified, generation);
|
|
{
|
|
IScopedLock lock(unsynced_keys_mutex.get());
|
|
SUnsyncedKey& us_key = (*curr_unsynced_keys)[std::make_pair(cd_id, key)];
|
|
if (!us_key.md5sum.empty())
|
|
{
|
|
us_key.md5sum[0] = 'A';
|
|
}
|
|
if (us_key.transid != 0)
|
|
{
|
|
us_key.transid = -1;
|
|
}
|
|
else
|
|
{
|
|
us_key.transid = transid;
|
|
us_key.md5sum = md5sum;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mirror_curr_total >= 0)
|
|
{
|
|
mirror_curr_total += compressed_size;
|
|
++mirror_items;
|
|
}
|
|
}
|
|
else if(!backend->has_transactions())
|
|
{
|
|
IDatabase* db = getDatabase();
|
|
{
|
|
KvStoreDao dao(db);
|
|
if(cd_id==0)
|
|
dao.deletePartialObject(object_id);
|
|
else
|
|
dao.deletePartialObjectCd(object_id);
|
|
}
|
|
db->freeMemory();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int64 KvStoreFrontend::new_transaction(int64 cd_id, bool allow_error_event)
|
|
{
|
|
Server->Log("Starting new transaction... (cd_id="+convert(cd_id)+")", LL_INFO);
|
|
IDatabase* db = getDatabase();
|
|
int64 id;
|
|
{
|
|
KvStoreDao dao(getDatabase());
|
|
DBScopedSynchronous synchronous(dao.getDb());
|
|
if (cd_id == 0)
|
|
dao.newTransaction();
|
|
else
|
|
dao.newTransactionCd(cd_id);
|
|
|
|
id = db->getLastInsertID();
|
|
}
|
|
db->freeMemory();
|
|
return id;
|
|
}
|
|
|
|
bool KvStoreFrontend::transaction_finalize(int64 cd_id, int64 transid, bool complete, bool allow_error_event)
|
|
{
|
|
if(complete)
|
|
Server->Log("Marking transaction "+convert(transid)+" as complete... (cd_id=" + convert(cd_id) + ")", LL_INFO);
|
|
else
|
|
Server->Log("Marking transaction " + convert(transid) + " as finalized... (cd_id=" + convert(cd_id) + ")", LL_INFO);
|
|
|
|
int64 size;
|
|
std::string md5sum;
|
|
int tries = 4;
|
|
while (tries > 0)
|
|
{
|
|
std::string tkey;
|
|
if(cd_id==0)
|
|
tkey=convert(transid) + (complete ? "_complete" : "_finalized");
|
|
else
|
|
tkey = convert(cd_id)+"_"+convert(transid) + (complete ? "_complete" : "_finalized");
|
|
|
|
std::unique_ptr<IFsFile> src(cachefs->openFile(empty_file_path, MODE_READ));
|
|
|
|
if(!src)
|
|
{
|
|
Server->Log("Error opening empty file. "+cachefs->lastError(), LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
if (backend->put(prefixKey(tkey),
|
|
src.get(), 0, allow_error_event, md5sum, size))
|
|
{
|
|
tries = -2;
|
|
break;
|
|
}
|
|
--tries;
|
|
}
|
|
|
|
if (tries != -2)
|
|
return false;
|
|
|
|
if (!sync())
|
|
{
|
|
Server->Log("Syncing failed", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
IDatabase* db = getDatabase();
|
|
{
|
|
KvStoreDao dao(db);
|
|
DBScopedSynchronous synchronous(dao.getDb());
|
|
DBScopedWriteTransaction scoped_transaction(dao.getDb());
|
|
|
|
std::vector<int64> last_finalized;
|
|
if (complete)
|
|
{
|
|
last_finalized = cd_id == 0 ?
|
|
dao.getLastFinalizedTransactions(dao.getMaxCompleteTransaction().value, transid) :
|
|
dao.getLastFinalizedTransactionsCd(cd_id, dao.getMaxCompleteTransactionCd(cd_id).value, transid);
|
|
|
|
}
|
|
|
|
if(cd_id==0)
|
|
dao.setTransactionComplete(complete ? 2 : 1, transid);
|
|
else
|
|
dao.setTransactionCompleteCd(complete ? 2 : 1, cd_id, transid);
|
|
|
|
if (complete)
|
|
{
|
|
for (size_t i = 0; i < last_finalized.size(); ++i)
|
|
{
|
|
dao.addTask(TASK_REMOVE_OLD_OBJECTS, last_finalized[i], Server->getTimeSeconds(), cd_id);
|
|
}
|
|
dao.addTask(TASK_REMOVE_OLD_OBJECTS, transid, Server->getTimeSeconds(), cd_id);
|
|
}
|
|
}
|
|
db->freeMemory();
|
|
return true;
|
|
}
|
|
|
|
bool KvStoreFrontend::set_active_transactions(int64 cd_id, const std::vector<int64>& active_transactions )
|
|
{
|
|
Server->Log("Setting active transactions... (cd_id=" + convert(cd_id) + ")", LL_INFO);
|
|
|
|
std::unique_ptr<IFsFile> empty_f(cachefs->openFile(empty_file_path, MODE_READ));
|
|
|
|
if(!empty_f)
|
|
{
|
|
Server->Log("Error opening empty file -2. "+cachefs->lastError(), LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
IDatabase* db = getDatabase();
|
|
KvStoreDao dao(db);
|
|
DBScopedSynchronous synchronous(dao.getDb());
|
|
|
|
int64 max_active = cd_id == 0 ?
|
|
dao.getMaxCompleteTransaction().value :
|
|
dao.getMaxCompleteTransactionCd(cd_id).value;
|
|
|
|
for (size_t i = 0; i < active_transactions.size(); ++i)
|
|
{
|
|
max_active = (std::max)(max_active, active_transactions[i]);
|
|
}
|
|
|
|
std::vector<int64> incomplete_transactions = cd_id == 0 ?
|
|
dao.getIncompleteTransactions(max_active) :
|
|
dao.getIncompleteTransactionsCd(max_active, cd_id);
|
|
|
|
for (size_t i = 0; i < incomplete_transactions.size(); ++i)
|
|
{
|
|
if (std::find(active_transactions.begin(), active_transactions.end(), incomplete_transactions[i])
|
|
== active_transactions.end())
|
|
{
|
|
int64 size;
|
|
std::string md5sum;
|
|
|
|
const int max_tries = 40;
|
|
int tries = max_tries;
|
|
while (tries > 0)
|
|
{
|
|
std::string tkey;
|
|
if (cd_id == 0)
|
|
tkey = convert(incomplete_transactions[i]) + "_inactive";
|
|
else
|
|
tkey = convert(cd_id)+"_"+convert(incomplete_transactions[i]) + "_inactive";
|
|
if (backend->put(prefixKey(tkey), empty_f.get(), 0, true, md5sum, size))
|
|
{
|
|
tries = -2;
|
|
break;
|
|
}
|
|
--tries;
|
|
retryWait("set_active_transactions", max_tries - tries);
|
|
}
|
|
|
|
if (tries != -2)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
sync();
|
|
|
|
DBScopedWriteTransaction scoped_transaction(dao.getDb());
|
|
|
|
for(size_t i=0;i<incomplete_transactions.size();++i)
|
|
{
|
|
if(std::find(active_transactions.begin(), active_transactions.end(), incomplete_transactions[i])
|
|
==active_transactions.end())
|
|
{
|
|
if (cd_id == 0)
|
|
{
|
|
dao.setTransactionActive(0, incomplete_transactions[i]);
|
|
dao.setTransactionComplete(0, incomplete_transactions[i]);
|
|
}
|
|
else
|
|
{
|
|
dao.setTransactionActiveCd(0, cd_id, incomplete_transactions[i]);
|
|
dao.setTransactionCompleteCd(0, cd_id, incomplete_transactions[i]);
|
|
}
|
|
dao.addTask(TASK_REMOVE_TRANSACTION, incomplete_transactions[i], Server->getTimeSeconds(), cd_id);
|
|
}
|
|
}
|
|
|
|
db->freeMemory();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool KvStoreFrontend::del(int64 cd_id, const std::vector<std::string>& keys, int64 transid )
|
|
{
|
|
std::string hexkey;
|
|
for(size_t i=0;i<keys.size();++i)
|
|
{
|
|
if(!hexkey.empty()) hexkey+=",";
|
|
hexkey += bytesToHex(reinterpret_cast<const unsigned char*>(keys[i].c_str()), keys[i].size());
|
|
}
|
|
|
|
Server->Log("Deleting objects "+ hexkey+" (cd_id="+convert(cd_id)+")", LL_INFO);
|
|
hexkey = std::string();
|
|
|
|
if(scrub_worker!=nullptr)
|
|
{
|
|
std::vector<std::pair<int64, std::string> > deleted_objects;
|
|
deleted_objects.resize(keys.size());
|
|
for (size_t i = 0; i < keys.size(); ++i)
|
|
{
|
|
deleted_objects[i].first = transid;
|
|
deleted_objects[i].second = keys[i];
|
|
}
|
|
IScopedLock lock(scrub_mutex.get());
|
|
if (scrub_worker != nullptr)
|
|
{
|
|
scrub_worker->add_deleted_objects(cd_id, deleted_objects);
|
|
}
|
|
}
|
|
|
|
KvStoreDao dao(getDatabase());
|
|
size_t idx = 0;
|
|
int64 total_del_size = 0;
|
|
int64 total_del_num = 0;
|
|
bool no_reset = true;
|
|
|
|
IKvStoreBackend::key_next_fun_t key_next_fun =
|
|
[this, &cd_id, transid, &idx, &keys, &dao, &total_del_size, &total_del_num, &no_reset](IKvStoreBackend::key_next_action_t action, std::string* key) {
|
|
|
|
if (action == IKvStoreBackend::key_next_action_t::clear) {
|
|
assert(key==nullptr);
|
|
return true;
|
|
}
|
|
|
|
if (action== IKvStoreBackend::key_next_action_t::reset) {
|
|
if(idx!=0)
|
|
no_reset = false;
|
|
|
|
idx = 0;
|
|
assert(key==nullptr);
|
|
return true;
|
|
}
|
|
|
|
assert(action == IKvStoreBackend::key_next_action_t::next);
|
|
|
|
KvStoreDao::CdObject cd_object;
|
|
cd_object.exists = false;
|
|
while (!cd_object.exists && idx<keys.size())
|
|
{
|
|
cd_object = cd_id==0 ?
|
|
dao.getObjectInTransid(transid, keys[idx]) :
|
|
dao.getObjectInTransidCd(cd_id, transid, keys[idx]);
|
|
if (!cd_object.exists)
|
|
++idx;
|
|
else if (cd_object.size > 0
|
|
&& no_reset)
|
|
total_del_size += cd_object.size;
|
|
|
|
if (no_reset && cd_object.exists)
|
|
++total_del_num;
|
|
}
|
|
|
|
if (idx >= keys.size())
|
|
return false;
|
|
|
|
*key = this->prefixKey(encodeKey(cd_id, keys[idx], transid));
|
|
++idx;
|
|
return true;
|
|
};
|
|
|
|
++total_del_ops;
|
|
|
|
bool ret = backend->del(key_next_fun, false);
|
|
|
|
|
|
#ifdef ASSERT_CHECK_DELETED
|
|
backend->sync(false, false);
|
|
for (const std::string& key : keys)
|
|
{
|
|
assert(backend->check_deleted(prefixKey(encodeKey(cd_id, key, transid)), std::string()));
|
|
}
|
|
#endif
|
|
|
|
if(ret)
|
|
{
|
|
sync();
|
|
|
|
IDatabase* db = getDatabase();
|
|
{
|
|
KvStoreDao dao(db);
|
|
DBScopedSynchronous synchronous(dao.getDb());
|
|
DBScopedWriteTransaction scoped_transaction(dao.getDb());
|
|
|
|
for (size_t i = 0; i < keys.size(); ++i)
|
|
{
|
|
if(cd_id==0)
|
|
dao.addDelMarkerObject(transid, keys[i]);
|
|
else
|
|
dao.addDelMarkerObjectCd(cd_id, transid, keys[i]);
|
|
}
|
|
}
|
|
db->freeMemory();
|
|
|
|
objects_total_size -= total_del_size;
|
|
objects_total_num -= total_del_num;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
size_t KvStoreFrontend::max_del_size()
|
|
{
|
|
return backend->max_del_size();
|
|
}
|
|
|
|
int64 KvStoreFrontend::generation_inc(int64 inc )
|
|
{
|
|
IScopedLock lock(gen_mutex.get());
|
|
|
|
int64 ret = current_generation;
|
|
current_generation += inc;
|
|
|
|
if (current_generation - last_persisted_generation >= generation_skip_max ||
|
|
current_generation - last_update_generation >= generation_skip_update)
|
|
{
|
|
last_update_generation = current_generation;
|
|
|
|
bool update_ok = false;
|
|
IDatabase* db = getDatabase();
|
|
{
|
|
KvStoreDao dao(getDatabase());
|
|
DBScopedSynchronous synchronous(getDatabase());
|
|
|
|
if (current_generation - last_persisted_generation >= generation_skip_max)
|
|
{
|
|
dao.updateGeneration(current_generation);
|
|
update_ok = true;
|
|
}
|
|
else
|
|
{
|
|
IQuery* q = dao.getUpdateGenerationQuery();
|
|
if (q == nullptr)
|
|
{
|
|
q = db->Prepare("UPDATE clouddrive_generation SET generation=?", false);
|
|
dao.setUpdateGenerationQuery(q);
|
|
}
|
|
q->Bind(current_generation);
|
|
if (q->Write(10))
|
|
{
|
|
update_ok = true;
|
|
}
|
|
q->Reset();
|
|
}
|
|
}
|
|
|
|
db->freeMemory();
|
|
|
|
if (update_ok)
|
|
{
|
|
last_persisted_generation = current_generation;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int64 KvStoreFrontend::get_generation(int64 cd_id)
|
|
{
|
|
KvStoreDao dao(getDatabase());
|
|
return dao.getGenerationCd(cd_id).value;
|
|
}
|
|
|
|
std::string KvStoreFrontend::get_stats()
|
|
{
|
|
#if 0
|
|
Server->Log("Getting stats...", LL_INFO);
|
|
|
|
int64 size;
|
|
IDatabase* db = getDatabase();
|
|
{
|
|
KvStoreDao dao(getDatabase());
|
|
size = dao.getSize();
|
|
}
|
|
db->freeMemory();
|
|
|
|
if (size != objects_total_size)
|
|
{
|
|
Server->Log("Calculated size from db != running size. DB=" + convert(size) + " running objects_total_size=" + convert(objects_total_size)+" diff="+PrettyPrintBytes(abs(objects_total_size-size)), LL_ERROR);
|
|
}
|
|
|
|
return "{\"ok\": true, \"size\": "+convert(size)+"}";
|
|
#else
|
|
return "{\"ok\": true, \"size\": " + convert(objects_total_size) + ", \"init_complete\": "+convert(objects_init_complete)+" }";
|
|
#endif
|
|
}
|
|
|
|
bool KvStoreFrontend::sync()
|
|
{
|
|
IScopedWriteLock write_lock(nullptr);
|
|
return sync_lock(write_lock);
|
|
}
|
|
|
|
bool KvStoreFrontend::sync_db()
|
|
{
|
|
if (backend->has_transactions())
|
|
{
|
|
put_db_worker.flush();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool KvStoreFrontend::sync_lock(IScopedWriteLock& lock)
|
|
{
|
|
bool res = db_wal_file->Sync();
|
|
if (!res)
|
|
{
|
|
Server->Log("Syncing wal file " + db_wal_file->getFilename() + " failed. " + os_last_error_str(), LL_ERROR);
|
|
}
|
|
lock.relock(put_shared_mutex.get());
|
|
{
|
|
IScopedLock lock(unsynced_keys_mutex.get());
|
|
std::swap(curr_unsynced_keys, other_unsynced_keys);
|
|
}
|
|
if (backend->has_transactions())
|
|
{
|
|
put_db_worker.flush();
|
|
}
|
|
if (!backend->sync(false, false))
|
|
{
|
|
Server->Log("Syncing backend failed", LL_ERROR);
|
|
res = false;
|
|
}
|
|
{
|
|
IScopedLock lock(unsynced_keys_mutex.get());
|
|
other_unsynced_keys->clear();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool KvStoreFrontend::is_put_sync()
|
|
{
|
|
return backend->is_put_sync();
|
|
}
|
|
|
|
namespace
|
|
{
|
|
class InterfaceLock
|
|
{
|
|
public:
|
|
InterfaceLock(std::string lock_file)
|
|
{
|
|
#ifndef _WIN32
|
|
fd = open(lock_file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
|
|
|
|
if (fd != -1)
|
|
{
|
|
flock(fd, LOCK_EX);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
~InterfaceLock()
|
|
{
|
|
#ifndef _WIN32
|
|
if (fd != -1)
|
|
{
|
|
flock(fd, LOCK_UN);
|
|
close(fd);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
int fd;
|
|
};
|
|
|
|
class ImportCallback : public IKvStoreBackend::IListCallback
|
|
{
|
|
public:
|
|
struct STransaction
|
|
{
|
|
int64 cd_id;
|
|
int64 transid;
|
|
|
|
STransaction(int64 cd_id, int64 transid)
|
|
: cd_id(cd_id), transid(transid) {}
|
|
|
|
bool operator<(const STransaction& other) const
|
|
{
|
|
return std::make_pair(cd_id, transid)
|
|
< std::make_pair(other.cd_id, other.transid);
|
|
}
|
|
};
|
|
|
|
std::map<STransaction, bool> transactions;
|
|
|
|
ImportCallback(KvStoreDao& dao, bool has_last_modified, int64 total_num_keys)
|
|
: dao(dao), last_modified_key_time(0), n_imported_keys(0), num_noprefix_files(0), num_prefix_files(0),
|
|
has_last_modified(has_last_modified), total_num_keys(total_num_keys)
|
|
{
|
|
|
|
}
|
|
|
|
~ImportCallback()
|
|
{
|
|
Server->Log("Enumerated a total of " + convert(n_imported_keys) + " objects in bucket.", LL_INFO);
|
|
}
|
|
|
|
virtual bool onlineItem(const std::string& key, const std::string& md5sum, int64 size, int64 last_modified)
|
|
{
|
|
if (key == "cd_magic_file"
|
|
|| key == "cd_num_file" )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if(key.find('_')==std::string::npos)
|
|
{
|
|
std::string err = "Key "+key+" has unknown format";
|
|
Server->Log(err, LL_ERROR);
|
|
}
|
|
std::string key_part = key;
|
|
if (key.find('/') != std::string::npos)
|
|
{
|
|
size_t lslash = key.find_last_of('/');
|
|
if (key.size() <= lslash + 1)
|
|
{
|
|
std::string err = "Key " + key + " has unknown format (2)";
|
|
Server->Log(err, LL_ERROR);
|
|
return true;
|
|
}
|
|
key_part = key.substr(lslash + 1);
|
|
|
|
if (num_noprefix_files>0
|
|
&& num_prefix_files == 0)
|
|
{
|
|
Server->Log("Key " + key + " has prefix while there are keys with no prefix", LL_WARNING);
|
|
}
|
|
++num_prefix_files;
|
|
}
|
|
else
|
|
{
|
|
if (num_prefix_files>0
|
|
&& num_noprefix_files==0)
|
|
{
|
|
Server->Log("Key " + key+" has no prefix while there are keys with prefix", LL_WARNING);
|
|
}
|
|
++num_noprefix_files;
|
|
}
|
|
int64 transid = os_atoi64(getuntil("_", key_part).c_str());
|
|
|
|
std::string hexkey = getafter("_", key_part);
|
|
|
|
int64 cd_id = 0;
|
|
if (hexkey.find("_") != std::string::npos)
|
|
{
|
|
cd_id = transid;
|
|
transid = os_atoi64(getuntil("_", hexkey).c_str());
|
|
hexkey = getafter("_", key_part);
|
|
}
|
|
|
|
if (hexkey == bytesToHex("test_file"))
|
|
{
|
|
if (key_part != key)
|
|
{
|
|
--num_prefix_files;
|
|
}
|
|
else
|
|
{
|
|
--num_noprefix_files;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto trans_it = transactions.find(STransaction(cd_id, transid));
|
|
|
|
if(trans_it==transactions.end())
|
|
{
|
|
if(cd_id==0)
|
|
dao.insertTransaction(transid);
|
|
else
|
|
dao.insertTransactionCd(transid, cd_id);
|
|
|
|
trans_it = transactions.insert(std::make_pair(STransaction(cd_id, transid), false)).first;
|
|
}
|
|
|
|
if(hexkey=="inactive")
|
|
{
|
|
Server->Log("Imported inactive transaction " + convert(transid)+" cd_id "+convert(cd_id), LL_INFO);
|
|
if(cd_id==0)
|
|
dao.setTransactionActive(0, transid);
|
|
else
|
|
dao.setTransactionActiveCd(0, cd_id, transid);
|
|
}
|
|
else if(hexkey=="complete")
|
|
{
|
|
Server->Log("Imported complete transaction " + convert(transid) + " cd_id " + convert(cd_id), LL_INFO);
|
|
if(cd_id==0)
|
|
dao.setTransactionComplete(2, transid);
|
|
else
|
|
dao.setTransactionCompleteCd(2, cd_id, transid);
|
|
}
|
|
else if (hexkey == "finalized")
|
|
{
|
|
Server->Log("Imported finalized transaction " + convert(transid) + " cd_id " + convert(cd_id), LL_INFO);
|
|
if(cd_id==0)
|
|
dao.setTransactionComplete(1, transid);
|
|
else
|
|
dao.setTransactionCompleteCd(1, cd_id, transid);
|
|
}
|
|
else if(IsHex(hexkey))
|
|
{
|
|
if (last_modified_key_time == 0)
|
|
{
|
|
Server->Log("Import from backend started...");
|
|
}
|
|
|
|
std::string bkey = hexToBytes(hexkey);
|
|
|
|
if (cd_id == 0)
|
|
{
|
|
if (!trans_it->second &&
|
|
dao.getLowerTransidObject(bkey, transid).exists)
|
|
{
|
|
trans_it->second = true;
|
|
}
|
|
|
|
if (has_last_modified)
|
|
{
|
|
dao.addObject2(transid, bkey, md5sum, size, last_modified);
|
|
}
|
|
else
|
|
{
|
|
dao.addObject(transid, bkey, md5sum, size);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!trans_it->second &&
|
|
dao.getLowerTransidObjectCd(cd_id, bkey, transid).exists)
|
|
{
|
|
trans_it->second = true;
|
|
}
|
|
|
|
if (has_last_modified)
|
|
{
|
|
dao.addObject2Cd(cd_id, transid, bkey, md5sum, size, last_modified);
|
|
}
|
|
else
|
|
{
|
|
dao.addObjectCd(cd_id, transid, bkey, md5sum, size);
|
|
}
|
|
}
|
|
|
|
|
|
if(last_modified>last_modified_key_time
|
|
&& key.find('_') != std::string::npos)
|
|
{
|
|
last_modified_key = key;
|
|
last_modified_key_time = last_modified;
|
|
last_modified_md5sum = md5sum;
|
|
}
|
|
|
|
++n_imported_keys;
|
|
|
|
if (n_imported_keys % 100==0)
|
|
{
|
|
Server->Log("Enumerated "+convert(n_imported_keys)+" objects in bucket...", LL_INFO);
|
|
|
|
std::string import_state = "{\"state\": \"importing\", \n"
|
|
"\"curr_keys\": " + convert(n_imported_keys) + ", \n"
|
|
"\"total_keys\": " + convert(total_num_keys + 1000) + "}\n";
|
|
|
|
setMountStatus(import_state);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Unknown object (not hex encoded) " + hexkey + " key "+key, LL_WARNING);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string lastModifiedKey()
|
|
{
|
|
return last_modified_key;
|
|
}
|
|
|
|
std::string lastModifiedMd5sum()
|
|
{
|
|
return last_modified_md5sum;
|
|
}
|
|
|
|
size_t hasPrefixFiles()
|
|
{
|
|
return num_prefix_files;
|
|
}
|
|
|
|
size_t hasNoPrefixFiles()
|
|
{
|
|
return num_noprefix_files;
|
|
}
|
|
|
|
private:
|
|
KvStoreDao& dao;
|
|
|
|
std::string last_modified_key;
|
|
std::string last_modified_md5sum;
|
|
int64 last_modified_key_time;
|
|
int64 n_imported_keys;
|
|
size_t num_prefix_files;
|
|
size_t num_noprefix_files;
|
|
bool has_last_modified;
|
|
int64 total_num_keys;
|
|
};
|
|
}
|
|
|
|
bool KvStoreFrontend::importFromBackend( KvStoreDao& dao )
|
|
{
|
|
IFsFile* tmp_f = Server->openTemporaryFile();
|
|
if (tmp_f == nullptr)
|
|
{
|
|
std::string err = "Error opening temporary file (num file). " + os_last_error_str();
|
|
Server->Log(err, LL_ERROR);
|
|
return false;
|
|
}
|
|
ScopedDeleteFile tmp_f_del(tmp_f);
|
|
std::string ret_md5sum;
|
|
unsigned int get_status;
|
|
int64 import_total_num_files = -1;
|
|
if (backend->get("cd_num_file", std::string(), IKvStoreBackend::GetDecrypted,
|
|
false, tmp_f, ret_md5sum, get_status))
|
|
{
|
|
if (tmp_f->Size() < 1 * 1024 * 1024)
|
|
{
|
|
std::string num_data = tmp_f->Read(0LL, static_cast<_u32>(tmp_f->Size()));
|
|
|
|
CRData rnumdata(num_data.data(), num_data.size());
|
|
|
|
char ver = -1;
|
|
if (!rnumdata.getChar(&ver)
|
|
|| ver != 0)
|
|
{
|
|
Server->Log("cd_num_file version wrong. ver="+convert((int)ver), LL_ERROR);
|
|
}
|
|
else if(!rnumdata.getVarInt(&import_total_num_files))
|
|
{
|
|
Server->Log("Error reading total num files", LL_ERROR);
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Import total num files: " + convert(import_total_num_files), LL_INFO);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Server->Log("cd_num_file too large ("+PrettyPrintBytes(tmp_f->Size())+")", LL_WARNING);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Error retrieving cd_num_file", LL_WARNING);
|
|
}
|
|
|
|
Server->Log("Importing from backend...", LL_INFO);
|
|
|
|
DBScopedSynchronous synchronous(dao.getDb());
|
|
DBScopedWriteTransaction scoped_transaction(dao.getDb());
|
|
|
|
ImportCallback import_callback(dao, has_last_modified, import_total_num_files);
|
|
|
|
if(!backend->list(&import_callback))
|
|
{
|
|
Server->Log("Error enumerating objects in bucket", LL_ERROR);
|
|
setMountStatus("{\"state\": \"error\", \"err\": \"Error enumerating objects in bucket\"}");
|
|
return false;
|
|
}
|
|
|
|
setMountStatus("{\"state\": \"get_generation\"}");
|
|
|
|
if(!import_callback.lastModifiedKey().empty())
|
|
{
|
|
Server->Log("Retrieving last object to get current generation...", LL_INFO);
|
|
|
|
IFsFile* temp_file = Server->openTemporaryFile();
|
|
ScopedDeleteFile delete_last_f(temp_file);
|
|
|
|
std::string md5sum_ret;
|
|
unsigned int get_status;
|
|
bool b = backend->get(import_callback.lastModifiedKey(), import_callback.lastModifiedMd5sum(), 0, true, temp_file, md5sum_ret, get_status);
|
|
|
|
if(!b)
|
|
{
|
|
Server->Log("Error retrieving last modified file", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
int64 generation=0;
|
|
|
|
if(!read_generation(temp_file, 0, generation))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
generation += 100;
|
|
|
|
dao.insertGeneration(generation);
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Found no object. Setting generation to zero...", LL_INFO);
|
|
|
|
dao.insertGeneration(0);
|
|
}
|
|
|
|
|
|
bool has_prefix = true;
|
|
if (import_callback.hasNoPrefixFiles() > import_callback.hasPrefixFiles())
|
|
{
|
|
Server->Log("More files with no prefix (" + convert(import_callback.hasNoPrefixFiles()) + ") then with prefix (" + convert(import_callback.hasPrefixFiles()) + "). Using no prefix", LL_WARNING);
|
|
has_prefix = false;
|
|
}
|
|
if (has_prefix)
|
|
{
|
|
dao.setMiscValue("has_prefix", "1");
|
|
}
|
|
|
|
for (auto trans_it : import_callback.transactions)
|
|
{
|
|
if(trans_it.second)
|
|
dao.addTask(TASK_REMOVE_OLD_OBJECTS, trans_it.first.transid,
|
|
Server->getTimeSeconds(), trans_it.first.cd_id);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int64 KvStoreFrontend::get_uploaded_bytes()
|
|
{
|
|
return backend->get_uploaded_bytes();
|
|
}
|
|
|
|
int64 KvStoreFrontend::get_downloaded_bytes()
|
|
{
|
|
return backend->get_downloaded_bytes();
|
|
}
|
|
|
|
IDatabase* KvStoreFrontend::getDatabase()
|
|
{
|
|
return Server->getDatabase(Server->getThreadID(), CLOUDDRIVE_DB);
|
|
}
|
|
|
|
std::string KvStoreFrontend::encodeKey( const std::string& key, int64 transid )
|
|
{
|
|
return std::to_string(transid) + "_" + bytesToHex(reinterpret_cast<const unsigned char*>(key.c_str()), key.size());
|
|
}
|
|
|
|
std::string KvStoreFrontend::encodeKey(int64 cd_id, const std::string& key, int64 transid)
|
|
{
|
|
return encodeKeyStatic(cd_id, key, transid);
|
|
}
|
|
|
|
std::string KvStoreFrontend::encodeKeyStatic(int64 cd_id, const std::string& key, int64 transid)
|
|
{
|
|
if (cd_id == 0)
|
|
return encodeKey(key, transid);
|
|
|
|
return std::to_string(cd_id) + "_" + std::to_string(transid) + "_" + bytesToHex(reinterpret_cast<const unsigned char*>(key.c_str()), key.size());
|
|
}
|
|
|
|
std::string KvStoreFrontend::prefixKey(const std::string & key)
|
|
{
|
|
if (!with_prefix)
|
|
return key;
|
|
|
|
std::string md5sum = Server->GenerateHexMD5(key);
|
|
return md5sum.substr(0, 3) + "/" + md5sum.substr(3, 2) + "/" + key;
|
|
}
|
|
|
|
IKvStoreBackend * KvStoreFrontend::getBackend()
|
|
{
|
|
return backend;
|
|
}
|
|
|
|
void KvStoreFrontend::start_scrub(ScrubAction action, const std::string & position)
|
|
{
|
|
IScopedLock lock(scrub_mutex.get());
|
|
|
|
if (scrub_worker != nullptr)
|
|
{
|
|
scrub_worker->quit();
|
|
THREADPOOL_TICKET t = scrub_worker->ticket;
|
|
lock.relock(nullptr);
|
|
Server->getThreadPool()->waitFor(t);
|
|
lock.relock(scrub_mutex.get());
|
|
if (scrub_worker != nullptr)
|
|
{
|
|
delete scrub_worker;
|
|
scrub_worker = nullptr;
|
|
}
|
|
}
|
|
|
|
scrub_worker = new ScrubWorker(action, backend, backend_mirror, this,
|
|
background_worker, position, has_last_modified);
|
|
scrub_worker->ticket = Server->getThreadPool()->execute(scrub_worker, strScrubAction(action));
|
|
}
|
|
|
|
std::string KvStoreFrontend::scrub_position()
|
|
{
|
|
IScopedLock lock(scrub_mutex.get());
|
|
|
|
if (scrub_worker != nullptr)
|
|
{
|
|
return scrub_worker->get_position();
|
|
}
|
|
return std::string();
|
|
}
|
|
|
|
std::string KvStoreFrontend::scrub_stats()
|
|
{
|
|
IScopedLock lock(scrub_mutex.get());
|
|
|
|
if (scrub_worker != nullptr)
|
|
{
|
|
return scrub_worker->stats();
|
|
}
|
|
|
|
return std::string();
|
|
}
|
|
|
|
void KvStoreFrontend::stop_scrub()
|
|
{
|
|
IScopedLock lock(scrub_mutex.get());
|
|
if (scrub_worker != nullptr)
|
|
{
|
|
scrub_worker->quit();
|
|
if (Server->getThreadPool()->waitFor(scrub_worker->ticket, 1000))
|
|
{
|
|
delete scrub_worker;
|
|
scrub_worker = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool KvStoreFrontend::is_background_worker_enabled()
|
|
{
|
|
return !background_worker.is_paused();
|
|
}
|
|
|
|
bool KvStoreFrontend::is_background_worker_running()
|
|
{
|
|
return background_worker.is_runnnig();
|
|
}
|
|
|
|
void KvStoreFrontend::enable_background_worker(bool b)
|
|
{
|
|
background_worker.set_pause(!b);
|
|
}
|
|
|
|
void KvStoreFrontend::set_background_worker_result_fn(const std::string& result_fn)
|
|
{
|
|
background_worker.set_result_fn(result_fn);
|
|
}
|
|
|
|
bool KvStoreFrontend::start_background_worker()
|
|
{
|
|
if (!has_background_task())
|
|
return false;
|
|
|
|
size_t nwork = background_worker.get_nwork();
|
|
|
|
enable_background_worker(true);
|
|
|
|
while (nwork == background_worker.get_nwork())
|
|
{
|
|
Server->wait(100);
|
|
|
|
if (!has_background_task() &&
|
|
nwork == background_worker.get_nwork())
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool KvStoreFrontend::has_background_task()
|
|
{
|
|
IDatabase* db = getDatabase();
|
|
KvStoreDao dao(db);
|
|
return dao.getTask(Server->getTimeSeconds() - task_delay).exists;
|
|
}
|
|
|
|
void KvStoreFrontend::start_scrub_sync_test1()
|
|
{
|
|
scrub_sync_test1 = true;
|
|
}
|
|
|
|
void KvStoreFrontend::start_scrub_sync_test2()
|
|
{
|
|
scrub_sync_test2 = true;
|
|
}
|
|
|
|
bool KvStoreFrontend::reupload(int64 transid_start, int64 transid_stop, IKvStoreBackend * old_backend)
|
|
{
|
|
if (transid_stop == 0)
|
|
{
|
|
transid_stop = LLONG_MAX;
|
|
}
|
|
IDatabase* db = getDatabase();
|
|
KvStoreDao dao(db);
|
|
IQuery* q_iter = db->Prepare("SELECT trans_id, tkey, md5sum FROM clouddrive_objects WHERE trans_id>=? AND trans_id<=? AND size!=-1");
|
|
q_iter->Bind(transid_start);
|
|
q_iter->Bind(transid_stop);
|
|
std::vector<KvStoreDao::CdIterObject> new_objects;
|
|
ScopedDatabaseCursor cur(q_iter->Cursor());
|
|
bool ret = true;
|
|
db_single_result res;
|
|
while (cur.next(res))
|
|
{
|
|
IFsFile* tmp_f = Server->openTemporaryFile();
|
|
if (tmp_f == nullptr)
|
|
{
|
|
Server->Log("Error opening temporary file. " + os_last_error_str(), LL_ERROR);
|
|
ret = false;
|
|
break;
|
|
}
|
|
ScopedDeleteFile tmp_f_delete(tmp_f);
|
|
|
|
std::string key = res["tkey"];
|
|
int64 transid = watoi64(res["trans_id"]);
|
|
std::string fkey = prefixKey(encodeKey(key, transid));
|
|
std::string md5sum = res["md5sum"];
|
|
|
|
std::string ret_md5sum;
|
|
unsigned int get_status;
|
|
int flags = IKvStoreBackend::GetDecrypted;
|
|
if (!old_backend->get(fkey, md5sum, flags, false, tmp_f, ret_md5sum, get_status))
|
|
{
|
|
if (!backend->get(fkey, md5sum, flags, false, tmp_f, ret_md5sum, get_status))
|
|
{
|
|
Server->Log("Error reading " + fkey + ". status=" + convert(get_status), LL_ERROR);
|
|
ret = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int64 size = 0;
|
|
std::string new_md5sum;
|
|
if (backend->put(fkey, tmp_f, 0, false, ret_md5sum, size))
|
|
{
|
|
Server->Log("Successfully rewritten " + fkey);
|
|
KvStoreDao::CdIterObject new_obj;
|
|
new_obj.md5sum = ret_md5sum;
|
|
new_obj.size = size;
|
|
new_obj.tkey = key;
|
|
new_obj.trans_id = transid;
|
|
new_objects.push_back(new_obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const KvStoreDao::CdIterObject& obj : new_objects)
|
|
{
|
|
dao.updateObjectSearch(obj.md5sum, obj.size, obj.tkey, obj.trans_id);
|
|
}
|
|
db->freeMemory();
|
|
return ret;
|
|
}
|
|
|
|
std::string KvStoreFrontend::meminfo()
|
|
{
|
|
std::string ret;
|
|
{
|
|
IScopedLock lock(unsynced_keys_mutex.get());
|
|
ret += "##KvStoreFrontend:\n";
|
|
ret += " unsynced_keys_a: " + convert(unsynced_keys_a.size()) + " * " + PrettyPrintBytes(sizeof(std::string)+sizeof(SUnsyncedKey)) + " = "+PrettyPrintBytes(unsynced_keys_a.size()*(sizeof(std::string) + sizeof(SUnsyncedKey)))+"\n";
|
|
ret += " unsynced_keys_b: " + convert(unsynced_keys_b.size()) + " * " + PrettyPrintBytes(sizeof(std::string)+ sizeof(SUnsyncedKey)) + " = " + PrettyPrintBytes(unsynced_keys_b.size()*(sizeof(std::string) + sizeof(SUnsyncedKey)))+ "\n";
|
|
}
|
|
ret += put_db_worker.meminfo();
|
|
{
|
|
IScopedLock lock(scrub_mutex.get());
|
|
if (scrub_worker != nullptr)
|
|
{
|
|
ret += scrub_worker->meminfo();
|
|
}
|
|
}
|
|
ret += backend->meminfo();
|
|
ret += background_worker.meminfo();
|
|
return ret;
|
|
}
|
|
|
|
void KvStoreFrontend::retry_all_deletion()
|
|
{
|
|
IDatabase* db = getDatabase();
|
|
KvStoreDao dao(db);
|
|
dao.insertAllDeletionTasks();
|
|
}
|
|
|
|
int64 KvStoreFrontend::get_total_balance_ops()
|
|
{
|
|
return total_balance_ops;
|
|
}
|
|
|
|
void KvStoreFrontend::incr_total_del_ops()
|
|
{
|
|
++total_del_ops;
|
|
}
|
|
|
|
int64 KvStoreFrontend::get_total_del_ops()
|
|
{
|
|
return total_del_ops;
|
|
}
|
|
|
|
bool KvStoreFrontend::has_backend_key(const std::string & key,
|
|
std::string& md5sum, bool update_md5sum)
|
|
{
|
|
if (key == "cd_magic_file")
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (key.find('_') == std::string::npos)
|
|
{
|
|
std::string err = "Key " + key + " has unknown format (has_backend_key)";
|
|
Server->Log(err, LL_ERROR);
|
|
}
|
|
std::string key_part = key;
|
|
if (key.find('/') != std::string::npos)
|
|
{
|
|
size_t lslash = key.find_last_of('/');
|
|
if (key.size() <= lslash + 1)
|
|
{
|
|
std::string err = "Key " + key + " has unknown format (2) (has_backend_key)";
|
|
Server->Log(err, LL_ERROR);
|
|
return true;
|
|
}
|
|
key_part = key.substr(lslash + 1);
|
|
}
|
|
|
|
int64 transid = os_atoi64(getuntil("_", key_part).c_str());
|
|
|
|
std::string hexkey = getafter("_", key_part);
|
|
|
|
if (hexkey == bytesToHex("test_file"))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
IDatabase* db = getDatabase();
|
|
KvStoreDao dao(db);
|
|
|
|
if (hexkey == "inactive")
|
|
{
|
|
KvStoreDao::STransactionProperties props = dao.getTransactionProperties(transid);
|
|
return props.exists
|
|
&& props.active == 0;
|
|
}
|
|
else if (hexkey == "complete")
|
|
{
|
|
KvStoreDao::STransactionProperties props = dao.getTransactionProperties(transid);
|
|
return props.exists
|
|
&& props.completed == 2;
|
|
}
|
|
else if (hexkey == "finalized")
|
|
{
|
|
KvStoreDao::STransactionProperties props = dao.getTransactionProperties(transid);
|
|
return props.exists
|
|
&& props.completed == 1;
|
|
}
|
|
else if (IsHex(hexkey))
|
|
{
|
|
std::string bkey = hexToBytes(hexkey);
|
|
KvStoreDao::CdObject obj = dao.getObjectInTransid(transid, bkey);
|
|
if (obj.exists)
|
|
{
|
|
if (update_md5sum)
|
|
dao.updateObjectMd5sum(md5sum, transid, bkey);
|
|
md5sum = obj.md5sum;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Unknown object (not hex encoded) " + hexkey + " key " + key+" (has_backend_key)", LL_WARNING);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
class BackendDelWorker : public IThread
|
|
{
|
|
IPipe* wpipe;
|
|
bool background_queue;
|
|
const std::vector<IKvStoreBackend::key_next_fun_t>& key_next_funs;
|
|
const std::vector<IKvStoreBackend::locinfo_next_fun_t>& locinfo_next_funs;
|
|
IKvStoreBackend* backend;
|
|
volatile bool& res;
|
|
public:
|
|
BackendDelWorker(IPipe* wpipe, const std::vector<IKvStoreBackend::key_next_fun_t>& key_next_funs,
|
|
const std::vector<IKvStoreBackend::locinfo_next_fun_t>& locinfo_next_funs,
|
|
IKvStoreBackend* backend, bool background_queue, volatile bool& res)
|
|
: wpipe(wpipe),
|
|
backend(backend), res(res),
|
|
background_queue(background_queue),
|
|
key_next_funs(key_next_funs),
|
|
locinfo_next_funs(locinfo_next_funs)
|
|
{
|
|
|
|
}
|
|
|
|
void operator()()
|
|
{
|
|
while (true)
|
|
{
|
|
std::string msg;
|
|
if (wpipe->Read(&msg) == 0)
|
|
{
|
|
wpipe->Write(std::string());
|
|
break;
|
|
}
|
|
|
|
CRData data(msg.data(), msg.size());
|
|
int64 idx;
|
|
bool b = data.getVarInt(&idx);
|
|
assert(b);
|
|
|
|
if (locinfo_next_funs[idx] != nullptr)
|
|
{
|
|
if (!backend->del(key_next_funs[idx],
|
|
locinfo_next_funs[idx], background_queue))
|
|
{
|
|
res = false;
|
|
}
|
|
locinfo_next_funs[idx](IKvStoreBackend::key_next_action_t::clear, nullptr);
|
|
}
|
|
else
|
|
{
|
|
size_t retry_n = 0;
|
|
bool del_res;
|
|
while (!(del_res = backend->del(key_next_funs[idx], background_queue))
|
|
&& retry_n<8)
|
|
{
|
|
Server->Log("Deleting objects failed (2). Retrying... Try "+convert(retry_n), LL_WARNING);
|
|
retryWait("BackendDel", ++retry_n);
|
|
key_next_funs[idx](IKvStoreBackend::key_next_action_t::reset, nullptr);
|
|
}
|
|
|
|
if (!del_res)
|
|
res = false;
|
|
}
|
|
|
|
key_next_funs[idx](IKvStoreBackend::key_next_action_t::clear, nullptr);
|
|
}
|
|
|
|
delete this;
|
|
}
|
|
};
|
|
}
|
|
|
|
void KvStoreFrontend::submit_del_post_flush()
|
|
{
|
|
KvStoreDao dao(getDatabase());
|
|
dao.setMiscValue("submit_del_done", "1");
|
|
}
|
|
|
|
bool KvStoreFrontend::backend_del_parallel(const std::vector<IKvStoreBackend::key_next_fun_t>& key_next_funs,
|
|
const std::vector<IKvStoreBackend::locinfo_next_fun_t>& locinfo_next_funs,
|
|
bool background_queue)
|
|
{
|
|
if (backend->num_del_parallel() <= 1)
|
|
{
|
|
for (size_t i = 0; i < key_next_funs.size(); ++i)
|
|
{
|
|
if (locinfo_next_funs[i] != nullptr)
|
|
{
|
|
if (!backend->del(key_next_funs[i], locinfo_next_funs[i], background_queue))
|
|
{
|
|
return false;
|
|
}
|
|
locinfo_next_funs[i](IKvStoreBackend::key_next_action_t::clear, nullptr);
|
|
}
|
|
else
|
|
{
|
|
size_t retry_n = 0;
|
|
bool res;
|
|
while (!(res=backend->del(key_next_funs[i], background_queue))
|
|
&& retry_n<8)
|
|
{
|
|
Server->Log("Deleting objects failed. Retrying... Try " + convert(retry_n), LL_WARNING);
|
|
retryWait("BackendDel", ++retry_n);
|
|
key_next_funs[i](IKvStoreBackend::key_next_action_t::reset, nullptr);
|
|
}
|
|
if (!res)
|
|
return false;
|
|
}
|
|
key_next_funs[i](IKvStoreBackend::key_next_action_t::clear, nullptr);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
std::unique_ptr<IPipe> wpipe(Server->createMemoryPipe());
|
|
for (size_t i = 0; i < key_next_funs.size(); ++i)
|
|
{
|
|
CWData wd;
|
|
wd.addVarInt(i);
|
|
wpipe->Write(wd.getDataPtr(), wd.getDataSize());
|
|
}
|
|
|
|
wpipe->Write(std::string());
|
|
|
|
std::vector<THREADPOOL_TICKET> tt;
|
|
volatile bool res = true;
|
|
for (size_t i = 0; i < backend->num_del_parallel(); ++i)
|
|
{
|
|
tt.push_back(Server->getThreadPool()->execute(
|
|
new BackendDelWorker(wpipe.get(), key_next_funs,
|
|
locinfo_next_funs, backend, background_queue, res),
|
|
"bdel worker"));
|
|
}
|
|
|
|
Server->getThreadPool()->waitFor(tt);
|
|
|
|
return res;
|
|
}
|
|
|
|
void KvStoreFrontend::add_last_modified_column()
|
|
{
|
|
db_results res = getDatabase()->Read("SELECT sql FROM sqlite_master WHERE type='table' AND name='clouddrive_objects'");
|
|
if (!res.empty())
|
|
{
|
|
std::string sql = res[0]["sql"];
|
|
if (sql.find("last_modified") == std::string::npos)
|
|
{
|
|
res = getDatabase()->Read("SELECT * FROM clouddrive_objects LIMIT 1");
|
|
if (res.empty())
|
|
{
|
|
getDatabase()->Write("ALTER TABLE clouddrive_objects ADD last_modified INTEGER");
|
|
has_last_modified = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
has_last_modified = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void KvStoreFrontend::add_cd_id_tasks_column()
|
|
{
|
|
db_results res = getDatabase()->Read("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'");
|
|
if (!res.empty())
|
|
{
|
|
std::string sql = res[0]["sql"];
|
|
if (sql.find("cd_id") == std::string::npos)
|
|
{
|
|
getDatabase()->Write("ALTER TABLE tasks ADD cd_id INTEGER DEFAULT 0");
|
|
}
|
|
}
|
|
}
|
|
|
|
void KvStoreFrontend::add_created_column()
|
|
{
|
|
db_results res = getDatabase()->Read("SELECT sql FROM sqlite_master WHERE type='table' AND name='tasks'");
|
|
if (!res.empty())
|
|
{
|
|
std::string sql = res[0]["sql"];
|
|
if (sql.find("created") == std::string::npos)
|
|
{
|
|
getDatabase()->Write("ALTER TABLE tasks ADD created INTEGER");
|
|
}
|
|
}
|
|
}
|
|
|
|
void KvStoreFrontend::add_mirrored_column()
|
|
{
|
|
db_results res = getDatabase()->Read("SELECT sql FROM sqlite_master WHERE type='table' AND name='clouddrive_objects'");
|
|
if (!res.empty())
|
|
{
|
|
std::string sql = res[0]["sql"];
|
|
if (sql.find("mirrored") == std::string::npos)
|
|
{
|
|
getDatabase()->Write("ALTER TABLE clouddrive_objects ADD mirrored INTEGER DEFAULT 0");
|
|
getDatabase()->Write("ALTER TABLE clouddrive_transactions ADD mirrored INTEGER DEFAULT 0");
|
|
}
|
|
}
|
|
|
|
getDatabase()->Write("CREATE INDEX IF NOT EXISTS clouddrive_objects_mirrored "
|
|
"ON clouddrive_objects(mirrored) WHERE mirrored=0");
|
|
}
|
|
|
|
int64 KvStoreFrontend::get_backend_mirror_del_log_wpos()
|
|
{
|
|
IScopedLock lock(mirror_del_log_mutex.get());
|
|
return backend_mirror_del_log_wpos;
|
|
}
|
|
|
|
bool KvStoreFrontend::update_total_num(int64 num)
|
|
{
|
|
IFsFile* new_f = Server->openTemporaryFile();
|
|
if (new_f == nullptr)
|
|
{
|
|
std::string err = "Error opening temporary file (update_total_num). " + os_last_error_str();
|
|
Server->Log(err, LL_ERROR);
|
|
return false;
|
|
}
|
|
ScopedDeleteFile new_f_del(new_f);
|
|
|
|
CWData wdata;
|
|
wdata.addChar(0);
|
|
wdata.addVarInt(num);
|
|
|
|
if (new_f->Write(wdata.getDataPtr(), wdata.getDataSize()) != wdata.getDataSize())
|
|
{
|
|
std::string err = "Error writing to cd_num_file file. " + os_last_error_str();
|
|
Server->Log(err, LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
std::string md5sum;
|
|
int64 size;
|
|
if (!backend->put("cd_num_file", new_f, 0, true, md5sum, size))
|
|
{
|
|
std::string err = "Error uploading cd_num_file file.";
|
|
Server->Log(err, LL_ERROR);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Updated total num with new num " + convert(num), LL_INFO);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const unsigned int del_mirror_magic = 0x4A00B231;
|
|
|
|
bool KvStoreFrontend::log_del_mirror(const std::string & fn)
|
|
{
|
|
if (backend_mirror_del_log.get() == nullptr)
|
|
{
|
|
Server->Log("Log del mirror disabled for " + fn, LL_INFO);
|
|
return true;
|
|
}
|
|
|
|
IScopedLock lock(mirror_del_log_mutex.get());
|
|
|
|
CWData wdata;
|
|
|
|
|
|
wdata.addUInt(del_mirror_magic);
|
|
wdata.addUShort(0);
|
|
wdata.addString2(fn);
|
|
unsigned short data_len = static_cast<unsigned short>(wdata.getDataSize());
|
|
memcpy(wdata.getDataPtr() + sizeof(del_mirror_magic), &data_len, sizeof(data_len));
|
|
|
|
_u32 w = backend_mirror_del_log->Write(backend_mirror_del_log_wpos, wdata.getDataPtr(), wdata.getDataSize());
|
|
if (w != wdata.getDataSize())
|
|
{
|
|
Server->Log("Logging for cloud mirror failed at pos "+convert(backend_mirror_del_log_wpos)
|
|
+" size "+convert((size_t)wdata.getDataSize())+". " + os_last_error_str(), LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
Server->Log("Log del mirror " + fn, LL_INFO);
|
|
|
|
backend_mirror_del_log_wpos += w;
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string KvStoreFrontend::next_log_del_mirror_item()
|
|
{
|
|
char rs[sizeof(unsigned int) + sizeof(unsigned short)];
|
|
|
|
while (true)
|
|
{
|
|
if (backend_mirror_del_log_rpos >= backend_mirror_del_log_wpos)
|
|
return std::string();
|
|
|
|
if (backend_mirror_del_log->Read(backend_mirror_del_log_rpos, rs, sizeof(rs)) != sizeof(rs))
|
|
return std::string();
|
|
|
|
unsigned int magic;
|
|
memcpy(&magic, rs, sizeof(magic));
|
|
if (magic == del_mirror_magic)
|
|
{
|
|
backend_mirror_del_log_rpos += sizeof(rs);
|
|
break;
|
|
}
|
|
++backend_mirror_del_log_rpos;
|
|
}
|
|
|
|
|
|
unsigned short rsize;
|
|
memcpy(&rsize, &rs[sizeof(unsigned int)], sizeof(rsize));
|
|
|
|
unsigned short msg_size = rsize - sizeof(unsigned int) - sizeof(unsigned short);
|
|
|
|
std::string msg_buf;
|
|
msg_buf.resize(msg_size);
|
|
|
|
if (backend_mirror_del_log->Read(backend_mirror_del_log_rpos, &msg_buf[0], msg_size) != msg_size)
|
|
return std::string();
|
|
|
|
backend_mirror_del_log_rpos += msg_size;
|
|
|
|
CRData rdata(msg_buf.data(), msg_buf.size());
|
|
|
|
std::string ret;
|
|
if (!rdata.getStr2(&ret))
|
|
return std::string();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void KvStoreFrontend::set_backend_mirror_del_log_rpos(int64 p)
|
|
{
|
|
backend_mirror_del_log_rpos = p;
|
|
}
|
|
|
|
int64 KvStoreFrontend::get_backend_mirror_del_log_rpos()
|
|
{
|
|
return backend_mirror_del_log_rpos;
|
|
}
|
|
|
|
void KvStoreFrontend::stop_defrag()
|
|
{
|
|
#ifdef HAS_LOCAL_BACKEND
|
|
KvStoreBackendLocal* bl = dynamic_cast<KvStoreBackendLocal*>(backend);
|
|
if (bl != nullptr)
|
|
{
|
|
Server->Log("Stopping defrag...");
|
|
bl->stop_defrag();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool KvStoreFrontend::set_all_mirrored(bool b)
|
|
{
|
|
IDatabase* db = getDatabase();
|
|
DBScopedWriteTransaction db_trans(db);
|
|
|
|
add_mirrored_column();
|
|
Server->deleteFile(ExtractFilePath(db_path) + os_file_sep() + "mirror_del.log");
|
|
db->Write(std::string("UPDATE clouddrive_objects SET mirrored=") + (b?"1":"0"));
|
|
db->Write(std::string("UPDATE clouddrive_transactions SET mirrored=") + (b ? "1" : "0"));
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string KvStoreFrontend::mirror_stats()
|
|
{
|
|
return "{ \"state\": " + convert(mirror_state) + ",\n"
|
|
+ "\"curr_pos\": " + convert(mirror_curr_pos) + ",\n"
|
|
+ "\"curr_total\": " + convert(mirror_curr_total) + ",\n"
|
|
+ "\"items\": " + convert(mirror_items) + " }\n";
|
|
}
|
|
|
|
bool KvStoreFrontend::start_defrag(const std::string& settings)
|
|
{
|
|
#ifdef HAS_LOCAL_BACKEND
|
|
KvStoreBackendLocal* backendl = dynamic_cast<KvStoreBackendLocal*>(backend);
|
|
if (backendl != nullptr)
|
|
{
|
|
if (backendl->start_defrag(settings))
|
|
{
|
|
return sync();
|
|
}
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void KvStoreFrontend::operator()()
|
|
{
|
|
KvStoreDao dao(getDatabase());
|
|
KvStoreDao::SSize sizes = dao.getSize();
|
|
if (sizes.exists)
|
|
{
|
|
objects_total_size += sizes.size;
|
|
objects_total_num += sizes.count;
|
|
}
|
|
objects_init_complete = true;
|
|
}
|
|
|
|
bool KvStoreFrontend::fast_write_retry()
|
|
{
|
|
return backend->fast_write_retry();
|
|
}
|
|
|
|
bool KvStoreFrontend::submit_del_cd(int64 cd_id, IHasKeyCallback* p_has_key_callback, int64 ctransid, bool& need_flush)
|
|
{
|
|
need_flush = false;
|
|
|
|
if (!allow_import)
|
|
return true;
|
|
|
|
KvStoreDao dao(getDatabase());
|
|
|
|
if (dao.getMiscValue("submit_del_done").value == "1")
|
|
return true;
|
|
|
|
DBScopedWriteTransaction db_trans(getDatabase());
|
|
|
|
IQuery* q_iter;
|
|
if (cd_id == 0)
|
|
q_iter = getDatabase()->Prepare("SELECT trans_id, tkey, md5sum, size FROM clouddrive_objects WHERE size!=-1 ORDER BY tkey ASC, trans_id ASC", false);
|
|
else
|
|
q_iter = getDatabase()->Prepare("SELECT trans_id, tkey, md5sum, size FROM clouddrive_objects_cd WHERE cd_id=" + convert(cd_id) + " AND size!=-1 ORDER BY tkey ASC, trans_id ASC", false);
|
|
|
|
IDatabaseCursor* cur = q_iter->Cursor();
|
|
|
|
bool ret = true;
|
|
db_single_result res;
|
|
|
|
while (cur->next(res))
|
|
{
|
|
if (!p_has_key_callback->hasKey(res["tkey"]))
|
|
{
|
|
std::string tkey = res["tkey"];
|
|
int64 transid = watoi64(res["trans_id"]);
|
|
|
|
Server->Log("Deleting " + bytesToHex(tkey) + " transid " + convert(transid)
|
|
+ " in transid "+convert(ctransid)+" (bitmap says no data is at this location)", LL_INFO);
|
|
|
|
if (cd_id == 0)
|
|
dao.addDelMarkerObject(ctransid, tkey);
|
|
else
|
|
dao.addDelMarkerObjectCd(cd_id, ctransid, tkey);
|
|
}
|
|
}
|
|
|
|
if (cur->has_error())
|
|
ret = false;
|
|
|
|
getDatabase()->destroyQuery(q_iter);
|
|
|
|
if (ret)
|
|
need_flush = true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool KvStoreFrontend::submit_del(IHasKeyCallback* p_has_key_callback, int64 ctransid, bool& need_flush)
|
|
{
|
|
return submit_del_cd(0, p_has_key_callback, ctransid, need_flush);
|
|
}
|
|
|
|
KvStoreFrontend::BackgroundWorker::BackgroundWorker(IKvStoreBackend* backend, KvStoreFrontend* frontend,
|
|
bool manual_run, bool multi_trans_delete)
|
|
: do_quit(false), backend(backend), frontend(frontend), pause_mutex(Server->createMutex()), pause(false), paused(false),
|
|
object_collector_size(0), object_collector_size_uncompressed(0), manual_run(manual_run), running(false),
|
|
nwork(0), startup_finished(false), scrub_pause(false), mirror_pause(false), multi_trans_delete(multi_trans_delete)
|
|
{
|
|
|
|
}
|
|
|
|
void KvStoreFrontend::BackgroundWorker::operator()()
|
|
{
|
|
IDatabase* db=Server->getDatabase(Server->getThreadID(), CLOUDDRIVE_DB);
|
|
KvStoreDao dao(db);
|
|
|
|
{
|
|
size_t n_wait = 0;
|
|
IScopedLock lock(pause_mutex.get());
|
|
pause = true;
|
|
while (!do_quit
|
|
&& n_wait < 15*60
|
|
&& pause
|
|
&& !scrub_pause
|
|
&& !mirror_pause)
|
|
{
|
|
lock.relock(nullptr);
|
|
Server->wait(1000);
|
|
lock.relock(pause_mutex.get());
|
|
++n_wait;
|
|
}
|
|
|
|
if (!pause_set)
|
|
pause = false;
|
|
}
|
|
|
|
|
|
while(!do_quit)
|
|
{
|
|
KvStoreDao::Task task;
|
|
IScopedLock pause_lock(pause_mutex.get());
|
|
if (!startup_finished)
|
|
{
|
|
task = dao.getActiveTask();
|
|
|
|
if (!task.exists)
|
|
startup_finished = true;
|
|
}
|
|
|
|
if (startup_finished)
|
|
{
|
|
if (!pause)
|
|
{
|
|
pause_lock.relock(nullptr);
|
|
task = dao.getTask(Server->getTimeSeconds() - frontend->task_delay);
|
|
}
|
|
else
|
|
{
|
|
pause_lock.relock(nullptr);
|
|
task.exists = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pause_lock.relock(nullptr);
|
|
}
|
|
|
|
if(task.exists)
|
|
{
|
|
std::vector<KvStoreDao::Task> tasks;
|
|
|
|
{
|
|
IScopedLock pause_lock2(pause_mutex.get());
|
|
++nwork;
|
|
}
|
|
|
|
dao.setTaskActive(task.id);
|
|
|
|
running = true;
|
|
|
|
size_t retry_n = 0;
|
|
switch (task.task_id)
|
|
{
|
|
case TASK_REMOVE_OLD_OBJECTS:
|
|
{
|
|
std::vector<int64> trans_ids;
|
|
trans_ids.push_back(task.trans_id);
|
|
|
|
if (multi_trans_delete)
|
|
{
|
|
tasks = dao.getTasks(Server->getTimeSeconds() - frontend->task_delay,
|
|
task.task_id, task.cd_id);
|
|
|
|
for (auto& ctask : tasks)
|
|
{
|
|
if (ctask.id != task.id)
|
|
{
|
|
trans_ids.push_back(ctask.trans_id);
|
|
dao.setTaskActive(ctask.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (!removeOldObjects(dao, trans_ids, task.cd_id))
|
|
{
|
|
retryWait("RemoveOldObjects", ++retry_n);
|
|
}
|
|
} break;
|
|
case TASK_REMOVE_TRANSACTION:
|
|
while (!removeTransaction(dao, task.trans_id, task.cd_id))
|
|
{
|
|
retryWait("RemoveTransaction", ++retry_n);
|
|
}
|
|
break;
|
|
default:
|
|
Server->Log("Unknown task "+convert(task.id)+" with id "+convert(task.task_id), LL_ERROR);
|
|
break;
|
|
}
|
|
|
|
dao.removeTask(task.id);
|
|
for (auto& ctask : tasks)
|
|
{
|
|
if (ctask.id != task.id)
|
|
dao.removeTask(ctask.id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (running)
|
|
{
|
|
dao.getDb()->freeMemory();
|
|
Server->mallocFlushTcache();
|
|
}
|
|
|
|
running = false;
|
|
|
|
if (manual_run)
|
|
pause = true;
|
|
|
|
{
|
|
IScopedLock lock(pause_mutex.get());
|
|
while (pause || scrub_pause ||
|
|
mirror_pause)
|
|
{
|
|
paused = true;
|
|
lock.relock(nullptr);
|
|
Server->wait(1000);
|
|
lock.relock(pause_mutex.get());
|
|
}
|
|
paused = false;
|
|
}
|
|
|
|
Server->wait(1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
void KvStoreFrontend::BackgroundWorker::quit()
|
|
{
|
|
do_quit=true;
|
|
}
|
|
|
|
std::string KvStoreFrontend::BackgroundWorker::meminfo()
|
|
{
|
|
std::string ret;
|
|
ret += "##KvStoreFrontend::BackgroundWorker:\n";
|
|
ret += " object_collector_size: " + convert(object_collector_size) + " = " + PrettyPrintBytes(object_collector_size) + "\n";
|
|
ret += " object_collector_size_uncompressed: " + convert(object_collector_size_uncompressed) + " = " + PrettyPrintBytes(object_collector_size_uncompressed) + "\n";
|
|
return ret;
|
|
}
|
|
|
|
bool KvStoreFrontend::BackgroundWorker::removeOldObjects(KvStoreDao& dao,
|
|
const std::vector<int64>& p_trans_ids, int64 cd_id)
|
|
{
|
|
std::string trans_ids_str;
|
|
for (int64 trans_id : p_trans_ids)
|
|
trans_ids_str += ", " + std::to_string(trans_id);
|
|
|
|
if (!trans_ids_str.empty())
|
|
trans_ids_str.erase(0, 2);
|
|
|
|
Server->Log("Worker: Removing old objects of transaction "+trans_ids_str, LL_INFO);
|
|
DBScopedSynchronous synchronous(dao.getDb());
|
|
|
|
std::vector<int64> trans_ids;
|
|
for (int64 trans_id : p_trans_ids)
|
|
{
|
|
KvStoreDao::STransactionProperties trans_props = cd_id == 0 ?
|
|
dao.getTransactionProperties(trans_id) :
|
|
dao.getTransactionPropertiesCd(cd_id, trans_id);
|
|
if (!trans_props.exists)
|
|
{
|
|
Server->Log("Worker: Transaction " + convert(trans_id) + " does not exist", LL_WARNING);
|
|
continue;
|
|
}
|
|
if (trans_props.active != 1)
|
|
{
|
|
Server->Log("Worker: Transaction " + convert(trans_id) + " has active = " + convert(trans_props.active), LL_WARNING);
|
|
continue;
|
|
}
|
|
trans_ids.push_back(trans_id);
|
|
}
|
|
|
|
if (trans_ids.empty())
|
|
return true;
|
|
|
|
size_t max_del_size = backend->max_del_size();
|
|
|
|
if (max_del_size > 1)
|
|
--max_del_size;
|
|
|
|
size_t stride_size = (std::min)(max_del_size, static_cast<size_t>(10000));
|
|
|
|
ObjectCollector object_collector(stride_size, frontend, -1, frontend->backend_mirror!=nullptr, cd_id);
|
|
|
|
int64 delete_size = 0;
|
|
int64 delete_num = 0;
|
|
|
|
bool has_object = false;
|
|
if (backend->del_with_location_info())
|
|
{
|
|
/*std::vector<KvStoreDao::CdDelObjectMd5> deletable_objects;
|
|
if (backend->ordered_del())
|
|
{
|
|
deletable_objects = dao.getDeletableObjectsMd5Ordered(trans_id);
|
|
}
|
|
else
|
|
{
|
|
deletable_objects = dao.getDeletableObjectsMd5(trans_id);
|
|
}*/
|
|
|
|
std::string mirrored = "";
|
|
if (frontend->backend_mirror != nullptr)
|
|
mirrored = ", mirrored";
|
|
|
|
std::string transid_filter = "(";
|
|
|
|
for (int64 transid : trans_ids)
|
|
{
|
|
if (transid_filter.size() > 1)
|
|
transid_filter += " OR ";
|
|
|
|
transid_filter += " (trans_id<"+ std::to_string(transid)+
|
|
" AND tkey IN (SELECT tkey FROM clouddrive_objects WHERE trans_id="+std::to_string(transid)+")) ";
|
|
}
|
|
transid_filter += ")";
|
|
|
|
IQuery *q;
|
|
if (backend->ordered_del())
|
|
{
|
|
std::string sql_str = cd_id == 0 ?
|
|
"SELECT trans_id, tkey, md5sum, size" + mirrored + " FROM clouddrive_objects WHERE "+transid_filter+" AND size != -1 ORDER BY trans_id ASC, tkey ASC" :
|
|
"SELECT trans_id, tkey, md5sum, size" + mirrored + " FROM clouddrive_objects_cd WHERE cd_id=? AND "+transid_filter+"AND size != -1 ORDER BY trans_id ASC, tkey ASC";
|
|
|
|
q = dao.getDb()->Prepare(sql_str, false);
|
|
}
|
|
else
|
|
{
|
|
std::string sql_str = cd_id == 0 ?
|
|
"SELECT trans_id, tkey, md5sum, size" + mirrored + " FROM clouddrive_objects WHERE "+transid_filter+" AND size != -1 ORDER BY tkey ASC" :
|
|
"SELECT trans_id, tkey, md5sum, size" + mirrored + " FROM clouddrive_objects_cd WHERE cd_id=? AND "+transid_filter+" AND size != -1 ORDER BY tkey ASC";
|
|
|
|
q = dao.getDb()->Prepare(sql_str, false);
|
|
}
|
|
|
|
if (cd_id != 0)
|
|
q->Bind(cd_id);
|
|
|
|
{
|
|
ScopedDatabaseCursor cursor(q->Cursor());
|
|
|
|
db_single_result res;
|
|
while(cursor.next(res))
|
|
{
|
|
KvStoreDao::CdDelObjectMd5 deletable_object;
|
|
deletable_object.trans_id = watoi64(res["trans_id"]);
|
|
deletable_object.tkey = res["tkey"];
|
|
deletable_object.md5sum = res["md5sum"];
|
|
|
|
bool mirrored = false;
|
|
if (frontend->backend_mirror != nullptr)
|
|
{
|
|
mirrored = watoi(res["mirrored"]) > 0;
|
|
}
|
|
|
|
int64 osize = watoi64(res["size"]);
|
|
if (osize > 0)
|
|
delete_size += osize;
|
|
|
|
++delete_num;
|
|
|
|
Server->Log("Deleting object (1) " + bytesToHex(reinterpret_cast<const unsigned char*>(deletable_object.tkey.c_str()), deletable_object.tkey.size()) + " transid " + convert(deletable_object.trans_id)+" cd_id "+convert(cd_id), LL_INFO);
|
|
|
|
object_collector.add(
|
|
deletable_object.trans_id,
|
|
deletable_object.tkey,
|
|
get_locinfo(deletable_object.md5sum),
|
|
mirrored);
|
|
|
|
has_object = true;
|
|
}
|
|
}
|
|
|
|
dao.getDb()->destroyQuery(q);
|
|
}
|
|
else
|
|
{
|
|
/*std::vector<KvStoreDao::CdDelObject> deletable_objects;
|
|
if (backend->ordered_del())
|
|
{
|
|
deletable_objects = dao.getDeletableObjectsOrdered(trans_id);
|
|
}
|
|
else
|
|
{
|
|
deletable_objects = dao.getDeletableObjects(trans_id);
|
|
}*/
|
|
|
|
std::string transid_filter = "(";
|
|
|
|
for (int64 transid : trans_ids)
|
|
{
|
|
if (transid_filter.size() > 1)
|
|
transid_filter += " OR ";
|
|
|
|
transid_filter += " (trans_id<" + std::to_string(transid) +
|
|
" AND tkey IN (SELECT tkey FROM clouddrive_objects WHERE trans_id=" + std::to_string(transid) + ")) ";
|
|
}
|
|
transid_filter += ")";
|
|
|
|
IQuery *q;
|
|
if (backend->ordered_del())
|
|
{
|
|
std::string sql_str = cd_id == 0 ?
|
|
"SELECT trans_id, tkey, size FROM clouddrive_objects WHERE "+transid_filter+" AND size != -1 ORDER BY trans_id ASC, tkey ASC" :
|
|
"SELECT trans_id, tkey, size FROM clouddrive_objects_cd WHERE cd_id=? AND "+transid_filter+" AND size != -1 ORDER BY trans_id ASC, tkey ASC";
|
|
q = dao.getDb()->Prepare(sql_str, false);
|
|
}
|
|
else
|
|
{
|
|
std::string sql_str = cd_id == 0 ?
|
|
"SELECT trans_id, tkey, size FROM clouddrive_objects WHERE "+transid_filter+" AND size != -1 ORDER BY tkey ASC" :
|
|
"SELECT trans_id, tkey, size FROM clouddrive_objects_cd WHERE cd_id=? AND "+transid_filter+" AND size != -1 ORDER BY tkey ASC";
|
|
q = dao.getDb()->Prepare(sql_str, false);
|
|
}
|
|
|
|
if (cd_id != 0)
|
|
q->Bind(cd_id);
|
|
|
|
{
|
|
ScopedDatabaseCursor cursor(q->Cursor());
|
|
db_single_result res;
|
|
while (cursor.next(res))
|
|
{
|
|
KvStoreDao::CdDelObject deletable_object;
|
|
deletable_object.trans_id = watoi64(res["trans_id"]);
|
|
deletable_object.tkey = res["tkey"];
|
|
|
|
int64 osize = watoi64(res["size"]);
|
|
if (osize > 0)
|
|
delete_size += osize;
|
|
|
|
++delete_num;
|
|
|
|
bool mirrored = false;
|
|
if (frontend->backend_mirror != nullptr)
|
|
{
|
|
mirrored = watoi(res["mirrored"]) > 0;
|
|
}
|
|
|
|
Server->Log("Deleting object (1) " + bytesToHex(reinterpret_cast<const unsigned char*>(deletable_object.tkey.c_str()), deletable_object.tkey.size()) + " transid " + convert(deletable_object.trans_id)+" cd_id "+convert(cd_id), LL_INFO);
|
|
|
|
object_collector.add(
|
|
deletable_object.trans_id,
|
|
deletable_object.tkey,
|
|
mirrored);
|
|
|
|
has_object = true;
|
|
}
|
|
}
|
|
|
|
dao.getDb()->destroyQuery(q);
|
|
}
|
|
|
|
bool ret=true;
|
|
|
|
if(has_object)
|
|
{
|
|
object_collector.finalize();
|
|
|
|
object_collector_size = object_collector.memsize;
|
|
object_collector_size_uncompressed = object_collector.uncompressed_memsize;
|
|
|
|
Server->Log("Object collector size " + PrettyPrintBytes(object_collector_size) + " uncompressed " + PrettyPrintBytes(object_collector_size_uncompressed), LL_INFO);
|
|
|
|
if (result_fn.empty())
|
|
{
|
|
ret = frontend->backend_del_parallel(object_collector.key_next_funs,
|
|
object_collector.locinfo_next_funs, true);
|
|
}
|
|
else
|
|
{
|
|
ret = object_collector.persist(TASK_REMOVE_OLD_OBJECTS, 0, 0, p_trans_ids, result_fn);
|
|
}
|
|
|
|
Server->Log("Backend del done res=" + convert(ret), LL_INFO);
|
|
}
|
|
|
|
if(ret)
|
|
{
|
|
if(has_object)
|
|
{
|
|
if (result_fn.empty()
|
|
&& !backend->sync(false, true))
|
|
{
|
|
object_collector_size = 0;
|
|
object_collector_size_uncompressed = 0;
|
|
return false;
|
|
}
|
|
|
|
#ifdef ASSERT_CHECK_DELETED
|
|
std::vector<KvStoreDao::CdDelObjectMd5> deletable_objects = cd_id==0 ?
|
|
dao.getDeletableObjectsMd5(trans_id) :
|
|
dao.getDeletableObjectsMd5Cd(cd_id, trans_id);
|
|
for (KvStoreDao::CdDelObjectMd5& obj : deletable_objects)
|
|
{
|
|
#ifdef ASSERT_CHECK_DELETED_NO_LOCINFO
|
|
obj.md5sum.clear();
|
|
#endif
|
|
assert(backend->check_deleted(frontend->prefixKey(frontend->encodeKey(cd_id, obj.tkey, obj.trans_id)),
|
|
get_locinfo(obj.md5sum)));
|
|
}
|
|
#endif
|
|
|
|
if (cd_id == 0)
|
|
{
|
|
for (int64 trans_id : trans_ids)
|
|
{
|
|
if (!dao.deleteDeletableObjects(trans_id))
|
|
{
|
|
object_collector_size = 0;
|
|
object_collector_size_uncompressed = 0;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int64 trans_id : trans_ids)
|
|
{
|
|
if (!dao.deleteDeletableObjectsCd(cd_id, trans_id))
|
|
{
|
|
object_collector_size = 0;
|
|
object_collector_size_uncompressed = 0;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int64 trans_id : trans_ids)
|
|
{
|
|
std::vector<int64> del_trans_ids = cd_id == 0 ?
|
|
dao.getDeletableTransactions(trans_id) :
|
|
dao.getDeletableTransactionsCd(cd_id, trans_id);
|
|
|
|
DBScopedWriteTransaction scoped_transaction(dao.getDb());
|
|
for (size_t i = 0; i < del_trans_ids.size(); ++i)
|
|
{
|
|
dao.addTask(TASK_REMOVE_TRANSACTION, del_trans_ids[i], Server->getTimeSeconds(), cd_id);
|
|
}
|
|
}
|
|
object_collector_size = 0;
|
|
object_collector_size_uncompressed = 0;
|
|
frontend->objects_total_size -= delete_size;
|
|
frontend->objects_total_num -= delete_num;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Delete request failed. Error removing old objects of transaction " + trans_ids_str, LL_ERROR);
|
|
object_collector_size = 0;
|
|
object_collector_size_uncompressed = 0;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool KvStoreFrontend::BackgroundWorker::removeTransaction(KvStoreDao& dao, int64 trans_id, int64 cd_id)
|
|
{
|
|
KvStoreDao::STransactionProperties trans_props = cd_id == 0 ?
|
|
dao.getTransactionProperties(trans_id) :
|
|
dao.getTransactionPropertiesCd(cd_id, trans_id);
|
|
|
|
if (!trans_props.exists)
|
|
{
|
|
Server->Log("Worker: Removing transaction " + convert(trans_id)+" cd_id "+convert(cd_id)+". Was already removed", LL_INFO);
|
|
return true;
|
|
}
|
|
|
|
Server->Log("Worker: Removing transaction "+convert(trans_id)+" cd_id "+convert(cd_id), LL_INFO);
|
|
DBScopedSynchronous synchronous(dao.getDb());
|
|
|
|
bool case1 = trans_props.active == 0
|
|
&& trans_props.completed == 0;
|
|
bool case2;
|
|
if(cd_id==0)
|
|
case2 = dao.getTransactionObjects(trans_id).empty()
|
|
&& trans_props.completed!=0;
|
|
else
|
|
case2 = dao.getTransactionObjectsCd(cd_id, trans_id).empty()
|
|
&& trans_props.completed != 0;
|
|
|
|
if ( !case1 && !case2)
|
|
{
|
|
Server->Log("Worker: Not deleting transaction " + convert(trans_id), LL_WARNING);
|
|
Server->Log("Worker: Transaction " + convert(trans_id) + " has active = " + convert(trans_props.active)+" completed = "+convert(trans_props.completed), LL_WARNING);
|
|
if(cd_id==0)
|
|
Server->Log("Worker: Transaction " + convert(trans_id) + " has "+convert(dao.getTransactionObjects(trans_id).size())+" objects", LL_WARNING);
|
|
else
|
|
Server->Log("Worker: Transaction " + convert(trans_id) + " has " + convert(dao.getTransactionObjectsCd(cd_id, trans_id).size()) + " objects", LL_WARNING);
|
|
return true;
|
|
}
|
|
|
|
size_t max_del_size = backend->max_del_size();
|
|
|
|
if (max_del_size > 1)
|
|
--max_del_size;
|
|
|
|
size_t stride_size = (std::min)(max_del_size, static_cast<size_t>(10000));
|
|
|
|
ObjectCollector object_collector(stride_size, frontend, trans_id, frontend->backend_mirror != nullptr, cd_id);
|
|
|
|
bool has_object = false;
|
|
int64 delete_size = 0;
|
|
int64 delete_num = 0;
|
|
|
|
std::string mirrored = "";
|
|
if (frontend->backend_mirror != nullptr)
|
|
mirrored = ", mirrored";
|
|
|
|
if (backend->del_with_location_info())
|
|
{
|
|
//std::vector<KvStoreDao::SDelItemMd5> trans_objects = dao.getTransactionObjectsMd5(trans_id);
|
|
|
|
std::string curr_q_str = cd_id == 0 ?
|
|
"SELECT tkey, md5sum, size" + mirrored + " FROM clouddrive_objects WHERE trans_id=? AND size != -1 ORDER BY tkey ASC" :
|
|
"SELECT tkey, md5sum, size" + mirrored + " FROM clouddrive_objects_cd WHERE cd_id=? AND trans_id=? AND size != -1 ORDER BY tkey ASC";
|
|
|
|
IQuery* q = dao.getDb()->Prepare(curr_q_str, false);;
|
|
if (cd_id != 0)
|
|
q->Bind(cd_id);
|
|
q->Bind(trans_id);
|
|
|
|
{
|
|
ScopedDatabaseCursor cursor(q->Cursor());
|
|
db_single_result res;
|
|
while (cursor.next(res))
|
|
{
|
|
KvStoreDao::SDelItemMd5 trans_object;
|
|
trans_object.tkey = res["tkey"];
|
|
trans_object.md5sum = res["md5sum"];
|
|
|
|
int64 osize = watoi64(res["size"]);
|
|
if (osize > 0)
|
|
delete_size += osize;
|
|
|
|
++delete_num;
|
|
|
|
bool mirrored = false;
|
|
if (frontend->backend_mirror != nullptr)
|
|
{
|
|
mirrored = watoi(res["mirrored"]) > 0;
|
|
}
|
|
|
|
Server->Log("Deleting object (2) " +
|
|
bytesToHex(reinterpret_cast<const unsigned char*>(trans_object.tkey.c_str()),
|
|
trans_object.tkey.size()) + " transid " + convert(trans_id)+" cd_id "+convert(cd_id), LL_INFO);
|
|
|
|
object_collector.add(-1,
|
|
trans_object.tkey,
|
|
get_locinfo(trans_object.md5sum),
|
|
mirrored);
|
|
|
|
has_object = true;
|
|
}
|
|
}
|
|
|
|
dao.getDb()->destroyQuery(q);
|
|
}
|
|
else
|
|
{
|
|
//std::vector<std::string> trans_objects = dao.getTransactionObjects(trans_id);
|
|
std::string curr_q_str = cd_id == 0 ?
|
|
"SELECT tkey, size" + mirrored + " FROM clouddrive_objects WHERE trans_id=? AND size != -1 ORDER BY tkey ASC" :
|
|
"SELECT tkey, size" + mirrored + " FROM clouddrive_objects WHERE cd_id=? AND trans_id=? AND size != -1 ORDER BY tkey ASC";
|
|
|
|
IQuery* q = dao.getDb()->Prepare(curr_q_str, false);;
|
|
if (cd_id != 0)
|
|
q->Bind(cd_id);
|
|
q->Bind(trans_id);
|
|
|
|
{
|
|
ScopedDatabaseCursor cursor(q->Cursor());
|
|
db_single_result res;
|
|
while (cursor.next(res))
|
|
{
|
|
std::string trans_object = res["tkey"];
|
|
int64 osize = watoi64(res["size"]);
|
|
if (osize > 0)
|
|
delete_size += osize;
|
|
|
|
++delete_num;
|
|
|
|
bool mirrored = false;
|
|
if (frontend->backend_mirror != nullptr)
|
|
{
|
|
mirrored = watoi(res["mirrored"]) > 0;
|
|
}
|
|
|
|
Server->Log("Deleting object (2) " + bytesToHex(reinterpret_cast<const unsigned char*>(trans_object.c_str()), trans_object.size()) + " transid " + convert(trans_id)+" cd_id "+convert(cd_id), LL_INFO);
|
|
object_collector.add(-1,
|
|
trans_object,
|
|
mirrored);
|
|
|
|
has_object = true;
|
|
}
|
|
}
|
|
|
|
dao.getDb()->destroyQuery(q);
|
|
}
|
|
|
|
bool ret=true;
|
|
|
|
KvStoreDao::STransactionProperties properties = cd_id == 0 ?
|
|
dao.getTransactionProperties(trans_id) :
|
|
dao.getTransactionPropertiesCd(cd_id, trans_id);
|
|
|
|
if (properties.exists)
|
|
{
|
|
has_object = properties.completed != 0 || properties.active == 0;
|
|
}
|
|
|
|
std::vector<std::string> add_backend_keys;
|
|
|
|
if(has_object)
|
|
{
|
|
object_collector.finalize();
|
|
|
|
object_collector_size = object_collector.memsize;
|
|
object_collector_size_uncompressed = object_collector.uncompressed_memsize;
|
|
|
|
Server->Log("Object collector size " + PrettyPrintBytes(object_collector_size) + " uncompressed " + PrettyPrintBytes(object_collector_size_uncompressed), LL_INFO);
|
|
|
|
if (result_fn.empty())
|
|
{
|
|
ret = frontend->backend_del_parallel(object_collector.key_next_funs, object_collector.locinfo_next_funs, true);
|
|
}
|
|
else
|
|
{
|
|
ret = object_collector.persist(TASK_REMOVE_TRANSACTION,
|
|
properties.completed, properties.active, { trans_id }, result_fn);
|
|
}
|
|
}
|
|
|
|
if(ret)
|
|
{
|
|
if (properties.completed != 0)
|
|
{
|
|
if (cd_id == 0)
|
|
add_backend_keys.push_back(frontend->prefixKey(convert(trans_id) + "_complete"));
|
|
else
|
|
add_backend_keys.push_back(frontend->prefixKey(convert(cd_id) + "_" + convert(trans_id) + "_complete"));
|
|
}
|
|
|
|
if (properties.active == 0)
|
|
{
|
|
if (cd_id == 0)
|
|
add_backend_keys.push_back(frontend->prefixKey(convert(trans_id) + "_inactive"));
|
|
else
|
|
add_backend_keys.push_back(frontend->prefixKey(convert(cd_id) + "_" + convert(trans_id) + "_inactive"));
|
|
}
|
|
|
|
if (!add_backend_keys.empty())
|
|
{
|
|
size_t idx = 0;
|
|
KvStoreFrontend* lfrontend = frontend;
|
|
backend->del([lfrontend, &add_backend_keys, &idx](IKvStoreBackend::key_next_action_t action, std::string* key) {
|
|
if (action == IKvStoreBackend::key_next_action_t::clear) {
|
|
add_backend_keys.clear();
|
|
assert(key == nullptr);
|
|
return true;
|
|
}
|
|
|
|
if (action == IKvStoreBackend::key_next_action_t::reset) {
|
|
idx = 0;
|
|
assert(key == nullptr);
|
|
return true;
|
|
}
|
|
|
|
assert(action == IKvStoreBackend::key_next_action_t::next);
|
|
|
|
if (idx >= add_backend_keys.size())
|
|
return false;
|
|
*key = add_backend_keys[idx];
|
|
lfrontend->log_del_mirror(*key);
|
|
++idx;
|
|
return true;
|
|
}, true);
|
|
}
|
|
|
|
|
|
if (has_object || add_backend_keys.empty())
|
|
{
|
|
if (!backend->sync(false, true))
|
|
{
|
|
object_collector_size = 0;
|
|
object_collector_size_uncompressed = 0;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(has_object)
|
|
{
|
|
#ifdef ASSERT_CHECK_DELETED
|
|
std::vector<KvStoreDao::SDelItemMd5> trans_objects = cd_id==0 ?
|
|
dao.getTransactionObjectsMd5(trans_id) :
|
|
dao.getTransactionObjectsMd5Cd(cd_id, trans_id);
|
|
for (KvStoreDao::SDelItemMd5& obj : trans_objects)
|
|
{
|
|
#ifdef ASSERT_CHECK_DELETED_NO_LOCINFO
|
|
obj.md5sum.clear();
|
|
#endif
|
|
assert(backend->check_deleted(frontend->prefixKey(frontend->encodeKey(cd_id, obj.tkey, trans_id)),
|
|
get_locinfo(obj.md5sum)));
|
|
}
|
|
for (std::string& key : add_backend_keys)
|
|
{
|
|
assert(backend->check_deleted(key, std::string()));
|
|
}
|
|
#endif
|
|
|
|
if (cd_id == 0)
|
|
{
|
|
if (!dao.deleteTransactionObjects(trans_id))
|
|
{
|
|
object_collector_size = 0;
|
|
object_collector_size_uncompressed = 0;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!dao.deleteTransactionObjectsCd(cd_id, trans_id))
|
|
{
|
|
object_collector_size = 0;
|
|
object_collector_size_uncompressed = 0;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
object_collector_size = 0;
|
|
object_collector_size_uncompressed = 0;
|
|
|
|
if (cd_id == 0)
|
|
{
|
|
if (!dao.deleteTransaction(trans_id))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!dao.deleteTransactionCd(cd_id, trans_id))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
frontend->objects_total_size -= delete_size;
|
|
frontend->objects_total_num -= delete_num;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
object_collector_size = 0;
|
|
object_collector_size_uncompressed = 0;
|
|
Server->Log("Delete request failed. Error deleting transaction " + convert(trans_id), LL_ERROR);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
KvStoreFrontend::ScrubWorker::ScrubWorker(ScrubAction scrub_action,
|
|
IKvStoreBackend* backend, IKvStoreBackend* backend_mirror,
|
|
KvStoreFrontend* frontend,
|
|
BackgroundWorker& background_worker, const std::string& position,
|
|
bool has_last_modified)
|
|
: scrub_action(scrub_action), complete_pc(-1), do_quit(false),
|
|
mutex(Server->createMutex()), frontend(frontend), backend(backend), backend_mirror(backend_mirror),
|
|
background_worker(background_worker), position(position),
|
|
has_last_modified(has_last_modified), curr_paused(true),
|
|
done_size(-1), total_size(-1)
|
|
{
|
|
|
|
}
|
|
|
|
namespace
|
|
{
|
|
void writeNewMd5sums(std::vector<KvStoreDao::CdIterObject>& new_md5sums,
|
|
KvStoreDao& dao)
|
|
{
|
|
if (new_md5sums.empty())
|
|
return;
|
|
|
|
DBScopedSynchronous synchronous(dao.getDb());
|
|
DBScopedWriteTransaction trans(dao.getDb());
|
|
for (auto& it : new_md5sums)
|
|
{
|
|
dao.updateObjectMd5sum(it.md5sum, it.trans_id, it.tkey);
|
|
}
|
|
new_md5sums.clear();
|
|
}
|
|
}
|
|
|
|
void KvStoreFrontend::ScrubWorker::operator()()
|
|
{
|
|
KvStoreDao dao(Server->getDatabase(Server->getThreadID(), CLOUDDRIVE_DB));
|
|
int64 scrub_starttime = Server->getTimeSeconds();
|
|
set_allow_defrag(false);
|
|
|
|
Server->Log("Waiting for background worker to finish startup...");
|
|
|
|
while (!background_worker.is_startup_finished())
|
|
{
|
|
Server->wait(10000);
|
|
}
|
|
|
|
Server->Log("Pausing background worker...");
|
|
|
|
background_worker.set_scrub_pause(true);
|
|
|
|
while (!background_worker.is_paused())
|
|
{
|
|
Server->wait(100);
|
|
background_worker.set_scrub_pause(true);
|
|
}
|
|
|
|
curr_paused = false;
|
|
|
|
Server->Log(strScrubActionC(scrub_action) + " started");
|
|
|
|
size_t scrub_oks = 0;
|
|
size_t scrub_errors = 0;
|
|
size_t scrub_repaired = 0;
|
|
|
|
bool with_last_modified = false;
|
|
if (has_last_modified)
|
|
{
|
|
if (position.empty())
|
|
{
|
|
if (dao.createObjectLastModifiedIdx())
|
|
{
|
|
with_last_modified = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
done_size = 0;
|
|
std::vector<KvStoreDao::CdIterObject> results;
|
|
if (position.empty())
|
|
{
|
|
total_size = dao.getSize().size;
|
|
if (total_size == 0)
|
|
{
|
|
if (with_last_modified)
|
|
{
|
|
dao.dropObjectLastModifiedIdx();
|
|
}
|
|
Server->Log(strScrubAction(scrub_action) + " finished. Nothing to "+ strScrubAction(scrub_action)+".");
|
|
background_worker.set_scrub_pause(false);
|
|
set_allow_defrag(true);
|
|
complete_pc = 101;
|
|
return;
|
|
}
|
|
|
|
if (with_last_modified)
|
|
{
|
|
results = dao.getInitialObjectsLM();
|
|
}
|
|
else
|
|
{
|
|
results = dao.getInitialObjects();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
done_size = 0;
|
|
int64 transid = 0;
|
|
std::string tkey;
|
|
int64 last_modified_start = -1;
|
|
|
|
#ifdef HAS_SCRUB
|
|
try
|
|
{
|
|
Json::Value root;
|
|
Json::Reader reader;
|
|
|
|
if (reader.parse(position, root, false))
|
|
{
|
|
done_size = root["done_size"].asInt64();
|
|
transid = root.get("transid", 0).asInt64();
|
|
tkey = hexToBytes(root.get("tkey", std::string()).asString());
|
|
last_modified_start = root.get("last_modified", -1).asInt64();
|
|
|
|
scrub_oks = root["ok"].asUInt64();
|
|
scrub_errors = root["error"].asUInt64();
|
|
scrub_repaired = root["repaired"].asUInt64();
|
|
scrub_starttime = root["starttime"].asInt64();
|
|
}
|
|
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
Server->Log(std::string("Error parsing position when continuing scrub") + e.what(), LL_ERROR);
|
|
}
|
|
#endif
|
|
|
|
if (transid == 0
|
|
&& tkey.empty()
|
|
&& has_last_modified)
|
|
{
|
|
if (dao.createObjectLastModifiedIdx())
|
|
{
|
|
with_last_modified = true;
|
|
}
|
|
}
|
|
|
|
if (with_last_modified)
|
|
{
|
|
if(last_modified_start<0)
|
|
total_size = dao.getSize().size;
|
|
else
|
|
total_size = dao.getSizePartialLMInit(last_modified_start);
|
|
}
|
|
else
|
|
{
|
|
if (transid == 0
|
|
&& tkey.empty())
|
|
total_size = dao.getSize().size;
|
|
else
|
|
total_size = dao.getSizePartial(tkey, transid);
|
|
}
|
|
|
|
if (total_size == 0)
|
|
{
|
|
if (with_last_modified)
|
|
{
|
|
dao.dropObjectLastModifiedIdx();
|
|
}
|
|
Server->Log(strScrubActionC(scrub_action) + " finished. Nothing to "+ strScrubAction(scrub_action)+".");
|
|
background_worker.set_scrub_pause(false);
|
|
set_allow_defrag(true);
|
|
complete_pc = 101;
|
|
return;
|
|
}
|
|
|
|
total_size += done_size;
|
|
|
|
if (done_size > total_size)
|
|
{
|
|
complete_pc = 100;
|
|
}
|
|
else
|
|
{
|
|
complete_pc = static_cast<int>((100 * done_size) / total_size);
|
|
}
|
|
|
|
Server->Log("Resuming at " + convert(complete_pc) + "%");
|
|
|
|
if (with_last_modified)
|
|
{
|
|
if (last_modified_start<0)
|
|
results = dao.getInitialObjectsLM();
|
|
else
|
|
results = dao.getIterObjectsLMInit(last_modified_start);
|
|
}
|
|
else
|
|
{
|
|
if (transid == 0
|
|
&& tkey.empty())
|
|
results = dao.getInitialObjects();
|
|
else
|
|
results = dao.getIterObjects(tkey, transid);
|
|
}
|
|
}
|
|
|
|
#ifdef HAS_LOCAL_BACKEND
|
|
int64 last_modified_stop = curr_last_modified();
|
|
#else
|
|
int64 last_modified_stop = 0;
|
|
#endif
|
|
|
|
int64 last_background_worker_time = Server->getTimeMS();
|
|
int64 last_size_update_time = Server->getTimeMS();
|
|
|
|
int last_complete_pc = -1;
|
|
std::vector<KvStoreDao::CdIterObject> new_md5sums;
|
|
std::vector<ScrubThread*> scrub_threads;
|
|
std::vector<THREADPOOL_TICKET> scrub_thread_tickets;
|
|
size_t n_scrub_threads = backend->num_scrub_parallel();
|
|
if (n_scrub_threads > os_get_num_cpus())
|
|
{
|
|
n_scrub_threads = os_get_num_cpus();
|
|
if (n_scrub_threads < 1)
|
|
n_scrub_threads = 1;
|
|
}
|
|
|
|
std::string scrub_thread_mult = getFile(ExtractFilePath(frontend->db_path) + os_file_sep() + "scrub_thread_mult");
|
|
if (!trim(scrub_thread_mult).empty())
|
|
{
|
|
n_scrub_threads = static_cast<size_t>(n_scrub_threads*atof(trim(scrub_thread_mult).c_str())+0.5);
|
|
}
|
|
scrub_threads.resize(n_scrub_threads);
|
|
scrub_thread_tickets.resize(n_scrub_threads);
|
|
|
|
ScrubQueue scrub_queue;
|
|
std::unique_ptr<IMutex> new_md5sums_mutex(Server->createMutex());
|
|
bool has_changes = false;
|
|
bool scrubbed_transid_markers = false;
|
|
|
|
IScopedLock lock(mutex.get());
|
|
|
|
do
|
|
{
|
|
has_changes = false;
|
|
|
|
scrub_queue.reset();
|
|
|
|
for (size_t i = 0; i < n_scrub_threads; ++i)
|
|
{
|
|
scrub_threads[i] = new ScrubThread(scrub_queue,
|
|
new_md5sums, new_md5sums_mutex.get(), has_changes,
|
|
done_size, with_last_modified, scrub_action,
|
|
backend, frontend, backend_mirror);
|
|
scrub_thread_tickets[i] = Server->getThreadPool()->execute(scrub_threads[i], strScrubAction(scrub_action) + convert(i));
|
|
}
|
|
|
|
for (size_t i = 0; i < results.size() && !do_quit; ++i)
|
|
{
|
|
KvStoreDao::CdIterObject& result = results[i];
|
|
|
|
assert(result.size != -1);
|
|
|
|
if (deleted_objects.find(std::make_pair(result.trans_id, result.tkey)) != deleted_objects.end())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
scrub_queue.add(result);
|
|
}
|
|
|
|
KvStoreDao::CdIterObject last_result;
|
|
bool has_last_result = false;
|
|
|
|
if (!results.empty()
|
|
&& !do_quit)
|
|
{
|
|
has_last_result = true;
|
|
last_result = results[results.size() - 1];
|
|
|
|
lock.relock(mutex.get());
|
|
deleted_objects.clear();
|
|
lock.relock(nullptr);
|
|
|
|
if (Server->getTimeMS() - last_size_update_time > 2 * 60 * 60 * 1000)
|
|
{
|
|
if (with_last_modified)
|
|
{
|
|
total_size = dao.getSizePartialLM(last_result.last_modified, last_modified_stop);
|
|
}
|
|
else
|
|
{
|
|
total_size = dao.getSizePartial(last_result.tkey, last_result.trans_id);
|
|
}
|
|
total_size += done_size;
|
|
last_size_update_time = Server->getTimeMS();
|
|
}
|
|
|
|
if (with_last_modified)
|
|
{
|
|
results = dao.getIterObjectsLM(last_result.last_modified, last_modified_stop);
|
|
}
|
|
else
|
|
{
|
|
results = dao.getIterObjects(last_result.tkey, last_result.trans_id);
|
|
}
|
|
}
|
|
|
|
if (!do_quit && results.empty()
|
|
&& !scrubbed_transid_markers)
|
|
{
|
|
std::vector<KvStoreDao::SCdTrans> transactions = dao.getTransactionIds();
|
|
|
|
for (const KvStoreDao::SCdTrans& transaction : transactions)
|
|
{
|
|
KvStoreDao::CdIterObject iter_obj;
|
|
iter_obj.size = 0;
|
|
iter_obj.last_modified = 0;
|
|
iter_obj.trans_id = -1;
|
|
iter_obj.tkey = convert(transaction.id);
|
|
if (transaction.completed == 2)
|
|
{
|
|
iter_obj.tkey += "_complete";
|
|
}
|
|
else if (transaction.completed == 1)
|
|
{
|
|
iter_obj.tkey += "_finalized";
|
|
}
|
|
else if(transaction.active==0)
|
|
{
|
|
iter_obj.tkey += "_inactive";
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
scrub_queue.add(iter_obj);
|
|
}
|
|
|
|
{
|
|
KvStoreDao::CdIterObject iter_obj;
|
|
iter_obj.size = 0;
|
|
iter_obj.last_modified = 0;
|
|
iter_obj.tkey = "cd_magic_file";
|
|
iter_obj.trans_id = -2;
|
|
scrub_queue.add(iter_obj);
|
|
}
|
|
|
|
scrubbed_transid_markers = true;
|
|
}
|
|
|
|
lock.relock(nullptr);
|
|
|
|
scrub_queue.stop();
|
|
|
|
Server->getThreadPool()->waitFor(scrub_thread_tickets);
|
|
|
|
scrub_errors += scrub_queue.scrub_errors;
|
|
scrub_oks += scrub_queue.scrub_oks;
|
|
scrub_repaired += scrub_queue.scrub_repaired;
|
|
|
|
if (scrub_queue.has_error())
|
|
{
|
|
if (with_last_modified)
|
|
{
|
|
dao.dropObjectLastModifiedIdx();
|
|
}
|
|
|
|
Server->Log("Error during scrub/balance. Stopping...", LL_ERROR);
|
|
background_worker.set_scrub_pause(false);
|
|
set_allow_defrag(true);
|
|
return;
|
|
}
|
|
|
|
if (has_changes)
|
|
{
|
|
backend->sync(scrub_sync_test1, false);
|
|
}
|
|
|
|
writeNewMd5sums(new_md5sums, dao);
|
|
|
|
if (has_changes)
|
|
{
|
|
backend->sync(scrub_sync_test2, false);
|
|
has_changes = false;
|
|
}
|
|
|
|
if (Server->getTimeMS() - last_background_worker_time > 10 * 60 * 1000)
|
|
{
|
|
curr_paused = true;
|
|
Server->Log("Unpausing background worker...");
|
|
|
|
int64 nwork = background_worker.get_nwork();
|
|
|
|
bool has_task = dao.getTask(Server->getTimeSeconds()-frontend->task_delay).exists;
|
|
|
|
background_worker.set_scrub_pause(false);
|
|
while (background_worker.is_paused()
|
|
&& !background_worker.get_pause())
|
|
{
|
|
Server->wait(100);
|
|
background_worker.set_scrub_pause(false);
|
|
}
|
|
|
|
if (has_task)
|
|
{
|
|
Server->wait(10000);
|
|
}
|
|
|
|
background_worker.set_scrub_pause(true);
|
|
Server->Log("Waiting for background worker...");
|
|
while (!background_worker.is_paused())
|
|
{
|
|
Server->wait(100);
|
|
background_worker.set_scrub_pause(true);
|
|
}
|
|
|
|
curr_paused = false;
|
|
|
|
last_background_worker_time = Server->getTimeMS();
|
|
|
|
if (nwork!=background_worker.get_nwork()
|
|
&& has_last_result)
|
|
{
|
|
lock.relock(mutex.get());
|
|
deleted_objects.clear();
|
|
lock.relock(nullptr);
|
|
|
|
if (with_last_modified)
|
|
{
|
|
results = dao.getIterObjectsLM(last_result.last_modified, last_modified_stop);
|
|
}
|
|
else
|
|
{
|
|
results = dao.getIterObjects(last_result.tkey, last_result.trans_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
lock.relock(mutex.get());
|
|
|
|
|
|
if (has_last_result)
|
|
{
|
|
if (with_last_modified)
|
|
{
|
|
position = "{ \"last_modified\": " + convert(last_result.last_modified);
|
|
}
|
|
else
|
|
{
|
|
position = "{ \"transid\": " + convert(last_result.trans_id) + ", \"tkey\": \"" + bytesToHex(last_result.tkey) + "\"";
|
|
}
|
|
|
|
position+= ", \"done_size\": " + convert(done_size) + ", \"complete_pc\": " + convert(complete_pc) +
|
|
+ ", \"ok\": " + convert(scrub_oks) + ", \"error\": " + convert(scrub_errors) + ", \"repaired\": " + convert(scrub_repaired) +
|
|
", \"starttime\": " + convert(scrub_starttime) + " }";
|
|
}
|
|
|
|
|
|
if (done_size > total_size)
|
|
{
|
|
complete_pc = 100;
|
|
}
|
|
else
|
|
{
|
|
complete_pc = static_cast<int>((100 * done_size) / total_size);
|
|
}
|
|
if (complete_pc != last_complete_pc)
|
|
{
|
|
Server->Log(strScrubActionC(scrub_action) + " " + convert(complete_pc) + "% done (ok: "+convert(scrub_oks)+" error: "+convert(scrub_errors)+" repaired: "+convert(scrub_repaired)+")");
|
|
}
|
|
last_complete_pc = complete_pc;
|
|
|
|
} while (!results.empty()
|
|
&& !do_quit);
|
|
|
|
if (has_changes)
|
|
{
|
|
backend->sync(scrub_sync_test1, false);
|
|
}
|
|
|
|
writeNewMd5sums(new_md5sums, dao);
|
|
|
|
if (has_changes)
|
|
{
|
|
backend->sync(scrub_sync_test2, false);
|
|
has_changes = false;
|
|
}
|
|
|
|
if (with_last_modified)
|
|
{
|
|
dao.dropObjectLastModifiedIdx();
|
|
}
|
|
|
|
complete_pc = do_quit ? 102 : 101;
|
|
Server->Log(strScrubActionC(scrub_action) + " finished"+(do_quit?" (quit)":"")+" (ok: "+convert(scrub_oks)+" error : "+convert(scrub_errors)+" repaired: "+convert(scrub_repaired)+")");
|
|
|
|
int scrub_finished_prio = LL_INFO;
|
|
|
|
if (scrub_errors > 0)
|
|
scrub_finished_prio = LL_ERROR;
|
|
|
|
str_map scrub_finished_extra;
|
|
scrub_finished_extra["ok"] = convert(scrub_oks);
|
|
scrub_finished_extra["error"] = convert(scrub_errors);
|
|
scrub_finished_extra["repaired"] = convert(scrub_repaired);
|
|
scrub_finished_extra["runtime_ms"] = convert((Server->getTimeSeconds() - scrub_starttime)*1000);
|
|
|
|
std::string scrub_finished_subj = strScrubActionC(scrub_action) + " finished";
|
|
|
|
if (scrub_errors > 0)
|
|
scrub_finished_subj += " with error";
|
|
if (scrub_errors > 1)
|
|
scrub_finished_subj += "s";
|
|
|
|
if (!do_quit)
|
|
{
|
|
addSystemEvent("scrub_finished", scrub_finished_subj,
|
|
strScrubActionC(scrub_action) + " finished after " + PrettyPrintTime((Server->getTimeSeconds() - scrub_starttime)*1000) +
|
|
" with " + convert(scrub_oks) +
|
|
" ok objects " + convert(scrub_errors) +
|
|
" objects with error and " + convert(scrub_repaired) + " repaired objects.", scrub_finished_prio, scrub_finished_extra);
|
|
}
|
|
|
|
background_worker.set_scrub_pause(false);
|
|
set_allow_defrag(true);
|
|
}
|
|
|
|
std::string KvStoreFrontend::ScrubWorker::meminfo() {
|
|
IScopedLock lock(mutex.get());
|
|
std::string ret = "##KvStoreFrontend::ScrubWorker:\n";
|
|
ret += " deleted_objects: " + convert(deleted_objects.size()) + " * " + PrettyPrintBytes(sizeof(std::string) + sizeof(int64)) + "\n";
|
|
return ret;
|
|
}
|
|
|
|
void KvStoreFrontend::ScrubWorker::set_allow_defrag(bool b)
|
|
{
|
|
#ifdef HAS_LOCAL_BACKEND
|
|
KvStoreBackendLocal* bl = dynamic_cast<KvStoreBackendLocal*>(backend);
|
|
if (bl != nullptr)
|
|
{
|
|
if(!b)
|
|
Server->Log("Stopping defrag...");
|
|
|
|
bl->set_allow_defrag(b);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
KvStoreFrontend::PutDbWorker::PutDbWorker(std::string db_path)
|
|
: mutex(Server->createMutex()),
|
|
add_cond(Server->createCondition()),
|
|
commit_cond(Server->createCondition()),
|
|
curr_items(&items_a), do_quit(false), do_synchronous(true),
|
|
db_wal_file(nullptr), with_last_modified(false),
|
|
db_path(db_path)
|
|
{
|
|
|
|
}
|
|
|
|
int64 KvStoreFrontend::PutDbWorker::add(int64 cd_id, int64 transid, const std::string & key, int64 generation)
|
|
{
|
|
IScopedLock lock(mutex.get());
|
|
wait_queue(lock);
|
|
int64 rowid = 0;
|
|
curr_items->push_back(SItem(cd_id, transid, key, generation, &rowid));
|
|
add_cond->notify_all();
|
|
|
|
while (rowid==0)
|
|
{
|
|
commit_cond->wait(&lock);
|
|
}
|
|
|
|
return rowid;
|
|
}
|
|
|
|
void KvStoreFrontend::PutDbWorker::add(int64 cd_id, int64 transid, const std::string & key,
|
|
const std::string & md5sum, int64 size, int64 last_modified, int64 generation)
|
|
{
|
|
IScopedLock lock(mutex.get());
|
|
if (curr_items->size()>10000)
|
|
{
|
|
add_cond->notify_all();
|
|
}
|
|
wait_queue(lock);
|
|
curr_items->push_back(SItem(cd_id, transid, key, md5sum, size, last_modified, generation));
|
|
}
|
|
|
|
void KvStoreFrontend::PutDbWorker::flush()
|
|
{
|
|
IScopedLock lock(mutex.get());
|
|
int64 rowid = 0;
|
|
curr_items->push_back(SItem(&rowid));
|
|
add_cond->notify_all();
|
|
|
|
while (rowid == 0)
|
|
{
|
|
commit_cond->wait(&lock);
|
|
}
|
|
}
|
|
|
|
void KvStoreFrontend::PutDbWorker::update(int64 cd_id, int64 objectid, int64 size,
|
|
const std::string & md5sum, int64 last_modified)
|
|
{
|
|
IScopedLock lock(mutex.get());
|
|
wait_queue(lock);
|
|
curr_items->push_back(SItem(cd_id, objectid, size, md5sum, last_modified));
|
|
add_cond->notify_all();
|
|
}
|
|
|
|
void KvStoreFrontend::PutDbWorker::quit()
|
|
{
|
|
IScopedLock lock(mutex.get());
|
|
do_quit = true;
|
|
add_cond->notify_all();
|
|
}
|
|
|
|
void KvStoreFrontend::PutDbWorker::operator()()
|
|
{
|
|
IDatabase* db = Server->getDatabase(Server->getThreadID(), CLOUDDRIVE_DB);
|
|
KvStoreDao dao(db);
|
|
DBScopedSynchronous synchronous(nullptr);
|
|
|
|
if (do_synchronous)
|
|
{
|
|
synchronous.reset(db);
|
|
}
|
|
|
|
std::vector<int64> rowids;
|
|
|
|
IScopedLock lock(mutex.get());
|
|
while (!do_quit)
|
|
{
|
|
while (curr_items->empty()
|
|
&& !do_quit)
|
|
{
|
|
add_cond->wait(&lock);
|
|
}
|
|
|
|
std::vector<SItem>* other_items = curr_items;
|
|
if (curr_items == &items_a)
|
|
{
|
|
curr_items = &items_b;
|
|
}
|
|
else
|
|
{
|
|
curr_items = &items_a;
|
|
}
|
|
rowids.resize(other_items->size());
|
|
|
|
lock.relock(nullptr);
|
|
|
|
bool needs_flush = false;
|
|
|
|
{
|
|
if (!dao.getDb()->BeginWriteTransaction())
|
|
{
|
|
worker_abort();
|
|
}
|
|
|
|
for (size_t i = 0; i < other_items->size(); ++i)
|
|
{
|
|
if ((*other_items)[i].type == EType::Add)
|
|
{
|
|
if ((*other_items)[i].cd_id!=0
|
|
&& (*other_items)[i].generation != 0)
|
|
{
|
|
if (!dao.updateGenerationCd((*other_items)[i].cd_id,
|
|
(*other_items)[i].generation))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
|
|
if ((*other_items)[i].cd_id == 0)
|
|
{
|
|
if (!dao.addPartialObject((*other_items)[i].transid, (*other_items)[i].key))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!dao.addPartialObjectCd((*other_items)[i].cd_id, (*other_items)[i].transid, (*other_items)[i].key))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
rowids[i] = db->getLastInsertID();
|
|
}
|
|
else if ((*other_items)[i].type == EType::Update)
|
|
{
|
|
if ((*other_items)[i].cd_id == 0)
|
|
{
|
|
if (with_last_modified)
|
|
{
|
|
if (!dao.updateObject2((*other_items)[i].key, (*other_items)[i].transid,
|
|
(*other_items)[i].objectid, (*other_items)[i].last_modified))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!dao.updateObject((*other_items)[i].key, (*other_items)[i].transid,
|
|
(*other_items)[i].objectid))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (with_last_modified)
|
|
{
|
|
if (!dao.updateObject2Cd((*other_items)[i].key, (*other_items)[i].transid,
|
|
(*other_items)[i].objectid, (*other_items)[i].last_modified))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!dao.updateObjectCd((*other_items)[i].key, (*other_items)[i].transid,
|
|
(*other_items)[i].objectid))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ((*other_items)[i].type == EType::Add2)
|
|
{
|
|
if ((*other_items)[i].cd_id != 0
|
|
&& (*other_items)[i].generation != 0)
|
|
{
|
|
if (!dao.updateGenerationCd((*other_items)[i].cd_id,
|
|
(*other_items)[i].generation))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
|
|
if ((*other_items)[i].cd_id == 0)
|
|
{
|
|
if (with_last_modified)
|
|
{
|
|
if (!dao.addObject2((*other_items)[i].transid, (*other_items)[i].key,
|
|
(*other_items)[i].md5sum, (*other_items)[i].size,
|
|
(*other_items)[i].last_modified))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!dao.addObject((*other_items)[i].transid, (*other_items)[i].key,
|
|
(*other_items)[i].md5sum, (*other_items)[i].size))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (with_last_modified)
|
|
{
|
|
if (!dao.addObject2Cd((*other_items)[i].cd_id, (*other_items)[i].transid, (*other_items)[i].key,
|
|
(*other_items)[i].md5sum, (*other_items)[i].size,
|
|
(*other_items)[i].last_modified))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!dao.addObjectCd((*other_items)[i].cd_id, (*other_items)[i].transid, (*other_items)[i].key,
|
|
(*other_items)[i].md5sum, (*other_items)[i].size))
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ((*other_items)[i].type == EType::Flush)
|
|
{
|
|
rowids[i] = 1;
|
|
|
|
if (!do_synchronous)
|
|
{
|
|
needs_flush = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
if (!dao.getDb()->EndTransaction())
|
|
{
|
|
worker_abort();
|
|
}
|
|
}
|
|
|
|
if (needs_flush)
|
|
{
|
|
assert(db_wal_file != nullptr);
|
|
|
|
if (db_wal_file != nullptr)
|
|
{
|
|
if (!db_wal_file->Sync())
|
|
{
|
|
Server->Log("Error flushing wal file. " + os_last_error_str(), LL_ERROR);
|
|
worker_abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
lock.relock(mutex.get());
|
|
|
|
for (size_t i = 0; i < other_items->size(); ++i)
|
|
{
|
|
if ((*other_items)[i].type == EType::Add
|
|
|| (*other_items)[i].type == EType::Flush)
|
|
{
|
|
*(*other_items)[i].rowid = rowids[i];
|
|
}
|
|
}
|
|
|
|
other_items->clear();
|
|
|
|
commit_cond->notify_all();
|
|
}
|
|
}
|
|
|
|
std::string KvStoreFrontend::PutDbWorker::meminfo() {
|
|
std::string ret;
|
|
IScopedLock lock(mutex.get());
|
|
ret += "##KvStoreFrontend::PutDbWorker:\n";
|
|
ret += " items_a: " + convert(items_a.capacity()) + " * " + PrettyPrintBytes(sizeof(SItem)) + "\n";
|
|
ret += " items_b: " + convert(items_b.capacity()) + " * " + PrettyPrintBytes(sizeof(SItem)) + "\n";
|
|
return ret;
|
|
}
|
|
|
|
void KvStoreFrontend::PutDbWorker::wait_queue(IScopedLock& lock)
|
|
{
|
|
while (curr_items->size() > 10000)
|
|
{
|
|
commit_cond->wait(&lock);
|
|
}
|
|
}
|
|
|
|
void KvStoreFrontend::PutDbWorker::worker_abort()
|
|
{
|
|
std::string last_error = extractLastLogErrors();
|
|
|
|
addSystemEvent("cache_db_err", "Error writing to cache db",
|
|
"Error writing to database at " + db_path + ".\n"
|
|
"Please make sure the cache device does not have any problems, then reboot to fix this issue\nLast Error:\n"
|
|
+last_error,
|
|
LL_ERROR);
|
|
abort();
|
|
}
|
|
|
|
void KvStoreFrontend::ScrubThread::operator()()
|
|
{
|
|
Auto(delete this);
|
|
IFsFile* tmpf = Server->openTemporaryFile();
|
|
ScopedDeleteFile del_tmpf(tmpf);
|
|
|
|
if (tmpf == nullptr)
|
|
{
|
|
Server->Log("Error opening temporary file in scrub. " + os_last_error_str(), LL_ERROR);
|
|
scrub_queue.set_error(true);
|
|
return;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
KvStoreDao::CdIterObject item = scrub_queue.get();
|
|
if (item.tkey.empty()
|
|
&& item.size==0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
std::string ret_md5sum;
|
|
unsigned int get_status;
|
|
|
|
int flags = IKvStoreBackend::GetBackground;
|
|
if (scrub_action == ScrubAction::Balance)
|
|
{
|
|
flags |= IKvStoreBackend::GetRebalance;
|
|
}
|
|
else if (scrub_action == ScrubAction::Scrub)
|
|
{
|
|
flags |= IKvStoreBackend::GetScrub;
|
|
}
|
|
else if(scrub_action==ScrubAction::Rebuild)
|
|
{
|
|
flags |= IKvStoreBackend::GetRebuild;
|
|
flags |= IKvStoreBackend::GetNoThrottle;
|
|
}
|
|
|
|
if (with_last_modified)
|
|
{
|
|
flags |= IKvStoreBackend::GetReadahead;
|
|
}
|
|
|
|
std::string object_fn;
|
|
|
|
if (item.trans_id==-2)
|
|
{
|
|
object_fn = item.tkey;
|
|
}
|
|
else if (item.trans_id == -1)
|
|
{
|
|
object_fn = frontend->prefixKey(item.tkey);
|
|
}
|
|
else
|
|
{
|
|
object_fn = frontend->prefixKey(frontend->encodeKey(item.tkey, item.trans_id));
|
|
}
|
|
|
|
bool b = backend->get(object_fn,
|
|
item.md5sum, flags,
|
|
false, tmpf, ret_md5sum, get_status);
|
|
|
|
size_t tries = 3;
|
|
size_t retry_n = 0;
|
|
bool has_newer = false;
|
|
while( (!b || (get_status & IKvStoreBackend::GetStatusRepairError) )
|
|
&& item.trans_id > 0
|
|
&& tries>0)
|
|
{
|
|
if (!(get_status & IKvStoreBackend::GetStatusEnospc))
|
|
{
|
|
int64 max_transid = frontend->get_transid(item.tkey, LLONG_MAX);
|
|
if (max_transid > item.trans_id)
|
|
{
|
|
has_newer = true;
|
|
break;
|
|
}
|
|
}
|
|
Server->wait(1000);
|
|
b = backend->get(object_fn,
|
|
item.md5sum, flags,
|
|
false,
|
|
tmpf, ret_md5sum, get_status);
|
|
--tries;
|
|
++retry_n;
|
|
|
|
if (get_status & IKvStoreBackend::GetStatusEnospc)
|
|
{
|
|
retryWait("ScrubEnospc", retry_n);
|
|
tries = 3;
|
|
}
|
|
}
|
|
|
|
bool curr_has_error=false;
|
|
if (!b)
|
|
{
|
|
if (backend_mirror == nullptr
|
|
|| !backend_mirror->get(object_fn,
|
|
item.md5sum, flags,
|
|
false, tmpf, ret_md5sum, get_status))
|
|
{
|
|
Server->Log("Error while " + strScrubAction(scrub_action) + " key " + object_fn+" Has_newer = "+convert(has_newer), LL_ERROR);
|
|
curr_has_error = true;
|
|
if (item.trans_id == -2 && item.tkey == "cd_magic_file")
|
|
{
|
|
Server->Log("Ignoring cd_magic_file scrub error", LL_WARNING);
|
|
}
|
|
else if (item.trans_id == -1 && (item.tkey.find("_complete") != std::string::npos
|
|
|| item.tkey.find("_finalized") != std::string::npos
|
|
|| item.tkey.find("_inactive") != std::string::npos))
|
|
{
|
|
Server->Log("Repairing. Adding empty file...", LL_WARNING);
|
|
int64 size;
|
|
std::string md5sum;
|
|
std::unique_ptr<IFsFile> empty_file(frontend->cachefs->openFile("empty.file", MODE_READ));
|
|
|
|
if(!empty_file)
|
|
{
|
|
Server->Log("Error while opening empty file. "+frontend->cachefs->lastError(), LL_ERROR);
|
|
++scrub_queue.scrub_errors;
|
|
}
|
|
else
|
|
{
|
|
if (!backend->put(object_fn,
|
|
empty_file.get(), 0, false, md5sum, size))
|
|
{
|
|
++scrub_queue.scrub_errors;
|
|
}
|
|
else
|
|
{
|
|
++scrub_queue.scrub_repaired;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++scrub_queue.scrub_errors;
|
|
}
|
|
}
|
|
else if (backend_mirror != nullptr &&
|
|
!(get_status & IKvStoreBackend::GetStatusSkipped))
|
|
{
|
|
bool put_success = false;
|
|
size_t tries = 0;
|
|
while (true)
|
|
{
|
|
std::string put_md5sum;
|
|
int64 put_size = 0;
|
|
if (backend->put(object_fn, tmpf, IKvStoreBackend::PutAlreadyCompressedEncrypted, true, put_md5sum, put_size))
|
|
{
|
|
put_success = true;
|
|
item.md5sum = put_md5sum;
|
|
IScopedLock lock(new_md5sums_mutex);
|
|
new_md5sums.push_back(item);
|
|
has_changes = true;
|
|
break;
|
|
}
|
|
retryWait("ScrubBput2", ++tries);
|
|
}
|
|
|
|
if (put_success)
|
|
{
|
|
++scrub_queue.scrub_repaired;
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Error while " + strScrubAction(scrub_action) + " key " + object_fn + ". Writing to storage failed (from mirror). Has_newer=" + convert(has_newer), LL_ERROR);
|
|
++scrub_queue.scrub_errors;
|
|
}
|
|
|
|
}
|
|
else if (backend_mirror != nullptr)
|
|
{
|
|
Server->Log("Backend mirror present, success, but no get_file. Has_newer=" + convert(has_newer), LL_ERROR);
|
|
++scrub_queue.scrub_errors;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (get_status & IKvStoreBackend::GetStatusRepaired)
|
|
++scrub_queue.scrub_repaired;
|
|
else
|
|
++scrub_queue.scrub_oks;
|
|
}
|
|
|
|
if(!(get_status & IKvStoreBackend::GetStatusSkipped))
|
|
{
|
|
if(!tmpf->Resize(0))
|
|
{
|
|
Server->Log("Error resizing temporary file in scrub. " + os_last_error_str(), LL_ERROR);
|
|
scrub_queue.set_error(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (b
|
|
&& !ret_md5sum.empty())
|
|
{
|
|
++frontend->total_balance_ops;
|
|
}
|
|
if (b
|
|
&& !ret_md5sum.empty()
|
|
&& item.md5sum != ret_md5sum)
|
|
{
|
|
item.md5sum = ret_md5sum;
|
|
IScopedLock lock(new_md5sums_mutex);
|
|
new_md5sums.push_back(item);
|
|
}
|
|
|
|
done_size += item.size;
|
|
if ( !b && !has_changes)
|
|
{
|
|
IScopedLock lock(new_md5sums_mutex);
|
|
has_changes = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void KvStoreFrontend::MirrorWorker::operator()()
|
|
{
|
|
#ifdef HAS_MIRROR
|
|
std::vector<STimeSpan> mirror_window = ServerSettings::getWindow(frontend->mirror_window);
|
|
|
|
KvStoreDao dao(Server->getDatabase(Server->getThreadID(), CLOUDDRIVE_DB));
|
|
|
|
if (frontend->mirror_curr_total < 0)
|
|
{
|
|
DBScopedWriteTransaction db_write_trans(dao.getDb());
|
|
|
|
KvStoreDao::SUnmirrored unmirrored = dao.getUnmirroredObjectsSize();
|
|
frontend->mirror_curr_total = unmirrored.tsize;
|
|
frontend->mirror_items = unmirrored.tcount;
|
|
}
|
|
|
|
int64 last_mirror = -1;
|
|
|
|
while (!do_quit)
|
|
{
|
|
if (!frontend->mirror_window.empty()
|
|
&& !ServerSettings::isInTimeSpan(mirror_window))
|
|
{
|
|
frontend->mirror_state = 1;
|
|
background_worker.set_mirror_pause(false);
|
|
Server->wait(60);
|
|
continue;
|
|
}
|
|
|
|
Server->Log("Pausing background worker (mirror worker)...");
|
|
|
|
bool has_task;
|
|
do
|
|
{
|
|
has_task = dao.getTask(Server->getTimeSeconds() - frontend->task_delay).exists;
|
|
|
|
background_worker.set_mirror_pause(false);
|
|
while (background_worker.is_paused()
|
|
&& !background_worker.get_pause())
|
|
{
|
|
Server->wait(100);
|
|
background_worker.set_mirror_pause(false);
|
|
}
|
|
|
|
if (has_task)
|
|
{
|
|
Server->wait(10000);
|
|
}
|
|
|
|
background_worker.set_mirror_pause(true);
|
|
Server->Log("Waiting for background worker (mirror worker)...");
|
|
while (!background_worker.is_paused())
|
|
{
|
|
Server->wait(100);
|
|
background_worker.set_mirror_pause(true);
|
|
}
|
|
} while (has_task);
|
|
|
|
if (last_mirror == -1
|
|
|| Server->getTimeMS() - last_mirror>30*60*1000)
|
|
{
|
|
last_mirror = Server->getTimeMS();
|
|
|
|
frontend->mirror_state = 0;
|
|
frontend->mirror_curr_pos = 0;
|
|
}
|
|
|
|
int64 orig_backend_mirror_del_log_rpos = frontend->backend_mirror_del_log_rpos;
|
|
int64 orig_backend_mirror_del_log_wpos = frontend->get_backend_mirror_del_log_wpos();
|
|
KvStoreFrontend* cfrontend = frontend;
|
|
|
|
int64 del_items = 0;
|
|
while (cfrontend->get_backend_mirror_del_log_rpos() < orig_backend_mirror_del_log_wpos)
|
|
{
|
|
if (cfrontend->next_log_del_mirror_item().empty())
|
|
break;
|
|
++del_items;
|
|
}
|
|
|
|
cfrontend->set_backend_mirror_del_log_rpos(orig_backend_mirror_del_log_rpos);
|
|
|
|
frontend->mirror_items += del_items;
|
|
|
|
bool ret = frontend->backend_mirror->del(
|
|
[cfrontend, orig_backend_mirror_del_log_rpos, orig_backend_mirror_del_log_wpos, &del_items]
|
|
(IKvStoreBackend::key_next_action_t action, std::string* key) {
|
|
if (action == IKvStoreBackend::key_next_action_t::clear)
|
|
{
|
|
assert(key == nullptr);
|
|
return true;
|
|
}
|
|
|
|
if (action == IKvStoreBackend::key_next_action_t::reset)
|
|
{
|
|
assert(key == nullptr);
|
|
cfrontend->set_backend_mirror_del_log_rpos(orig_backend_mirror_del_log_rpos);
|
|
return true;
|
|
}
|
|
|
|
if (cfrontend->get_backend_mirror_del_log_rpos() >= orig_backend_mirror_del_log_wpos)
|
|
return false;
|
|
|
|
*key = cfrontend->next_log_del_mirror_item();
|
|
|
|
if (key->empty())
|
|
return false;
|
|
|
|
Server->Log("Deleting " + *key + " from mirror...", LL_INFO);
|
|
if (del_items > 0)
|
|
{
|
|
--cfrontend->mirror_items;
|
|
--del_items;
|
|
}
|
|
|
|
return true;
|
|
}, false);
|
|
|
|
if (!ret)
|
|
{
|
|
Server->Log("Deletion from mirror failed.", LL_INFO);
|
|
frontend->mirror_items -= del_items;
|
|
continue;
|
|
}
|
|
else if (del_items > 0)
|
|
{
|
|
last_mirror = Server->getTimeMS();
|
|
}
|
|
|
|
cfrontend->backend_mirror_del_log->PunchHole(0, cfrontend->get_backend_mirror_del_log_rpos());
|
|
|
|
std::vector<KvStoreDao::CdIterObject2> objs = dao.getUnmirroredObjects();
|
|
|
|
if (objs.empty())
|
|
{
|
|
std::vector<KvStoreDao::SCdTrans> transactions = dao.getUnmirroredTransactions();
|
|
|
|
frontend->mirror_items += transactions.size();
|
|
|
|
for (KvStoreDao::SCdTrans& trans : transactions)
|
|
{
|
|
std::string suffix;
|
|
if (trans.completed == 2)
|
|
{
|
|
suffix = "_complete";
|
|
}
|
|
else if (trans.completed == 1)
|
|
{
|
|
suffix = "_finalized";
|
|
}
|
|
else if (trans.active == 0)
|
|
{
|
|
suffix = "_inactive";
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
std::unique_ptr<IFsFile> empty_file(frontend->cachefs->openFile(frontend->empty_file_path, MODE_READ));
|
|
|
|
if(!empty_file)
|
|
{
|
|
Server->Log("Cannot open empty file: "+frontend->cachefs->lastError(), LL_ERROR);
|
|
abort();
|
|
}
|
|
|
|
int64 size;
|
|
std::string md5sum;
|
|
if (frontend->backend_mirror->put(frontend->prefixKey(convert(trans.id) + suffix),
|
|
empty_file.get(), 0, false, md5sum, size))
|
|
{
|
|
dao.setTransactionMirrored(trans.id);
|
|
--frontend->mirror_items;
|
|
last_mirror = Server->getTimeMS();
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Mirroring transaction status failed.", LL_INFO);
|
|
--frontend->mirror_items;
|
|
}
|
|
}
|
|
|
|
if (transactions.empty())
|
|
{
|
|
frontend->mirror_state = 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsigned int stride_size = 100;
|
|
size_t num_threads = os_get_num_cpus();
|
|
|
|
std::vector<THREADPOOL_TICKET> tickets;
|
|
std::vector<std::unique_ptr<MirrorThread> > mirror_threads;
|
|
std::unique_ptr<IPipe> rpipe(Server->createMemoryPipe());
|
|
mirror_threads.resize(num_threads);
|
|
tickets.resize(num_threads);
|
|
for (size_t i = 0; i < num_threads; ++i)
|
|
{
|
|
mirror_threads[i].reset(new MirrorThread(objs, rpipe.get(),
|
|
frontend->backend, frontend->backend_mirror, frontend));
|
|
tickets[i] = Server->getThreadPool()->execute(mirror_threads[i].get(),
|
|
"mirror upload");
|
|
}
|
|
|
|
for (size_t i = 0; i < objs.size(); i += stride_size)
|
|
{
|
|
CWData wdata;
|
|
wdata.addVarInt(i);
|
|
wdata.addVarInt(stride_size);
|
|
rpipe->Write(wdata.getDataPtr(), wdata.getDataSize());
|
|
}
|
|
|
|
rpipe->Write(std::string());
|
|
|
|
Server->getThreadPool()->waitFor(tickets);
|
|
|
|
bool has_error = false;
|
|
for (auto& mt : mirror_threads)
|
|
{
|
|
if (mt->get_has_error())
|
|
has_error = true;
|
|
}
|
|
|
|
if (!has_error)
|
|
{
|
|
DBScopedWriteTransaction trans(dao.getDb());
|
|
|
|
for (auto obj : objs)
|
|
{
|
|
dao.setObjectMirrored(obj.id);
|
|
}
|
|
|
|
last_mirror = Server->getTimeMS();
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Mirroring objects failed.", LL_INFO);
|
|
}
|
|
}
|
|
}
|
|
|
|
delete this;
|
|
#endif //HAS_MIRROR
|
|
}
|
|
|
|
void KvStoreFrontend::MirrorThread::operator()()
|
|
{
|
|
IFsFile* tmpf = Server->openTemporaryFile();
|
|
ScopedDeleteFile del_tmpf(tmpf);
|
|
|
|
if(tmpf==nullptr)
|
|
abort();
|
|
|
|
std::string cdata;
|
|
while (!has_error
|
|
&& rpipe->Read(&cdata)>0)
|
|
{
|
|
CRData data(cdata.data(), cdata.size());
|
|
|
|
int64 start;
|
|
int64 size;
|
|
bool b = data.getVarInt(&start);
|
|
b &= data.getVarInt(&size);
|
|
assert(b && start>=0 && size>0);
|
|
|
|
for (size_t i = start; i < static_cast<size_t>(start + size); ++i)
|
|
{
|
|
KvStoreDao::CdIterObject2& obj = objs[i];
|
|
|
|
std::string fn = frontend->prefixKey(frontend->encodeKey(obj.tkey, obj.trans_id));
|
|
std::string ret_md5sum;
|
|
unsigned int get_status;
|
|
size_t retry_n = 0;
|
|
bool success = false;
|
|
while (!success
|
|
&& retry_n < 10)
|
|
{
|
|
if(!tmpf->Resize(0) || !tmpf->Seek(0))
|
|
{
|
|
abort();
|
|
}
|
|
|
|
if (backend->get(fn, obj.md5sum, IKvStoreBackend::GetPrependMd5sum, false, tmpf, ret_md5sum, get_status))
|
|
{
|
|
size_t retry_n_put = 0;
|
|
|
|
while (!success
|
|
&& retry_n_put < 10)
|
|
{
|
|
tmpf->Seek(0);
|
|
int64 ret_size;
|
|
|
|
if (backend_mirror->put(fn, tmpf, IKvStoreBackend::PutAlreadyCompressedEncrypted, false, ret_md5sum, ret_size))
|
|
{
|
|
frontend->mirror_curr_pos += obj.size;
|
|
success = true;
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
retryWait("Mirror", ++retry_n_put);
|
|
}
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
has_error = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
retryWait("Mirror", ++retry_n);
|
|
}
|
|
}
|
|
|
|
--frontend->mirror_items;
|
|
|
|
if (!success)
|
|
{
|
|
has_error = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
rpipe->Write(std::string());
|
|
}
|