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
{
NavDataCache* cache = NavDataCache::instance();
if (cache->isReadOnly()) {
return;
}
SGPath path;
if (!XMLLoader::findAirportData(ident(), "threshold", path)) {
return; // no XML threshold data
@ -636,6 +640,10 @@ void FGAirport::validateTowerData() const
mTowerDataLoaded = true;
NavDataCache* cache = NavDataCache::instance();
if (cache->isReadOnly()) {
return;
}
SGPath path;
if (!XMLLoader::findAirportData(ident(), "twr", path)) {
return; // no XML tower data
@ -684,6 +692,10 @@ bool FGAirport::validateILSData()
mILSDataLoaded = true;
NavDataCache* cache = NavDataCache::instance();
if (cache->isReadOnly()) {
return false;
}
SGPath path;
if (!XMLLoader::findAirportData(ident(), "ils", path)) {
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");
if (logClass == "terrasync") {
simgear::SGTerraSync* tsync = (simgear::SGTerraSync*) globals->get_subsystem("terrasync");
obj->setBuffer(tsync->log());
if (tsync) {
obj->setBuffer(tsync->log());
}
} else {
FGNasalSys* nasal = (FGNasalSys*) globals->get_subsystem("nasal");
obj->setBuffer(nasal->log());

View file

@ -25,17 +25,19 @@
# include <config.h>
#endif
#include <simgear/compiler.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // strcmp()
#ifdef _WIN32
#if defined(SG_WINDOWS)
# include <io.h> // isatty()
# include <process.h> // _getpid()
# include <Windows.h>
# define isatty _isatty
#endif
#include <simgear/compiler.h>
#include <string>
#include <boost/algorithm/string/compare.hpp>
#include <boost/algorithm/string/predicate.hpp>
@ -407,7 +409,7 @@ static SGPath platformDefaultDataPath()
}
#endif
void fgInitHome()
bool fgInitHome()
{
SGPath dataPath = SGPath::fromEnv("FG_HOME", platformDefaultDataPath());
globals->set_fg_home(dataPath.c_str());
@ -416,6 +418,66 @@ void fgInitHome()
if (!fgHome.exists()) {
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)

View file

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

View file

@ -96,6 +96,39 @@ static void fgMainLoop( void )
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()
{
@ -126,36 +159,8 @@ static void fgIdleFunction ( void ) {
}
} else if ( idle_state == 2 ) {
// 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);
initTerrasync();
idle_state++;
fgSplashProgress("loading-nav-dat");
@ -325,10 +330,14 @@ int fgMainInit( int argc, char **argv ) {
sglog().setLogLevels( SG_ALL, SG_ALERT );
globals = new FGGlobals;
fgInitHome();
if (!fgInitHome()) {
return EXIT_FAILURE;
}
// now home is initialised, we can log to a file inside it
logToFile();
if (!fgGetBool("/sim/fghome-readonly")) {
// now home is initialised, we can log to a file inside it
logToFile();
}
std::string version;
#ifdef FLIGHTGEAR_VERSION

View file

@ -1409,6 +1409,7 @@ struct OptionDesc {
{"enable-fullscreen", false, OPTION_BOOL, "/sim/startup/fullscreen", true, "", 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 },
{"read-only", false, OPTION_BOOL, "/sim/fghome-readonly", true, "", 0 },
{"ignore-autosave", false, OPTION_FUNC, "", false, "", fgOptIgnoreAutosave },
{"restore-defaults", false, OPTION_BOOL, "/sim/startup/restore-defaults", true, "", 0 },
{"shading-flat", false, OPTION_BOOL, "/sim/rendering/shading", false, "", 0 },

View file

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

View file

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