Threadsafe terrasync state updates/reading.

This commit is contained in:
James Turner
2016-06-08 15:27:47 +01:00
parent da1aeece14
commit 0ccf3e1629
+173 -128
View File
@@ -55,6 +55,9 @@
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx> #include <simgear/misc/strutils.hxx>
#include <simgear/threads/SGQueue.hxx> #include <simgear/threads/SGQueue.hxx>
#include <simgear/threads/SGThread.hxx>
#include <simgear/threads/SGGuard.hxx>
#include <simgear/misc/sg_dir.hxx> #include <simgear/misc/sg_dir.hxx>
#include <simgear/debug/BufferedLogCallback.hxx> #include <simgear/debug/BufferedLogCallback.hxx>
#include <simgear/props/props_io.hxx> #include <simgear/props/props_io.hxx>
@@ -154,7 +157,8 @@ class SyncSlot
public: public:
SyncSlot() : SyncSlot() :
isNewDirectory(false), isNewDirectory(false),
busy(false) busy(false),
pendingKBytes(0)
{} {}
SyncItem currentItem; SyncItem currentItem;
@@ -191,6 +195,36 @@ static unsigned int syncSlotForType(SyncItem::Type ty)
} }
} }
struct TerrasyncThreadState
{
TerrasyncThreadState() :
_busy(false),
_stalled(false),
_fail_count(0),
_updated_tile_count(0),
_success_count(0),
_consecutive_errors(0),
_allowed_errors(6),
_cache_hits(0),
_transfer_rate(0),
_total_kb_downloaded(0),
_totalKbPending(0)
{}
bool _busy;
bool _stalled;
int _fail_count;
int _updated_tile_count;
int _success_count;
int _consecutive_errors;
int _allowed_errors;
int _cache_hits;
int _transfer_rate;
// kbytes, not bytes, because bytes might overflow 2^31
int _total_kb_downloaded;
unsigned int _totalKbPending;
};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// SGTerraSync::WorkerThread ////////////////////////////////////////////////// // SGTerraSync::WorkerThread //////////////////////////////////////////////////
@@ -204,10 +238,31 @@ public:
void stop(); void stop();
bool start(); bool start();
bool isIdle() {return !_busy; } bool isIdle()
{
SGGuard<SGMutex> g(_stateLock);
return !_state._busy;
}
bool isRunning()
{
SGGuard<SGMutex> g(_stateLock);
return _running;
}
bool isStalled()
{
SGGuard<SGMutex> g(_stateLock);
return _state._stalled;
}
void request(const SyncItem& dir) {waitingTiles.push_front(dir);} void request(const SyncItem& dir) {waitingTiles.push_front(dir);}
bool isDirty() { bool r = _is_dirty;_is_dirty = false;return r;}
bool hasNewTiles() { return !_freshTiles.empty();} bool hasNewTiles()
{
return !_freshTiles.empty();
}
SyncItem getNewTile() { return _freshTiles.pop_front();} SyncItem getNewTile() { return _freshTiles.pop_front();}
void setHTTPServer(const std::string& server) void setHTTPServer(const std::string& server)
@@ -217,28 +272,36 @@ public:
void setLocalDir(string dir) { _local_dir = stripPath(dir);} void setLocalDir(string dir) { _local_dir = stripPath(dir);}
string getLocalDir() { return _local_dir;} string getLocalDir() { return _local_dir;}
void setAllowedErrorCount(int errors) {_allowed_errors = errors;}
void setAllowedErrorCount(int errors)
{
SGGuard<SGMutex> g(_stateLock);
_state._allowed_errors = errors;
}
void setCachePath(const SGPath& p) {_persistentCachePath = p;} void setCachePath(const SGPath& p) {_persistentCachePath = p;}
void setCacheHits(unsigned int hits) {_cache_hits = hits;} void setCacheHits(unsigned int hits)
{
volatile bool _active; SGGuard<SGMutex> g(_stateLock);
volatile bool _running; _state._cache_hits = hits;
volatile bool _busy; }
volatile bool _stalled;
volatile int _fail_count;
volatile int _updated_tile_count;
volatile int _success_count;
volatile int _consecutive_errors;
volatile int _allowed_errors;
volatile int _cache_hits;
volatile int _transfer_rate;
// kbytes, not bytes, because bytes might overflow 2^31
volatile int _total_kb_downloaded;
unsigned int _totalKbPending;
TerrasyncThreadState threadsafeCopyState()
{
TerrasyncThreadState st;
{
SGGuard<SGMutex> g(_stateLock);
st = _state;
}
return st;
}
private: private:
void incrementCacheHits()
{
SGGuard<SGMutex> g(_stateLock);
_state._cache_hits++;
}
virtual void run(); virtual void run();
// internal mode run and helpers // internal mode run and helpers
@@ -257,8 +320,7 @@ private:
HTTP::Client _http; HTTP::Client _http;
SyncSlot _syncSlots[NUM_SYNC_SLOTS]; SyncSlot _syncSlots[NUM_SYNC_SLOTS];
volatile bool _is_dirty; bool _stop, _running;
volatile bool _stop;
SGBlockingDeque <SyncItem> waitingTiles; SGBlockingDeque <SyncItem> waitingTiles;
TileAgeCache _completedTiles; TileAgeCache _completedTiles;
@@ -268,24 +330,14 @@ private:
string _local_dir; string _local_dir;
SGPath _persistentCachePath; SGPath _persistentCachePath;
string _httpServer; string _httpServer;
TerrasyncThreadState _state;
SGMutex _stateLock;
}; };
SGTerraSync::WorkerThread::WorkerThread() : SGTerraSync::WorkerThread::WorkerThread() :
_active(false), _stop(false),
_running(false), _running(false)
_busy(false),
_stalled(false),
_fail_count(0),
_updated_tile_count(0),
_success_count(0),
_consecutive_errors(0),
_allowed_errors(6),
_cache_hits(0),
_transfer_rate(0),
_total_kb_downloaded(0),
_totalKbPending(0),
_is_dirty(false),
_stop(false)
{ {
_http.setUserAgent("terrascenery-" SG_STRINGIZE(SIMGEAR_VERSION)); _http.setUserAgent("terrascenery-" SG_STRINGIZE(SIMGEAR_VERSION));
} }
@@ -295,28 +347,31 @@ void SGTerraSync::WorkerThread::stop()
// drop any pending requests // drop any pending requests
waitingTiles.clear(); waitingTiles.clear();
if (!_running) if (!isRunning())
return; return;
// set stop flag and wake up the thread with an empty request // set stop flag and wake up the thread with an empty request
_stop = true; {
SGGuard<SGMutex> g(_stateLock);
_stop = true;
}
SyncItem w(string(), SyncItem::Stop); SyncItem w(string(), SyncItem::Stop);
request(w); request(w);
join(); join();
_running = false;
} }
bool SGTerraSync::WorkerThread::start() bool SGTerraSync::WorkerThread::start()
{ {
if (_running) if (isRunning())
return false; return false;
if (_local_dir=="") if (_local_dir=="")
{ {
SG_LOG(SG_TERRASYNC,SG_ALERT, SG_LOG(SG_TERRASYNC,SG_ALERT,
"Cannot start scenery download. Local cache directory is undefined."); "Cannot start scenery download. Local cache directory is undefined.");
_fail_count++; _state._fail_count++;
_stalled = true; _state._stalled = true;
return false; return false;
} }
@@ -326,8 +381,8 @@ bool SGTerraSync::WorkerThread::start()
SG_LOG(SG_TERRASYNC,SG_ALERT, SG_LOG(SG_TERRASYNC,SG_ALERT,
"Cannot start scenery download. Directory '" << _local_dir << "Cannot start scenery download. Directory '" << _local_dir <<
"' does not exist. Set correct directory path or create directory folder."); "' does not exist. Set correct directory path or create directory folder.");
_fail_count++; _state._fail_count++;
_stalled = true; _state._stalled = true;
return false; return false;
} }
@@ -337,18 +392,13 @@ bool SGTerraSync::WorkerThread::start()
SG_LOG(SG_TERRASYNC,SG_ALERT, SG_LOG(SG_TERRASYNC,SG_ALERT,
"Cannot start scenery download. Directory '" << _local_dir << "Cannot start scenery download. Directory '" << _local_dir <<
"' contains the base package. Use a separate directory."); "' contains the base package. Use a separate directory.");
_fail_count++; _state._fail_count++;
_stalled = true; _state._stalled = true;
return false; return false;
} }
_fail_count = 0;
_updated_tile_count = 0;
_success_count = 0;
_consecutive_errors = 0;
_stop = false; _stop = false;
_stalled = false; _state = TerrasyncThreadState(); // clean state
_running = true;
string status = "Using built-in HTTP support."; string status = "Using built-in HTTP support.";
@@ -365,7 +415,11 @@ bool SGTerraSync::WorkerThread::start()
void SGTerraSync::WorkerThread::run() void SGTerraSync::WorkerThread::run()
{ {
_active = true; {
SGGuard<SGMutex> g(_stateLock);
_running = true;
}
initCompletedTilesPersistentCache(); initCompletedTilesPersistentCache();
if (_httpServer == "automatic" ) { if (_httpServer == "automatic" ) {
@@ -429,9 +483,11 @@ void SGTerraSync::WorkerThread::run()
runInternal(); runInternal();
_active = false; {
_running = false; SGGuard<SGMutex> g(_stateLock);
_is_dirty = true; _running = false;
}
} }
void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot) void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
@@ -444,8 +500,9 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
SG_LOG(SG_TERRASYNC, SG_INFO, "HTTP request count:" << _http.hasActiveRequests()); SG_LOG(SG_TERRASYNC, SG_INFO, "HTTP request count:" << _http.hasActiveRequests());
slot.nextWarnTimeout += 10000; slot.nextWarnTimeout += 10000;
} }
slot.pendingKBytes = slot.repository->bytesToDownload();
#endif #endif
// convert bytes to kbytes here
slot.pendingKBytes = (slot.repository->bytesToDownload() >> 10);
return; // easy, still working return; // easy, still working
} }
@@ -518,9 +575,12 @@ void SGTerraSync::WorkerThread::runInternal()
SG_LOG(SG_TERRASYNC, SG_WARN, "failure doing HTTP update" << e.getFormattedMessage()); SG_LOG(SG_TERRASYNC, SG_WARN, "failure doing HTTP update" << e.getFormattedMessage());
} }
_transfer_rate = _http.transferRateBytesPerSec(); {
// convert from bytes to kbytes SGGuard<SGMutex> g(_stateLock);
_total_kb_downloaded = static_cast<int>(_http.totalBytesDownloaded() / 1024); _state._transfer_rate = _http.transferRateBytesPerSec();
// convert from bytes to kbytes
_state._total_kb_downloaded = static_cast<int>(_http.totalBytesDownloaded() / 1024);
}
if (_stop) if (_stop)
break; break;
@@ -530,11 +590,10 @@ void SGTerraSync::WorkerThread::runInternal()
SyncItem next = waitingTiles.pop_front(); SyncItem next = waitingTiles.pop_front();
SyncItem::Status cacheStatus = isPathCached(next); SyncItem::Status cacheStatus = isPathCached(next);
if (cacheStatus != SyncItem::Invalid) { if (cacheStatus != SyncItem::Invalid) {
_cache_hits++; incrementCacheHits();
SG_LOG(SG_TERRASYNC, SG_DEBUG, "\nTerraSync Cache hit for: '" << next._dir << "'"); SG_LOG(SG_TERRASYNC, SG_DEBUG, "\nTerraSync Cache hit for: '" << next._dir << "'");
next._status = cacheStatus; next._status = cacheStatus;
_freshTiles.push_back(next); _freshTiles.push_back(next);
_is_dirty = true;
continue; continue;
} }
@@ -552,8 +611,12 @@ void SGTerraSync::WorkerThread::runInternal()
anySlotBusy |= _syncSlots[slot].busy; anySlotBusy |= _syncSlots[slot].busy;
} }
_totalKbPending = newPendingCount; // approximately atomic update {
_busy = anySlotBusy; SGGuard<SGMutex> g(_stateLock);
_state._totalKbPending = newPendingCount; // approximately atomic update
_state._busy = anySlotBusy;
}
if (!anySlotBusy) { if (!anySlotBusy) {
// wait on the blocking deque here, otherwise we spin // wait on the blocking deque here, otherwise we spin
// the loop very fast, since _http::update with no connections // the loop very fast, since _http::update with no connections
@@ -587,15 +650,15 @@ SyncItem::Status SGTerraSync::WorkerThread::isPathCached(const SyncItem& next) c
void SGTerraSync::WorkerThread::fail(SyncItem failedItem) void SGTerraSync::WorkerThread::fail(SyncItem failedItem)
{ {
SGGuard<SGMutex> g(_stateLock);
time_t now = time(0); time_t now = time(0);
_consecutive_errors++; _state._consecutive_errors++;
_fail_count++; _state._fail_count++;
failedItem._status = SyncItem::Failed; failedItem._status = SyncItem::Failed;
_freshTiles.push_back(failedItem); _freshTiles.push_back(failedItem);
SG_LOG(SG_TERRASYNC,SG_INFO, SG_LOG(SG_TERRASYNC,SG_INFO,
"Failed to sync'" << failedItem._dir << "'"); "Failed to sync'" << failedItem._dir << "'");
_completedTiles[ failedItem._dir ] = now + UpdateInterval::FailedAttempt; _completedTiles[ failedItem._dir ] = now + UpdateInterval::FailedAttempt;
_is_dirty = true;
} }
void SGTerraSync::WorkerThread::notFound(SyncItem item) void SGTerraSync::WorkerThread::notFound(SyncItem item)
@@ -608,27 +671,29 @@ void SGTerraSync::WorkerThread::notFound(SyncItem item)
time_t now = time(0); time_t now = time(0);
item._status = SyncItem::NotFound; item._status = SyncItem::NotFound;
_freshTiles.push_back(item); _freshTiles.push_back(item);
_is_dirty = true;
_notFoundItems[ item._dir ] = now + UpdateInterval::SuccessfulAttempt; _notFoundItems[ item._dir ] = now + UpdateInterval::SuccessfulAttempt;
writeCompletedTilesPersistentCache(); writeCompletedTilesPersistentCache();
} }
void SGTerraSync::WorkerThread::updated(SyncItem item, bool isNewDirectory) void SGTerraSync::WorkerThread::updated(SyncItem item, bool isNewDirectory)
{ {
time_t now = time(0); {
_consecutive_errors = 0; SGGuard<SGMutex> g(_stateLock);
_success_count++; time_t now = time(0);
SG_LOG(SG_TERRASYNC,SG_INFO, _state._consecutive_errors = 0;
"Successfully synchronized directory '" << item._dir << "'"); _state._success_count++;
SG_LOG(SG_TERRASYNC,SG_INFO,
"Successfully synchronized directory '" << item._dir << "'");
item._status = SyncItem::Updated; item._status = SyncItem::Updated;
if (item._type == SyncItem::Tile) { if (item._type == SyncItem::Tile) {
_updated_tile_count++; _state._updated_tile_count++;
}
_freshTiles.push_back(item);
_completedTiles[ item._dir ] = now + UpdateInterval::SuccessfulAttempt;
} }
_freshTiles.push_back(item);
_completedTiles[ item._dir ] = now + UpdateInterval::SuccessfulAttempt;
_is_dirty = true;
writeCompletedTilesPersistentCache(); writeCompletedTilesPersistentCache();
} }
@@ -747,8 +812,7 @@ void SGTerraSync::shutdown()
void SGTerraSync::reinit() void SGTerraSync::reinit()
{ {
// do not reinit when enabled and we're already up and running // do not reinit when enabled and we're already up and running
if ((_terraRoot->getBoolValue("enabled",false))&& if ((_terraRoot->getBoolValue("enabled",false)) && _workerThread->isRunning())
(_workerThread->_active && _workerThread->_running))
{ {
return; return;
} }
@@ -769,7 +833,7 @@ void SGTerraSync::reinit()
} }
} }
_stalledNode->setBoolValue(_workerThread->_stalled); _stalledNode->setBoolValue(_workerThread->isStalled());
} }
void SGTerraSync::bind() void SGTerraSync::bind()
@@ -779,20 +843,6 @@ void SGTerraSync::bind()
} }
_bound = true; _bound = true;
_tiedProperties.Tie( _terraRoot->getNode("busy", true), (bool*) &_workerThread->_busy );
_tiedProperties.Tie( _terraRoot->getNode("active", true), (bool*) &_workerThread->_active );
_tiedProperties.Tie( _terraRoot->getNode("update-count", true), (int*) &_workerThread->_success_count );
_tiedProperties.Tie( _terraRoot->getNode("error-count", true), (int*) &_workerThread->_fail_count );
_tiedProperties.Tie( _terraRoot->getNode("tile-count", true), (int*) &_workerThread->_updated_tile_count );
_tiedProperties.Tie( _terraRoot->getNode("cache-hits", true), (int*) &_workerThread->_cache_hits );
_tiedProperties.Tie( _terraRoot->getNode("transfer-rate-bytes-sec", true), (int*) &_workerThread->_transfer_rate );
// use kbytes here because propety doesn't support 64-bit and we might conceivably
// download more than 2G in a single session
_tiedProperties.Tie( _terraRoot->getNode("downloaded-kbytes", true), (int*) &_workerThread->_total_kb_downloaded );
_tiedProperties.Tie( _terraRoot->getNode("pending-kbytes", true), (int*) &_workerThread->_totalKbPending );
_terraRoot->getNode("busy", true)->setAttribute(SGPropertyNode::WRITE,false); _terraRoot->getNode("busy", true)->setAttribute(SGPropertyNode::WRITE,false);
_terraRoot->getNode("active", true)->setAttribute(SGPropertyNode::WRITE,false); _terraRoot->getNode("active", true)->setAttribute(SGPropertyNode::WRITE,false);
@@ -801,9 +851,10 @@ void SGTerraSync::bind()
_terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false); _terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false);
_terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); _terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
_terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); _terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
// stalled is used as a signal handler (to connect listeners triggering GUI pop-ups) // stalled is used as a signal handler (to connect listeners triggering GUI pop-ups)
_stalledNode = _terraRoot->getNode("stalled", true); _stalledNode = _terraRoot->getNode("stalled", true);
_stalledNode->setBoolValue(_workerThread->_stalled); _stalledNode->setBoolValue(_workerThread->isStalled());
_stalledNode->setAttribute(SGPropertyNode::PRESERVE,true); _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true);
} }
@@ -821,34 +872,28 @@ void SGTerraSync::unbind()
void SGTerraSync::update(double) void SGTerraSync::update(double)
{ {
static SGBucket bucket; TerrasyncThreadState copiedState(_workerThread->threadsafeCopyState());
if (_workerThread->isDirty())
_terraRoot->setBoolValue("busy", copiedState._busy);
_terraRoot->setIntValue("update-count", copiedState._success_count);
_terraRoot->setIntValue("error-count", copiedState._fail_count);
_terraRoot->setIntValue("tile-count", copiedState._updated_tile_count);
_terraRoot->setIntValue("cache-hits", copiedState._cache_hits);
_terraRoot->setIntValue("transfer-rate-bytes-sec", copiedState._transfer_rate);
_terraRoot->setIntValue("downloaded-kbytes", copiedState._total_kb_downloaded);
_terraRoot->setIntValue("pending-kbytes", copiedState._totalKbPending);
_stalledNode->setBoolValue(_workerThread->isStalled());
_terraRoot->setBoolValue("active", _workerThread->isRunning());
while (_workerThread->hasNewTiles())
{ {
if (!_workerThread->_active) SyncItem next = _workerThread->getNewTile();
{
if (_workerThread->_stalled) if ((next._type == SyncItem::Tile) || (next._type == SyncItem::AIData)) {
{ _activeTileDirs.erase(next._dir);
SG_LOG(SG_TERRASYNC,SG_ALERT,
"Automatic scenery download/synchronization stalled. Too many errors.");
}
else
{
// not really an alert - just always show this message
SG_LOG(SG_TERRASYNC,SG_ALERT,
"Automatic scenery download/synchronization has stopped.");
}
_stalledNode->setBoolValue(_workerThread->_stalled);
} }
} // of freshly synced items
while (_workerThread->hasNewTiles())
{
SyncItem next = _workerThread->getNewTile();
if ((next._type == SyncItem::Tile) || (next._type == SyncItem::AIData)) {
_activeTileDirs.erase(next._dir);
}
} // of freshly synced items
}
} }
bool SGTerraSync::isIdle() {return _workerThread->isIdle();} bool SGTerraSync::isIdle() {return _workerThread->isIdle();}
@@ -897,7 +942,7 @@ bool SGTerraSync::scheduleTile(const SGBucket& bucket)
bool SGTerraSync::isTileDirPending(const std::string& sceneryDir) const bool SGTerraSync::isTileDirPending(const std::string& sceneryDir) const
{ {
if (!_workerThread->_running) { if (!_workerThread->isRunning()) {
return false; return false;
} }
@@ -926,7 +971,7 @@ void SGTerraSync::scheduleDataDir(const std::string& dataDir)
bool SGTerraSync::isDataDirPending(const std::string& dataDir) const bool SGTerraSync::isDataDirPending(const std::string& dataDir) const
{ {
if (!_workerThread->_running) { if (!_workerThread->isRunning()) {
return false; return false;
} }