TerraSync: make tarball extraction asynchronous

This commit is contained in:
James Turner
2020-11-03 17:49:19 +00:00
parent 46f4967f6e
commit a59c4e2c8b
2 changed files with 110 additions and 48 deletions
+99 -42
View File
@@ -416,6 +416,66 @@ public:
return _relativePath; return _relativePath;
} }
class ArchiveExtractTask {
public:
ArchiveExtractTask(SGPath p, const std::string &relPath)
: relativePath(relPath), file(p), extractor(p.dir()) {
if (!file.open(SG_IO_IN)) {
SG_LOG(SG_TERRASYNC, SG_ALERT,
"Unable to open " << p << " to extract");
return;
}
buffer = (uint8_t *)malloc(bufferSize);
}
ArchiveExtractTask(const ArchiveExtractTask &) = delete;
HTTPRepoPrivate::ProcessResult run(HTTPRepoPrivate *repo) {
size_t rd = file.read((char *)buffer, bufferSize);
extractor.extractBytes(buffer, rd);
if (file.eof()) {
extractor.flush();
file.close();
if (!extractor.isAtEndOfArchive()) {
SG_LOG(SG_TERRASYNC, SG_ALERT, "Corrupt tarball " << relativePath);
repo->failedToUpdateChild(relativePath,
HTTPRepository::REPO_ERROR_IO);
return HTTPRepoPrivate::ProcessFailed;
}
if (extractor.hasError()) {
SG_LOG(SG_TERRASYNC, SG_ALERT, "Error extracting " << relativePath);
repo->failedToUpdateChild(relativePath,
HTTPRepository::REPO_ERROR_IO);
return HTTPRepoPrivate::ProcessFailed;
}
return HTTPRepoPrivate::ProcessDone;
}
return HTTPRepoPrivate::ProcessContinue;
}
~ArchiveExtractTask() { free(buffer); }
private:
// intentionally small so we extract incrementally on Windows
// where Defender throttles many small files, sorry
// if you make this bigger we will be more efficient but stall for
// longer when extracting the Airports_archive
const int bufferSize = 1024 * 64;
std::string relativePath;
uint8_t *buffer = nullptr;
SGBinaryFile file;
ArchiveExtractor extractor;
};
using ArchiveExtractTaskPtr = std::shared_ptr<ArchiveExtractTask>;
void didUpdateFile(const std::string& file, const std::string& hash, size_t sz) void didUpdateFile(const std::string& file, const std::string& hash, size_t sz)
{ {
// check hash matches what we expected // check hash matches what we expected
@@ -454,35 +514,22 @@ public:
} }
if (pathAvailable) { if (pathAvailable) {
// If this is a tarball, then extract it. // we use a Task helper to extract tarballs incrementally.
SGBinaryFile f(p); // without this, archive extraction blocks here, which
if (! f.open(SG_IO_IN)) SG_LOG(SG_TERRASYNC, SG_ALERT, "Unable to open " << p << " to extract"); // prevents other repositories downloading / updating.
// Unfortunately due Windows AV (Defender, etc) we cna block
// here for many minutes.
SG_LOG(SG_TERRASYNC, SG_INFO, "Extracting " << absolutePath() << "/" << file << " to " << p.dir()); // use a lambda to own this shared_ptr; this means when the
SGPath extractDir = p.dir(); // lambda is destroyed, the ArchiveExtraTask will get
ArchiveExtractor ex(extractDir); // cleaned up.
ArchiveExtractTaskPtr t =
const size_t bufSize = 1024 * 1024; std::make_shared<ArchiveExtractTask>(p, _relativePath);
uint8_t* buf = (uint8_t*)malloc(bufSize); auto cb = [t](HTTPRepoPrivate *repo) {
while (!f.eof()) { return t->run(repo);
size_t rd = f.read((char*)buf, bufSize); };
ex.extractBytes(buf, rd);
}
ex.flush();
free(buf);
if (! ex.isAtEndOfArchive()) {
SG_LOG(SG_TERRASYNC, SG_ALERT, "Corrupt tarball " << p);
_repository->failedToUpdateChild(
_relativePath, HTTPRepository::REPO_ERROR_IO);
}
if (ex.hasError()) {
SG_LOG(SG_TERRASYNC, SG_ALERT, "Error extracting " << p);
_repository->failedToUpdateChild(
_relativePath, HTTPRepository::REPO_ERROR_IO);
}
_repository->addTask(cb);
} else { } else {
SG_LOG(SG_TERRASYNC, SG_ALERT, "Unable to remove old file/directory " << removePath); SG_LOG(SG_TERRASYNC, SG_ALERT, "Unable to remove old file/directory " << removePath);
} // of pathAvailable } // of pathAvailable
@@ -820,14 +867,19 @@ void HTTPRepository::process()
const int maxToProcess = 16; const int maxToProcess = 16;
while (processedCount < maxToProcess) { while (processedCount < maxToProcess) {
if (_d->pendingUpdateOfChildren.empty()) { if (_d->pendingTasks.empty()) {
break; break;
} }
auto dirToUpdate = _d->pendingUpdateOfChildren.front(); auto task = _d->pendingTasks.front();
_d->pendingUpdateOfChildren.pop_front(); auto result = task(_d.get());
dirToUpdate->updateChildrenBasedOnHash(); if (result == HTTPRepoPrivate::ProcessContinue) {
++processedCount; // assume we're not complete
return;
}
_d->pendingTasks.pop_front();
++processedCount;
} }
_d->checkForComplete(); _d->checkForComplete();
@@ -835,9 +887,10 @@ void HTTPRepository::process()
void HTTPRepoPrivate::checkForComplete() void HTTPRepoPrivate::checkForComplete()
{ {
if (pendingUpdateOfChildren.empty() && activeRequests.empty() && queuedRequests.empty()) { if (pendingTasks.empty() && activeRequests.empty() &&
isUpdating = false; queuedRequests.empty()) {
} isUpdating = false;
}
} }
size_t HTTPRepository::bytesToDownload() const size_t HTTPRepository::bytesToDownload() const
@@ -1309,12 +1362,16 @@ HTTPRepository::failure() const
void HTTPRepoPrivate::scheduleUpdateOfChildren(HTTPDirectory* dir) void HTTPRepoPrivate::scheduleUpdateOfChildren(HTTPDirectory* dir)
{ {
auto it = std::find(pendingUpdateOfChildren.begin(), pendingUpdateOfChildren.end(), dir); auto updateChildTask = [dir](const HTTPRepoPrivate *) {
if (it != pendingUpdateOfChildren.end()) { dir->updateChildrenBasedOnHash();
return; // duplicate add, skip return ProcessDone;
} };
pendingUpdateOfChildren.push_back(dir); addTask(updateChildTask);
}
void HTTPRepoPrivate::addTask(RepoProcessTask task) {
pendingTasks.push_back(task);
} }
int HTTPRepoPrivate::countDirtyHashCaches() const int HTTPRepoPrivate::countDirtyHashCaches() const
+11 -6
View File
@@ -20,6 +20,7 @@
#pragma once #pragma once
#include <deque> #include <deque>
#include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -50,7 +51,7 @@ protected:
size_t _contentSize = 0; size_t _contentSize = 0;
}; };
typedef SGSharedPtr<HTTPRepoGetRequest> RepoRequestPtr; using RepoRequestPtr = SGSharedPtr<HTTPRepoGetRequest>;
class HTTPRepoPrivate { class HTTPRepoPrivate {
public: public:
@@ -102,20 +103,24 @@ public:
HTTPDirectory *getOrCreateDirectory(const std::string &path); HTTPDirectory *getOrCreateDirectory(const std::string &path);
bool deleteDirectory(const std::string &relPath, const SGPath &absPath); bool deleteDirectory(const std::string &relPath, const SGPath &absPath);
void scheduleUpdateOfChildren(HTTPDirectory* dir);
typedef std::vector<HTTPDirectory_ptr> DirectoryVector; typedef std::vector<HTTPDirectory_ptr> DirectoryVector;
DirectoryVector directories; DirectoryVector directories;
// list of directories to be locally processed void scheduleUpdateOfChildren(HTTPDirectory *dir);
// this is used to avoid deep recursion when receiving the .dirIndex;
// we don't want to recurse over a large repo in a single call
std::deque<HTTPDirectory*> pendingUpdateOfChildren;
SGPath installedCopyPath; SGPath installedCopyPath;
int countDirtyHashCaches() const; int countDirtyHashCaches() const;
void flushHashCaches(); void flushHashCaches();
enum ProcessResult { ProcessContinue, ProcessDone, ProcessFailed };
using RepoProcessTask = std::function<ProcessResult(HTTPRepoPrivate *repo)>;
void addTask(RepoProcessTask task);
std::deque<RepoProcessTask> pendingTasks;
}; };
} // namespace simgear } // namespace simgear