1
0
Fork 0

Multiple-instance support.

Write PID file to FG_HOME, use this to detect multiple launches.
When this situation is detected, set a marker property and place various
objects into read-only mode, such as the NavCache and TerraSync.

PID file is created using open+unlink semantics on POSIX, and 
DELETE_ON_CLOSE on Windows, so it will be removed when fgfs exits,
even if killed or crashes.
This commit is contained in:
James Turner 2013-11-14 16:48:14 +00:00
parent 13d5fd4c25
commit 0cda3cbfb2
8 changed files with 153 additions and 47 deletions

View file

@ -556,6 +556,10 @@ void FGAirport::loadProcedures() const
void FGAirport::loadSceneryDefinitions() const void FGAirport::loadSceneryDefinitions() const
{ {
NavDataCache* cache = NavDataCache::instance(); NavDataCache* cache = NavDataCache::instance();
if (cache->isReadOnly()) {
return;
}
SGPath path; SGPath path;
if (!XMLLoader::findAirportData(ident(), "threshold", path)) { if (!XMLLoader::findAirportData(ident(), "threshold", path)) {
return; // no XML threshold data return; // no XML threshold data
@ -636,6 +640,10 @@ void FGAirport::validateTowerData() const
mTowerDataLoaded = true; mTowerDataLoaded = true;
NavDataCache* cache = NavDataCache::instance(); NavDataCache* cache = NavDataCache::instance();
if (cache->isReadOnly()) {
return;
}
SGPath path; SGPath path;
if (!XMLLoader::findAirportData(ident(), "twr", path)) { if (!XMLLoader::findAirportData(ident(), "twr", path)) {
return; // no XML tower data return; // no XML tower data
@ -684,6 +692,10 @@ bool FGAirport::validateILSData()
mILSDataLoaded = true; mILSDataLoaded = true;
NavDataCache* cache = NavDataCache::instance(); NavDataCache* cache = NavDataCache::instance();
if (cache->isReadOnly()) {
return false;
}
SGPath path; SGPath path;
if (!XMLLoader::findAirportData(ident(), "ils", path)) { if (!XMLLoader::findAirportData(ident(), "ils", path)) {
return false; // no XML tower data return false; // no XML tower data

View file

@ -1057,7 +1057,9 @@ FGPUIDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeigh
string logClass = props->getStringValue("logclass"); string logClass = props->getStringValue("logclass");
if (logClass == "terrasync") { if (logClass == "terrasync") {
simgear::SGTerraSync* tsync = (simgear::SGTerraSync*) globals->get_subsystem("terrasync"); simgear::SGTerraSync* tsync = (simgear::SGTerraSync*) globals->get_subsystem("terrasync");
if (tsync) {
obj->setBuffer(tsync->log()); obj->setBuffer(tsync->log());
}
} else { } else {
FGNasalSys* nasal = (FGNasalSys*) globals->get_subsystem("nasal"); FGNasalSys* nasal = (FGNasalSys*) globals->get_subsystem("nasal");
obj->setBuffer(nasal->log()); obj->setBuffer(nasal->log());

View file

@ -25,17 +25,19 @@
# include <config.h> # include <config.h>
#endif #endif
#include <simgear/compiler.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> // strcmp() #include <string.h> // strcmp()
#ifdef _WIN32 #if defined(SG_WINDOWS)
# include <io.h> // isatty() # include <io.h> // isatty()
# include <process.h> // _getpid()
# include <Windows.h>
# define isatty _isatty # define isatty _isatty
#endif #endif
#include <simgear/compiler.h>
#include <string> #include <string>
#include <boost/algorithm/string/compare.hpp> #include <boost/algorithm/string/compare.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
@ -407,7 +409,7 @@ static SGPath platformDefaultDataPath()
} }
#endif #endif
void fgInitHome() bool fgInitHome()
{ {
SGPath dataPath = SGPath::fromEnv("FG_HOME", platformDefaultDataPath()); SGPath dataPath = SGPath::fromEnv("FG_HOME", platformDefaultDataPath());
globals->set_fg_home(dataPath.c_str()); globals->set_fg_home(dataPath.c_str());
@ -416,6 +418,66 @@ void fgInitHome()
if (!fgHome.exists()) { if (!fgHome.exists()) {
fgHome.create(0755); fgHome.create(0755);
} }
if (!fgHome.exists()) {
flightgear::fatalMessageBox("Problem setting up user data",
"Unable to create the user-data storage folder at: '"
+ dataPath.str() + "'");
return false;
}
if (fgGetBool("/sim/fghome-readonly", false)) {
// user / config forced us into readonly mode, fine
SG_LOG(SG_GENERAL, SG_INFO, "Running with FG_HOME readonly");
return true;
}
// write our PID, and check writeability
SGPath pidPath(dataPath, "fgfs.pid");
if (pidPath.exists()) {
SG_LOG(SG_GENERAL, SG_INFO, "flightgear instance already running, switching to FG_HOME read-only.");
// set a marker property so terrasync/navcache don't try to write
// from secondary instances
fgSetBool("/sim/fghome-readonly", true);
return true;
}
char buf[16];
bool result = false;
#if defined(SG_WINDOWS)
size_t len = snprintf(buf, 16, "%d", _getpid());
HANDLE f = CreateFileA(pidPath.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, /* sharing */
NULL, /* security attributes */
CREATE_NEW, /* error if already exists */
FILE_FLAG_DELETE_ON_CLOSE,
NULL /* template */);
result = (f != INVALID_HANDLE_VALUE);
if (result) {
DWORD written;
WriteFile(f, buf, len, &written, NULL /* overlapped */);
}
#else
// POSIX, do open+unlink trick to the file is deleted on exit, even if we
// crash or exit(-1)
size_t len = snprintf(buf, 16, "%d", getpid());
int fd = ::open(pidPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0644);
if (fd >= 0) {
::write(fd, buf, len);
::unlink(pidPath.c_str()); // delete file when app quits
result = true;
}
fgSetBool("/sim/fghome-readonly", false);
#endif
if (!result) {
flightgear::fatalMessageBox("File permissions problem",
"Can't write to user-data storage folder, check file permissions and FG_HOME.",
"User-data at:" + dataPath.str());
}
return result;
} }
// Read in configuration (file and command line) // Read in configuration (file and command line)

View file

@ -34,7 +34,7 @@ class SGPropertyNode;
// Return the current base package version // Return the current base package version
std::string fgBasePackageVersion(); std::string fgBasePackageVersion();
void fgInitHome(); bool fgInitHome();
// Read in configuration (file and command line) // Read in configuration (file and command line)
int fgInitConfig ( int argc, char **argv ); int fgInitConfig ( int argc, char **argv );

View file

@ -96,6 +96,39 @@ static void fgMainLoop( void )
simgear::AtomicChangeListener::fireChangeListeners(); simgear::AtomicChangeListener::fireChangeListeners();
} }
static void initTerrasync()
{
if (fgGetBool("/sim/fghome-readonly", false)) {
return;
}
// start TerraSync up now, so it can be synchronizing shared models
// and airports data in parallel with a nav-cache rebuild.
SGPath tsyncCache(globals->get_fg_home());
tsyncCache.append("terrasync-cache.xml");
// wipe the cache file if requested
if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) {
SG_LOG(SG_GENERAL, SG_INFO, "restore-defaults requested, wiping terrasync update cache at " <<
tsyncCache);
if (tsyncCache.exists()) {
tsyncCache.remove();
}
}
fgSetString("/sim/terrasync/cache-path", tsyncCache.c_str());
simgear::SGTerraSync* terra_sync = new simgear::SGTerraSync();
terra_sync->setRoot(globals->get_props());
globals->add_subsystem("terrasync", terra_sync);
terra_sync->bind();
terra_sync->init();
// add the terrasync root as a data path so data can be retrieved from it
std::string terraSyncDir(fgGetString("/sim/terrasync/scenery-dir"));
globals->append_data_path(terraSyncDir);
}
static void registerMainLoop() static void registerMainLoop()
{ {
@ -127,35 +160,7 @@ static void fgIdleFunction ( void ) {
} else if ( idle_state == 2 ) { } else if ( idle_state == 2 ) {
// start TerraSync up now, so it can be synchronizing shared models initTerrasync();
// and airports data in parallel with a nav-cache rebuild.
SGPath tsyncCache(globals->get_fg_home());
tsyncCache.append("terrasync-cache.xml");
// wipe the cache file if requested
if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) {
SG_LOG(SG_GENERAL, SG_INFO, "restore-defaults requested, wiping terrasync update cache at " <<
tsyncCache);
if (tsyncCache.exists()) {
tsyncCache.remove();
}
}
fgSetString("/sim/terrasync/cache-path", tsyncCache.c_str());
simgear::SGTerraSync* terra_sync = new simgear::SGTerraSync();
terra_sync->setRoot(globals->get_props());
globals->add_subsystem("terrasync", terra_sync);
terra_sync->bind();
terra_sync->init();
// add the terrasync root as a data path so data can be retrieved from it
std::string terraSyncDir(fgGetString("/sim/terrasync/scenery-dir"));
globals->append_data_path(terraSyncDir);
idle_state++; idle_state++;
fgSplashProgress("loading-nav-dat"); fgSplashProgress("loading-nav-dat");
@ -325,10 +330,14 @@ int fgMainInit( int argc, char **argv ) {
sglog().setLogLevels( SG_ALL, SG_ALERT ); sglog().setLogLevels( SG_ALL, SG_ALERT );
globals = new FGGlobals; globals = new FGGlobals;
fgInitHome(); if (!fgInitHome()) {
return EXIT_FAILURE;
}
if (!fgGetBool("/sim/fghome-readonly")) {
// now home is initialised, we can log to a file inside it // now home is initialised, we can log to a file inside it
logToFile(); logToFile();
}
std::string version; std::string version;
#ifdef FLIGHTGEAR_VERSION #ifdef FLIGHTGEAR_VERSION

View file

@ -1409,6 +1409,7 @@ struct OptionDesc {
{"enable-fullscreen", false, OPTION_BOOL, "/sim/startup/fullscreen", true, "", 0 }, {"enable-fullscreen", false, OPTION_BOOL, "/sim/startup/fullscreen", true, "", 0 },
{"disable-save-on-exit", false, OPTION_BOOL, "/sim/startup/save-on-exit", false, "", 0 }, {"disable-save-on-exit", false, OPTION_BOOL, "/sim/startup/save-on-exit", false, "", 0 },
{"enable-save-on-exit", false, OPTION_BOOL, "/sim/startup/save-on-exit", true, "", 0 }, {"enable-save-on-exit", false, OPTION_BOOL, "/sim/startup/save-on-exit", true, "", 0 },
{"read-only", false, OPTION_BOOL, "/sim/fghome-readonly", true, "", 0 },
{"ignore-autosave", false, OPTION_FUNC, "", false, "", fgOptIgnoreAutosave }, {"ignore-autosave", false, OPTION_FUNC, "", false, "", fgOptIgnoreAutosave },
{"restore-defaults", false, OPTION_BOOL, "/sim/startup/restore-defaults", true, "", 0 }, {"restore-defaults", false, OPTION_BOOL, "/sim/startup/restore-defaults", true, "", 0 },
{"shading-flat", false, OPTION_BOOL, "/sim/rendering/shading", false, "", 0 }, {"shading-flat", false, OPTION_BOOL, "/sim/rendering/shading", false, "", 0 },

View file

@ -58,6 +58,7 @@
#include <simgear/threads/SGGuard.hxx> #include <simgear/threads/SGGuard.hxx>
#include <Main/globals.hxx> #include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Main/options.hxx> #include <Main/options.hxx>
#include "markerbeacon.hxx" #include "markerbeacon.hxx"
#include "navrecord.hxx" #include "navrecord.hxx"
@ -209,6 +210,7 @@ public:
outer(o), outer(o),
db(NULL), db(NULL),
path(p), path(p),
readOnly(false),
cacheHits(0), cacheHits(0),
cacheMisses(0), cacheMisses(0),
transactionLevel(0), transactionLevel(0),
@ -225,12 +227,14 @@ public:
{ {
SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache at:" << path); SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache at:" << path);
// see http://code.google.com/p/flightgear-bugs/issues/detail?id=1055 readOnly = fgGetBool("/sim/fghome-readonly", false);
// for the logic here. Sigh.
std::string pathUtf8 = simgear::strutils::convertWindowsLocal8BitToUtf8(path.str());
sqlite3_open_v2(pathUtf8.c_str(), &db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
int openFlags = readOnly ? SQLITE_OPEN_READONLY :
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
// see http://code.google.com/p/flightgear-bugs/issues/detail?id=1055
// for the UTF8 / path logic here
std::string pathUtf8 = simgear::strutils::convertWindowsLocal8BitToUtf8(path.str());
sqlite3_open_v2(pathUtf8.c_str(), &db, openFlags, NULL);
sqlite3_stmt_ptr checkTables = sqlite3_stmt_ptr checkTables =
prepare("SELECT count(*) FROM sqlite_master WHERE name='properties'"); prepare("SELECT count(*) FROM sqlite_master WHERE name='properties'");
@ -240,7 +244,7 @@ public:
execSelect(checkTables); execSelect(checkTables);
bool didCreate = false; bool didCreate = false;
if (sqlite3_column_int(checkTables, 0) == 0) { if (!readOnly && (sqlite3_column_int(checkTables, 0) == 0)) {
SG_LOG(SG_NAVCACHE, SG_INFO, "will create tables"); SG_LOG(SG_NAVCACHE, SG_INFO, "will create tables");
initTables(); initTables();
didCreate = true; didCreate = true;
@ -858,6 +862,7 @@ public:
NavDataCache* outer; NavDataCache* outer;
sqlite3* db; sqlite3* db;
SGPath path; SGPath path;
bool readOnly;
/// the actual cache of ID -> instances. This holds an owning reference, /// the actual cache of ID -> instances. This holds an owning reference,
/// so once items are in the cache they will never be deleted until /// so once items are in the cache they will never be deleted until
@ -1048,8 +1053,12 @@ NavDataCache::NavDataCache()
SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "NavCache: init failed:" << e.what() SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "NavCache: init failed:" << e.what()
<< " (attempt " << t << ")"); << " (attempt " << t << ")");
d.reset(); d.reset();
// only wipe the existing if not readonly
if (!fgGetBool("/sim/fghome-readonly", false)) {
homePath.remove(); homePath.remove();
} }
}
} // of retry loop } // of retry loop
double RADIUS_EARTH_M = 7000 * 1000.0; // 7000km is plenty double RADIUS_EARTH_M = 7000 * 1000.0; // 7000km is plenty
@ -1097,6 +1106,10 @@ NavDataCache* NavDataCache::instance()
bool NavDataCache::isRebuildRequired() bool NavDataCache::isRebuildRequired()
{ {
if (d->readOnly) {
return false;
}
if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) { if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) {
SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: restore-defaults requested, will rebuild cache"); SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: restore-defaults requested, will rebuild cache");
return true; return true;
@ -2148,6 +2161,11 @@ void NavDataCache::dropGroundnetFor(PositionedID aAirport)
d->execUpdate(q); d->execUpdate(q);
} }
bool NavDataCache::isReadOnly() const
{
return d->readOnly;
}
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// Transaction RAII object // Transaction RAII object

View file

@ -267,6 +267,8 @@ public:
NavDataCache* _instance; NavDataCache* _instance;
bool _committed; bool _committed;
}; };
bool isReadOnly() const;
private: private:
NavDataCache(); NavDataCache();