auto shutdown of sandbox when interface quits

This commit is contained in:
Brad Hefta-Gaub 2016-10-15 14:37:25 -07:00
parent fb47b42ea5
commit d46170bec0
12 changed files with 329 additions and 84 deletions

View file

@ -94,6 +94,7 @@
#include <RenderShadowTask.h>
#include <RenderDeferredTask.h>
#include <ResourceCache.h>
#include <SandboxUtils.h>
#include <SceneScriptingInterface.h>
#include <ScriptEngines.h>
#include <ScriptCache.h>
@ -415,8 +416,6 @@ bool setupEssentials(int& argc, char** argv) {
static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
bool previousSessionCrashed = CrashHandler::checkForResetSettings(suppressPrompt);
CrashHandler::writeRunningMarkerFiler();
qAddPostRoutine(CrashHandler::deleteRunningMarkerFile);
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
@ -504,8 +503,11 @@ Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
Setting::Handle<int> sessionRunTime{ "sessionRunTime", 0 };
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) :
QApplication(argc, argv),
_shouldRunServer(runServer),
_runServerPath(runServerPathOption),
_runningMarker(this, RUNNING_MARKER_FILENAME),
_window(new MainWindow(desktop())),
_sessionRunTimer(startupTimer),
_previousSessionCrashed(setupEssentials(argc, argv)),
@ -529,7 +531,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
_lastFaceTrackerUpdate(0)
{
_runningMarker.startRunningMarker();
PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care
PluginManager::getInstance()->setContainer(pluginContainer);
@ -575,6 +577,31 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
qCDebug(interfaceapp) << "[VERSION] We will use DEVELOPMENT global services.";
#endif
bool wantsSandboxRunning = shouldRunServer();
bool determinedSandboxState = false;
bool sandboxRunning = false;
SandboxUtils sandboxUtils;
sandboxUtils.ifLocalSandboxRunningElse([&]() {
qDebug() << "Home sandbox appears to be running.....";
determinedSandboxState = true;
sandboxRunning = true;
}, [&]() {
qDebug() << "Home sandbox does not appear to be running....";
determinedSandboxState = true;
sandboxRunning = false;
if (wantsSandboxRunning) {
QString contentPath = getRunServerPath();
SandboxUtils::runLocalSandbox(contentPath, true, RUNNING_MARKER_FILENAME);
}
});
quint64 MAX_WAIT_TIME = USECS_PER_SECOND * 4;
auto startWaiting = usecTimestampNow();
while (!determinedSandboxState && (usecTimestampNow() - startWaiting <= MAX_WAIT_TIME)) {
QCoreApplication::processEvents();
usleep(USECS_PER_MSEC * 50); // 20hz
}
_bookmarks = new Bookmarks(); // Before setting up the menu
@ -1299,7 +1326,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
const QString TUTORIAL_PATH = "/tutorial_begin";
if (shouldGoToTutorial) {
DependencyManager::get<AddressManager>()->ifLocalSandboxRunningElse([=]() {
sandboxUtils.ifLocalSandboxRunningElse([=]() {
qDebug() << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
}, [=]() {
@ -1324,7 +1351,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// If this is a first run we short-circuit the address passed in
if (isFirstRun) {
if (hasHMDAndHandControllers) {
DependencyManager::get<AddressManager>()->ifLocalSandboxRunningElse([=]() {
sandboxUtils.ifLocalSandboxRunningElse([=]() {
qDebug() << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox();
}, [=]() {

View file

@ -46,6 +46,8 @@
#include <ThreadSafeValueCache.h>
#include <shared/FileLogger.h>
#include <RunningMarker.h>
#include "avatar/MyAvatar.h"
#include "Bookmarks.h"
#include "Camera.h"
@ -87,6 +89,8 @@ static const UINT UWM_SHOW_APPLICATION =
RegisterWindowMessage("UWM_SHOW_APPLICATION_{71123FD6-3DA8-4DC1-9C27-8A12A6250CBA}_" + qgetenv("USERNAME"));
#endif
static const QString RUNNING_MARKER_FILENAME = "Interface.running";
class Application;
#if defined(qApp)
#undef qApp
@ -103,7 +107,16 @@ class Application : public QApplication,
// TODO? Get rid of those
friend class OctreePacketProcessor;
private:
bool _shouldRunServer { false };
QString _runServerPath;
RunningMarker _runningMarker;
public:
// startup related getter/setters
bool shouldRunServer() const { return _shouldRunServer; }
bool hasRunServerPath() const { return !_runServerPath.isEmpty(); }
QString getRunServerPath() const { return _runServerPath; }
// virtual functions required for PluginContainer
virtual ui::Menu* getPrimaryMenu() override;
@ -127,7 +140,7 @@ public:
static void initPlugins(const QStringList& arguments);
static void shutdownPlugins();
Application(int& argc, char** argv, QElapsedTimer& startup_time);
Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runServer, QString runServerPathOption);
~Application();
void postLambdaEvent(std::function<void()> f) override;

View file

@ -23,9 +23,10 @@
#include <QVBoxLayout>
#include <QtCore/QUrl>
#include "Application.h"
#include "Menu.h"
static const QString RUNNING_MARKER_FILENAME = "Interface.running";
#include <RunningMarker.h>
bool CrashHandler::checkForResetSettings(bool suppressPrompt) {
QSettings::setDefaultFormat(QSettings::IniFormat);
@ -39,7 +40,7 @@ bool CrashHandler::checkForResetSettings(bool suppressPrompt) {
// If option does not exist in Interface.ini so assume default behavior.
bool displaySettingsResetOnCrash = !displayCrashOptions.isValid() || displayCrashOptions.toBool();
QFile runningMarkerFile(runningMarkerFilePath());
QFile runningMarkerFile(RunningMarker::getMarkerFilePath(RUNNING_MARKER_FILENAME));
bool wasLikelyCrash = runningMarkerFile.exists();
if (suppressPrompt) {
@ -161,20 +162,3 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
}
}
void CrashHandler::writeRunningMarkerFiler() {
QFile runningMarkerFile(runningMarkerFilePath());
if (!runningMarkerFile.exists()) {
runningMarkerFile.open(QIODevice::WriteOnly);
runningMarkerFile.close();
}
}
void CrashHandler::deleteRunningMarkerFile() {
QFile runningMarkerFile(runningMarkerFilePath());
if (runningMarkerFile.exists()) {
runningMarkerFile.remove();
}
}
const QString CrashHandler::runningMarkerFilePath() {
return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + RUNNING_MARKER_FILENAME;
}

View file

@ -19,9 +19,6 @@ class CrashHandler {
public:
static bool checkForResetSettings(bool suppressPrompt = false);
static void writeRunningMarkerFiler();
static void deleteRunningMarkerFile();
private:
enum Action {
DELETE_INTERFACE_INI,
@ -31,8 +28,6 @@ private:
static Action promptUserForAction(bool showCrashMessage);
static void handleCrash(Action action);
static const QString runningMarkerFilePath();
};
#endif // hifi_CrashHandler_h

View file

@ -37,6 +37,8 @@
#include <CrashReporter.h>
#endif
int main(int argc, const char* argv[]) {
#if HAS_BUGSPLAT
static QString BUG_SPLAT_DATABASE = "interface_alpha";
@ -128,22 +130,9 @@ int main(int argc, const char* argv[]) {
parser.addOption(runServerOption);
parser.addOption(serverContentPathOption);
parser.parse(arguments);
if (parser.isSet(runServerOption)) {
QString applicationDirPath = QFileInfo(arguments[0]).path();
QString serverPath = applicationDirPath + "/server-console/server-console.exe";
qDebug() << "Application dir path is: " << applicationDirPath;
qDebug() << "Server path is: " << serverPath;
QStringList args;
if (parser.isSet(serverContentPathOption)) {
QString serverContentPath = QFileInfo(arguments[0]).path() + "/" + parser.value(serverContentPathOption);
args << "--" << "--contentPath" << serverContentPath;
}
qDebug() << QFileInfo(arguments[0]).path();
qDebug() << QProcess::startDetached(serverPath, args);
// Sleep a short amount of time to give the server a chance to start
usleep(2000000);
}
bool runServer = parser.isSet(runServerOption);
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
QElapsedTimer startupTime;
startupTime.start();
@ -166,10 +155,11 @@ int main(int argc, const char* argv[]) {
SteamClient::init();
int exitCode;
{
QSettings::setDefaultFormat(QSettings::IniFormat);
Application app(argc, const_cast<char**>(argv), startupTime);
Application app(argc, const_cast<char**>(argv), startupTime, runServer, serverContentPathOptionValue);
// If we failed the OpenGLVersion check, log it.
if (override) {
@ -223,7 +213,6 @@ int main(int argc, const char* argv[]) {
QTranslator translator;
translator.load("i18n/interface_en");
app.installTranslator(&translator);
qCDebug(interfaceapp, "Created QT Application.");
exitCode = app.exec();
server.close();

View file

@ -831,32 +831,3 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) {
}
}
void AddressManager::ifLocalSandboxRunningElse(std::function<void()> localSandboxRunningDoThis,
std::function<void()> localSandboxNotRunningDoThat) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL);
sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(sandboxStatus);
connect(reply, &QNetworkReply::finished, this, [reply, localSandboxRunningDoThis, localSandboxNotRunningDoThat]() {
auto statusData = reply->readAll();
auto statusJson = QJsonDocument::fromJson(statusData);
if (!statusJson.isEmpty()) {
auto statusObject = statusJson.object();
auto serversValue = statusObject.value("servers");
if (!serversValue.isUndefined() && serversValue.isObject()) {
auto serversObject = serversValue.toObject();
auto serversCount = serversObject.size();
const int MINIMUM_EXPECTED_SERVER_COUNT = 5;
if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) {
localSandboxRunningDoThis();
return;
}
}
}
localSandboxNotRunningDoThat();
});
}

View file

@ -25,7 +25,6 @@
const QString HIFI_URL_SCHEME = "hifi";
const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome";
const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost";
const QString SANDBOX_STATUS_URL = "http://localhost:60332/status";
const QString INDEX_PATH = "/";
const QString GET_PLACE = "/api/v1/places/%1";
@ -78,11 +77,6 @@ public:
const QStack<QUrl>& getBackStack() const { return _backStack; }
const QStack<QUrl>& getForwardStack() const { return _forwardStack; }
/// determines if the local sandbox is likely running. It does not account for custom setups, and is only
/// intended to detect the standard local sandbox install.
void ifLocalSandboxRunningElse(std::function<void()> localSandboxRunningDoThis,
std::function<void()> localSandboxNotRunningDoThat);
public slots:
void handleLookupString(const QString& lookupString, bool fromSuggestions = false);

View file

@ -0,0 +1,96 @@
//
// SandboxUtils.cpp
// libraries/networking/src
//
// Created by Brad Hefta-Gaub on 2016-10-15.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QDataStream>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QProcess>
#include <QStandardPaths>
#include <QThread>
#include <QTimer>
#include <NumericalConstants.h>
#include <SharedUtil.h>
#include <RunningMarker.h>
#include "SandboxUtils.h"
#include "NetworkAccessManager.h"
void SandboxUtils::ifLocalSandboxRunningElse(std::function<void()> localSandboxRunningDoThis,
std::function<void()> localSandboxNotRunningDoThat) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL);
sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(sandboxStatus);
connect(reply, &QNetworkReply::finished, this, [reply, localSandboxRunningDoThis, localSandboxNotRunningDoThat]() {
if (reply->error() == QNetworkReply::NoError) {
auto statusData = reply->readAll();
auto statusJson = QJsonDocument::fromJson(statusData);
if (!statusJson.isEmpty()) {
auto statusObject = statusJson.object();
auto serversValue = statusObject.value("servers");
if (!serversValue.isUndefined() && serversValue.isObject()) {
auto serversObject = serversValue.toObject();
auto serversCount = serversObject.size();
const int MINIMUM_EXPECTED_SERVER_COUNT = 5;
if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) {
localSandboxRunningDoThis();
return;
}
}
}
}
localSandboxNotRunningDoThat();
});
}
void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName) {
QString applicationDirPath = QFileInfo(QCoreApplication::applicationFilePath()).path();
QString serverPath = applicationDirPath + "/server-console/server-console.exe";
qDebug() << "Application dir path is: " << applicationDirPath;
qDebug() << "Server path is: " << serverPath;
qDebug() << "autoShutdown: " << autoShutdown;
bool hasContentPath = !contentPath.isEmpty();
bool passArgs = autoShutdown || hasContentPath;
QStringList args;
if (passArgs) {
args << "--";
}
if (hasContentPath) {
QString serverContentPath = applicationDirPath + "/" + contentPath;
args << "--contentPath" << serverContentPath;
}
if (autoShutdown) {
QString interfaceRunningStateFile = RunningMarker::getMarkerFilePath(runningMarkerName);
args << "--shutdownWatcher" << interfaceRunningStateFile;
}
qDebug() << applicationDirPath;
qDebug() << "Launching sandbox with:" << args;
qDebug() << QProcess::startDetached(serverPath, args);
// Sleep a short amount of time to give the server a chance to start
usleep(2000000); /// do we really need this??
}

View file

@ -0,0 +1,32 @@
//
// SandboxUtils.h
// libraries/networking/src
//
// Created by Brad Hefta-Gaub on 2016-10-15.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_SandboxUtils_h
#define hifi_SandboxUtils_h
#include <functional>
#include <QtCore/QObject>
const QString SANDBOX_STATUS_URL = "http://localhost:60332/status";
class SandboxUtils : public QObject {
Q_OBJECT
public:
/// determines if the local sandbox is likely running. It does not account for custom setups, and is only
/// intended to detect the standard local sandbox install.
void ifLocalSandboxRunningElse(std::function<void()> localSandboxRunningDoThis,
std::function<void()> localSandboxNotRunningDoThat);
static void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName);
};
#endif // hifi_SandboxUtils_h

View file

@ -0,0 +1,73 @@
//
// RunningMarker.cpp
// libraries/shared/src
//
// Created by Brad Hefta-Gaub on 2016-10-16
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RunningMarker.h"
#include <QFile>
#include <QStandardPaths>
#include <QThread>
#include <QTimer>
#include "NumericalConstants.h"
#include "PathUtils.h"
RunningMarker::RunningMarker(QObject* parent, QString name) :
_parent(parent),
_name(name)
{
}
void RunningMarker::startRunningMarker() {
static const int RUNNING_STATE_CHECK_IN_MSECS = MSECS_PER_SECOND;
// start the nodeThread so its event loop is running
QThread* runningMarkerThread = new QThread(_parent);
runningMarkerThread->setObjectName("Running Marker Thread");
runningMarkerThread->start();
writeRunningMarkerFiler(); // write the first file, even before timer
QTimer* runningMarkerTimer = new QTimer(_parent);
QObject::connect(runningMarkerTimer, &QTimer::timeout, [=](){
writeRunningMarkerFiler();
});
runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS);
}
RunningMarker::~RunningMarker() {
deleteRunningMarkerFile();
}
void RunningMarker::writeRunningMarkerFiler() {
QFile runningMarkerFile(getFilePath());
// always write, even it it exists, so that it touches the files
if (runningMarkerFile.open(QIODevice::WriteOnly)) {
runningMarkerFile.close();
}
}
void RunningMarker::deleteRunningMarkerFile() {
QFile runningMarkerFile(getFilePath());
if (runningMarkerFile.exists()) {
runningMarkerFile.remove();
}
}
QString RunningMarker::getFilePath() {
return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name;
}
QString RunningMarker::getMarkerFilePath(QString name) {
return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + name;
}

View file

@ -0,0 +1,35 @@
//
// RunningMarker.h
// interface/src
//
// Created by David Rowe on 24 Aug 2015.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RunningMarker_h
#define hifi_RunningMarker_h
#include <QObject>
#include <QString>
class RunningMarker {
public:
RunningMarker(QObject* parent, QString name);
~RunningMarker();
void startRunningMarker();
QString getFilePath();
static QString getMarkerFilePath(QString name);
protected:
void writeRunningMarkerFiler();
void deleteRunningMarkerFile();
QObject* _parent { nullptr };
QString _name;
};
#endif // hifi_RunningMarker_h

View file

@ -128,6 +128,12 @@ function shutdown() {
}
}
function forcedShutdown() {
if (!isShuttingDown) {
shutdownCallback(0);
}
}
function shutdownCallback(idx) {
if (idx == 0 && !isShuttingDown) {
isShuttingDown = true;
@ -226,6 +232,7 @@ if (shouldQuit) {
// Check command line arguments to see how to find binaries
var argv = require('yargs').argv;
var pathFinder = require('./modules/path-finder.js');
var interfacePath = null;
@ -774,6 +781,7 @@ function maybeShowSplash() {
}
}
const trayIconOS = (osType == "Darwin") ? "osx" : "win";
var trayIcons = {};
trayIcons[ProcessGroupStates.STARTED] = "console-tray-" + trayIconOS + ".png";
@ -842,6 +850,34 @@ function onContentLoaded() {
// start the home server
homeServer.start();
}
// If we were launched with the shutdownWatcher option, then we need to watch for the interface app
// shutting down. The interface app will regularly update a running state file which we will check.
// If the file doesn't exist or stops updating for a significant amount of time, we will shut down.
if (argv.shutdownWatcher) {
console.log("Shutdown watcher requested... argv.shutdownWatcher:", argv.shutdownWatcher);
var MAX_TIME_SINCE_EDIT = 5000; // 5 seconds between updates
var firstAttemptToCheck = new Date().getTime();
var shutdownWatchInterval = setInterval(function(){
var stats = fs.stat(argv.shutdownWatcher,function(err, stats) {
if (err) {
var sinceFirstCheck = new Date().getTime() - firstAttemptToCheck;
if (sinceFirstCheck > MAX_TIME_SINCE_EDIT) {
console.log("Running state file is missing, assume interface has shutdown... shutting down snadbox.");
forcedShutdown();
clearTimeout(shutdownWatchInterval);
}
} else {
var sinceEdit = new Date().getTime() - stats.mtime.getTime();
if (sinceEdit > MAX_TIME_SINCE_EDIT) {
console.log("Running state of interface hasn't updated in MAX time... shutting down.");
forcedShutdown();
clearTimeout(shutdownWatchInterval);
}
}
});
}, 1000);
}
}