You've already forked mariadb-columnstore-engine
mirror of
https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
synced 2025-12-17 01:02:23 +03:00
Made Sync take into account that object names may change
beteween when a job is created and when it is run.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
#include "Synchronizer.h"
|
#include "Synchronizer.h"
|
||||||
|
#include "Metadatafile.h"
|
||||||
#include <boost/thread/mutex.hpp>
|
#include <boost/thread/mutex.hpp>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@@ -61,8 +62,6 @@ enum OpFlags
|
|||||||
JOURNAL = 0x1,
|
JOURNAL = 0x1,
|
||||||
DELETE = 0x2,
|
DELETE = 0x2,
|
||||||
NEW_OBJECT = 0x4,
|
NEW_OBJECT = 0x4,
|
||||||
IN_PROGRESS = 0x8,
|
|
||||||
IS_FLUSH = 0x10
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Synchronizer::newJournalEntry(const string &key)
|
void Synchronizer::newJournalEntry(const string &key)
|
||||||
@@ -115,29 +114,41 @@ void Synchronizer::flushObject(const string &key)
|
|||||||
|
|
||||||
void Synchronizer::makeJob(const string &key)
|
void Synchronizer::makeJob(const string &key)
|
||||||
{
|
{
|
||||||
boost::shared_ptr<Job> j(new Job(this, key));
|
boost::shared_ptr<string> s(new string(key));
|
||||||
|
names.push_front(s);
|
||||||
|
|
||||||
|
boost::shared_ptr<Job> j(new Job(this, names.begin()));
|
||||||
threadPool.addJob(j);
|
threadPool.addJob(j);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synchronizer::process(const string &key)
|
void Synchronizer::process(list<string>::iterator &name)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
check if there is a pendingOp for *it
|
||||||
|
if yes, start processing it
|
||||||
|
if no,
|
||||||
|
check if there is an ongoing op and block on it
|
||||||
|
if not, return
|
||||||
|
*/
|
||||||
|
|
||||||
boost::unique_lock<boost::mutex> s(mutex);
|
boost::unique_lock<boost::mutex> s(mutex);
|
||||||
|
|
||||||
auto op = opsInProgress.find(key);
|
|
||||||
// it's already in progress
|
|
||||||
if (op != opsInProgress.end())
|
|
||||||
{
|
|
||||||
op->second->wait(&mutex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto it = pendingOps.find(key);
|
auto it = pendingOps.find(key);
|
||||||
// no work to be done on this key
|
|
||||||
if (it == pendingOps.end())
|
if (it == pendingOps.end())
|
||||||
return;
|
{
|
||||||
|
auto op = opsInProgress.find(key);
|
||||||
|
// it's already in progress
|
||||||
|
if (op != opsInProgress.end())
|
||||||
|
{
|
||||||
|
op->second->wait(&mutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boost::shared_ptr<PendingOps> pending = it->second;
|
boost::shared_ptr<PendingOps> pending = it->second;
|
||||||
opsInProgress[key] = *it;
|
opsInProgress[key] = *it;
|
||||||
pendingOps.erase(it);
|
pendingOps.erase(it);
|
||||||
|
string sourceFile = Metadata::getSourceFilenameFromKey(*name);
|
||||||
s.unlock();
|
s.unlock();
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
@@ -145,11 +156,11 @@ void Synchronizer::process(const string &key)
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (pending->opFlags & DELETE)
|
if (pending->opFlags & DELETE)
|
||||||
synchronizeDelete(key);
|
synchronizeDelete(sourceFile, name);
|
||||||
else if (pending->opFlags & JOURNAL)
|
else if (pending->opFlags & JOURNAL)
|
||||||
synchronizerWithJournal(key);
|
synchronizerWithJournal(sourceFile, name);
|
||||||
else if (pending->opFlags & NEW_OBJECT)
|
else if (pending->opFlags & NEW_OBJECT)
|
||||||
synchronize(key);
|
synchronize(sourceFile, name);
|
||||||
else
|
else
|
||||||
throw logic_error("Synchronizer::process(): got an unknown op flag");
|
throw logic_error("Synchronizer::process(): got an unknown op flag");
|
||||||
pending->notify(&mutex);
|
pending->notify(&mutex);
|
||||||
@@ -159,8 +170,14 @@ void Synchronizer::process(const string &key)
|
|||||||
logger->log(LOG_CRIT, "Synchronizer::process(): error sync'ing %s opFlags=%d, got '%s'. Requeueing it.", key.c_str(),
|
logger->log(LOG_CRIT, "Synchronizer::process(): error sync'ing %s opFlags=%d, got '%s'. Requeueing it.", key.c_str(),
|
||||||
pending->opFlags, e.what());
|
pending->opFlags, e.what());
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
// TODO: requeue the job instead of looping infinitely
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.lock();
|
||||||
|
opsInProgress.erase(key);
|
||||||
|
names.erase(name);
|
||||||
|
|
||||||
// TBD: On a network outage or S3 outage, it might not be a bad idea to keep retrying
|
// TBD: On a network outage or S3 outage, it might not be a bad idea to keep retrying
|
||||||
// until the end of time. This will (?) naturally make the system unusable until the blockage
|
// until the end of time. This will (?) naturally make the system unusable until the blockage
|
||||||
// is cleared, which is what we want, right? Is there a way to nicely tell the user what
|
// is cleared, which is what we want, right? Is there a way to nicely tell the user what
|
||||||
@@ -207,40 +224,58 @@ struct ScopedWriteLock
|
|||||||
bool locked;
|
bool locked;
|
||||||
};
|
};
|
||||||
|
|
||||||
void Synchronizer::synchronize(const string &key)
|
void Synchronizer::synchronize(const string &sourceFile, list<string>::iterator &it)
|
||||||
{
|
{
|
||||||
ScopedReadLock s(ioc, key);
|
ScopedReadLock s(ioc, sourceFile);
|
||||||
|
|
||||||
|
string &key = *it;
|
||||||
char buf[80];
|
char buf[80];
|
||||||
bool exists = false;
|
bool exists = false;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = cs->exists(key, &exists);
|
err = cs->exists(key, &exists);
|
||||||
if (err)
|
if (err)
|
||||||
throw runtime_error(strerror_r(errno, buf, 80));
|
throw runtime_error(string("synchronize(): checking existence of ") + key + ", got " +
|
||||||
|
strerror_r(errno, buf, 80));
|
||||||
|
if (exists)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// can this run after a delete op?
|
||||||
|
exists = bf::exists(cache->getCachePath() / key);
|
||||||
if (!exists)
|
if (!exists)
|
||||||
{
|
{
|
||||||
err = cs->putObject(cache->getCachePath() / key, key);
|
logger->log(LOG_WARNING, "synchronize(): was told to upload %s but it does not exist locally", key.c_str());
|
||||||
if (err)
|
return;
|
||||||
throw runtime_error(strerror_r(errno, buf, 80));
|
|
||||||
replicator->delete(key, Replicator::NO_LOCAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = cs->putObject(cache->getCachePath() / key, key);
|
||||||
|
if (err)
|
||||||
|
throw runtime_error(string("synchronize(): uploading ") + key + ", got " + strerror_r(errno, buf, 80));
|
||||||
|
replicator->delete(key, Replicator::NO_LOCAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synchronizer::synchronizeDelete(const string &key)
|
void Synchronizer::synchronizeDelete(const string &sourceFile, list<string>::iterator &it)
|
||||||
{
|
{
|
||||||
/* Right now I think this is being told to delete key from cloud storage,
|
ScopedWriteLock s(ioc, sourceFile);
|
||||||
and that it has already been deleted everywhere locally. If that's not right,
|
cs->delete(*it);
|
||||||
we'll have to add some sync around this. */
|
|
||||||
cs->delete(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synchronizer::synchronizeWithJournal(const string &key)
|
void Synchronizer::synchronizeWithJournal(const string &sourceFile, list<string>::iterator &lit)
|
||||||
{
|
{
|
||||||
// interface to Metadata TBD
|
// interface to Metadata TBD
|
||||||
//string sourceFilename = Metadata::getSourceFromKey(key);
|
ScopedWriteLock s(ioc, sourceFile);
|
||||||
ScopedWriteLock s(ioc, sourceFilename);
|
|
||||||
|
string &key = *lit;
|
||||||
bf::path oldCachePath = cache->getCachePath() / key;
|
bf::path oldCachePath = cache->getCachePath() / key;
|
||||||
string journalName = oldCachePath.string() + ".journal";
|
string journalName = oldCachePath.string() + ".journal";
|
||||||
|
|
||||||
|
if (!bf::exists(journalName))
|
||||||
|
{
|
||||||
|
logger->(LOG_WARNING, "synchronizeWithJournal(): no journal file found for %s", key.c_str());
|
||||||
|
// I don't think this should happen, maybe throw a logic_error here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int err;
|
int err;
|
||||||
boost::shared_array<uint8_t> data;
|
boost::shared_array<uint8_t> data;
|
||||||
size_t count = 0, size = 0;
|
size_t count = 0, size = 0;
|
||||||
@@ -262,7 +297,7 @@ void Synchronizer::synchronizeWithJournal(const string &key)
|
|||||||
assert(data);
|
assert(data);
|
||||||
|
|
||||||
// get a new key for the resolved version & upload it
|
// get a new key for the resolved version & upload it
|
||||||
string newKey = ios->newKeyFromOldKey(key);
|
string newKey = ioc->newKeyFromOldKey(key);
|
||||||
err = cs->putObject(data, size, newKey);
|
err = cs->putObject(data, size, newKey);
|
||||||
if (err)
|
if (err)
|
||||||
throw runtime_error(string("Synchronizer: putObject() failed: ") + strerror_r(errno, buf, 80));
|
throw runtime_error(string("Synchronizer: putObject() failed: ") + strerror_r(errno, buf, 80));
|
||||||
@@ -292,13 +327,7 @@ void Synchronizer::synchronizeWithJournal(const string &key)
|
|||||||
count += err;
|
count += err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// might be wise to add things like getting a file size to a utility class.
|
cache->rename(key, newKey, size - bf::file_size(oldCachePath));
|
||||||
// TBD how often we need to do it.
|
|
||||||
struct stat statbuf;
|
|
||||||
err = stat(oldCachePath.string().c_str(), &statbuf);
|
|
||||||
assert(!err);
|
|
||||||
cache->rename(key, newKey, size - statbuf.st_size);
|
|
||||||
rename(key, newKey);
|
|
||||||
replicator->delete(key);
|
replicator->delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,17 +338,13 @@ void Synchronizer::synchronizeWithJournal(const string &key)
|
|||||||
md.rename(key, newKey);
|
md.rename(key, newKey);
|
||||||
replicator->updateMetadata(sourceFilename, md);
|
replicator->updateMetadata(sourceFilename, md);
|
||||||
*/
|
*/
|
||||||
|
rename(key, newKey);
|
||||||
ioc->renameObject(oldkey, newkey);
|
ioc->renameObject(oldkey, newkey);
|
||||||
s.unlock();
|
s.unlock();
|
||||||
|
|
||||||
struct stat statbuf;
|
|
||||||
err = stat(journalName.string().c_str(), &statbuf);
|
|
||||||
assert(!err);
|
|
||||||
|
|
||||||
// delete the old object & journal file
|
// delete the old object & journal file
|
||||||
|
cache->deletedJournal(bf::file_size(journalName);
|
||||||
replicator->delete(journalName);
|
replicator->delete(journalName);
|
||||||
cache->deletedJournal(statbuf.st_size);
|
|
||||||
cs->delete(key);
|
cs->delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,8 +355,12 @@ void Synchronizer::rename(const string &oldKey, const string &newKey)
|
|||||||
auto it = pendingOps.find(oldKey);
|
auto it = pendingOps.find(oldKey);
|
||||||
if (it == pendingOps.end())
|
if (it == pendingOps.end())
|
||||||
return;
|
return;
|
||||||
pendingOps.erase(it);
|
|
||||||
pendingOps[newKey] = it->second;
|
pendingOps[newKey] = it->second;
|
||||||
|
pendingOps.erase(it);
|
||||||
|
|
||||||
|
for (auto &name: objNames)
|
||||||
|
if (*name == oldKey)
|
||||||
|
*name = newKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The helper objects & fcns */
|
/* The helper objects & fcns */
|
||||||
|
|||||||
@@ -51,15 +51,22 @@ class Synchronizer : public boost::noncopyable
|
|||||||
|
|
||||||
struct Job : public ThreadPool::Job
|
struct Job : public ThreadPool::Job
|
||||||
{
|
{
|
||||||
Job(Synchronizer *s, const std::string &k) : sync(s), key(k) { }
|
Job(Synchronizer *s, std::list<std::string>::iterator &i) : sync(s), it(i) { }
|
||||||
void operator()() { sync->process(key); }
|
void operator()() { sync->process(it); }
|
||||||
Synchronizer *sync;
|
Synchronizer *sync;
|
||||||
std::string key;
|
std::list<std::string>::iterator it;
|
||||||
};
|
};
|
||||||
|
|
||||||
ThreadPool threadPool;
|
ThreadPool threadPool;
|
||||||
std::map<std::string, boost::shared_ptr<PendingOps> > pendingOps;
|
std::map<std::string, boost::shared_ptr<PendingOps> > pendingOps;
|
||||||
std::map<std::string, boost::shared_ptr<PendingOps> > opsInProgress;
|
std::map<std::string, boost::shared_ptr<PendingOps> > opsInProgress;
|
||||||
|
|
||||||
|
// this is a bit of a kludge to handle objects being renamed. Jobs can be issued
|
||||||
|
// against name1, but when the job starts running, the target may be name2.
|
||||||
|
// some consolidation should be possible between this and the two maps above, tbd.
|
||||||
|
// in general the code got kludgier b/c of renaming, needs a cleanup pass.
|
||||||
|
std::list<std::string> objNames;
|
||||||
|
|
||||||
SMLogging *logger;
|
SMLogging *logger;
|
||||||
Cache *cache;
|
Cache *cache;
|
||||||
Replicator *replicator;
|
Replicator *replicator;
|
||||||
|
|||||||
Reference in New Issue
Block a user