core/command: improve dead instance selection

Prints dead instances if they exist, as well as allowing dead instance
selection for a substring if no live instances exist.
This commit is contained in:
outfoxxed 2025-07-02 22:47:19 -07:00
parent 86591f122d
commit 0e6518a706
Signed by untrusted user: outfoxxed
GPG key ID: 4C88A185FB89301E
8 changed files with 86 additions and 24 deletions

View file

@ -3,12 +3,12 @@
#include <qdatastream.h> #include <qdatastream.h>
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) { QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
stream << info.instanceId << info.configPath << info.shellId << info.launchTime; stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid;
return stream; return stream;
} }
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) { QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime; stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid;
return stream; return stream;
} }

View file

@ -3,12 +3,14 @@
#include <qdatetime.h> #include <qdatetime.h>
#include <qlogging.h> #include <qlogging.h>
#include <qstring.h> #include <qstring.h>
#include <sys/types.h>
struct InstanceInfo { struct InstanceInfo {
QString instanceId; QString instanceId;
QString configPath; QString configPath;
QString shellId; QString shellId;
QDateTime launchTime; QDateTime launchTime;
pid_t pid = -1;
static InstanceInfo CURRENT; // NOLINT static InstanceInfo CURRENT; // NOLINT
}; };

View file

@ -9,6 +9,7 @@
#include <qdir.h> #include <qdir.h>
#include <qlogging.h> #include <qlogging.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qpair.h>
#include <qstandardpaths.h> #include <qstandardpaths.h>
#include <qtenvironmentvariables.h> #include <qtenvironmentvariables.h>
#include <qtversionchecks.h> #include <qtversionchecks.h>
@ -367,29 +368,30 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
return true; return true;
} }
QVector<InstanceLockInfo> QsPaths::collectInstances(const QString& path, bool fallbackDead) { QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
QsPaths::collectInstances(const QString& path) {
qCDebug(logPaths) << "Collecting instances from" << path; qCDebug(logPaths) << "Collecting instances from" << path;
auto instances = QVector<InstanceLockInfo>(); auto liveInstances = QVector<InstanceLockInfo>();
auto deadInstances = QVector<InstanceLockInfo>();
auto dir = QDir(path); auto dir = QDir(path);
InstanceLockInfo info; InstanceLockInfo info;
for (auto& entry: dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { for (auto& entry: dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
auto path = dir.filePath(entry); auto path = dir.filePath(entry);
if (QsPaths::checkLock(path, &info, fallbackDead)) { if (QsPaths::checkLock(path, &info, true)) {
if (fallbackDead && info.pid != -1) {
fallbackDead = false;
instances.clear();
}
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid " qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
<< info.pid << ") at " << path; << info.pid << ") at " << path;
instances.push_back(info); if (info.pid == -1) {
deadInstances.push_back(info);
} else {
liveInstances.push_back(info);
}
} else { } else {
qCDebug(logPaths) << "Skipped potential instance at" << path; qCDebug(logPaths) << "Skipped potential instance at" << path;
} }
} }
return instances; return qMakePair(liveInstances, deadInstances);
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <qdatetime.h> #include <qdatetime.h>
#include <qdir.h> #include <qdir.h>
#include <qpair.h>
#include <qtypes.h> #include <qtypes.h>
#include "instanceinfo.hpp" #include "instanceinfo.hpp"
@ -22,7 +23,8 @@ public:
static QString ipcPath(const QString& id); static QString ipcPath(const QString& id);
static bool static bool
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false); checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
static QVector<InstanceLockInfo> collectInstances(const QString& path, bool fallbackDead = false); static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
collectInstances(const QString& path);
QDir* baseRunDir(); QDir* baseRunDir();
QDir* shellRunDir(); QDir* shellRunDir();

View file

@ -3,6 +3,7 @@
#include <cerrno> #include <cerrno>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <utility>
#include <qconfig.h> #include <qconfig.h>
#include <qcontainerfwd.h> #include <qcontainerfwd.h>
@ -177,14 +178,33 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
} }
} else if (!cmd.instance.id->isEmpty()) { } else if (!cmd.instance.id->isEmpty()) {
path = basePath->filePath("by-pid"); path = basePath->filePath("by-pid");
auto instances = QsPaths::collectInstances(path, deadFallback); auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
instances.removeIf([&](const InstanceLockInfo& info) { liveInstances.removeIf([&](const InstanceLockInfo& info) {
return !info.instance.instanceId.startsWith(*cmd.instance.id); return !info.instance.instanceId.startsWith(*cmd.instance.id);
}); });
deadInstances.removeIf([&](const InstanceLockInfo& info) {
return !info.instance.instanceId.startsWith(*cmd.instance.id);
});
auto instances = liveInstances.isEmpty() && deadFallback ? deadInstances : liveInstances;
if (instances.isEmpty()) { if (instances.isEmpty()) {
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id; if (deadFallback) {
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
} else {
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
if (!deadInstances.isEmpty()) {
qCInfo(logBare) << "Some dead instances match:";
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
}
}
return -1; return -1;
} else if (instances.length() != 1) { } else if (instances.length() != 1) {
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id; qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
@ -208,14 +228,29 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
path = QDir(basePath->filePath("by-path")).filePath(pathId); path = QDir(basePath->filePath("by-path")).filePath(pathId);
auto instances = QsPaths::collectInstances(path, deadFallback); auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
auto instances = liveInstances;
if (instances.isEmpty() && deadFallback) {
instances = deadInstances;
}
sortInstances( sortInstances(
instances, instances,
cmd.config.newest || (!instances.empty() && instances.first().pid == -1) cmd.config.newest || (!instances.empty() && instances.first().pid == -1)
); );
if (instances.isEmpty()) { if (instances.isEmpty()) {
qCInfo(logBare) << "No running instances for" << configFilePath; if (liveInstances.isEmpty() && deadInstances.length() > 1) {
qCInfo(logBare) << "No running instances for" << configFilePath;
qCInfo(logBare) << "Dead instances:";
sortInstances(deadInstances, cmd.config.newest);
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
} else {
qCInfo(logBare) << "No running instances for" << configFilePath;
}
return -1; return -1;
} }
@ -276,7 +311,18 @@ int listInstances(CommandState& cmd) {
path = QDir(basePath->filePath("by-path")).filePath(pathId); path = QDir(basePath->filePath("by-path")).filePath(pathId);
} }
auto instances = QsPaths::collectInstances(path); auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
sortInstances(liveInstances, cmd.config.newest);
QList<InstanceLockInfo> instances;
if (cmd.instance.includeDead) {
sortInstances(deadInstances, cmd.config.newest);
instances = std::move(deadInstances);
instances.append(liveInstances);
} else {
instances = std::move(liveInstances);
}
if (instances.isEmpty()) { if (instances.isEmpty()) {
if (cmd.instance.all) { if (cmd.instance.all) {
@ -286,7 +332,6 @@ int listInstances(CommandState& cmd) {
qCInfo(logBare) << "Use --all to list all instances."; qCInfo(logBare) << "Use --all to list all instances.";
} }
} else { } else {
sortInstances(instances, cmd.config.newest);
if (cmd.output.json) { if (cmd.output.json) {
auto array = QJsonArray(); auto array = QJsonArray();
@ -295,7 +340,7 @@ int listInstances(CommandState& cmd) {
auto json = QJsonObject(); auto json = QJsonObject();
json["id"] = instance.instance.instanceId; json["id"] = instance.instance.instanceId;
json["pid"] = instance.pid; json["pid"] = instance.instance.pid;
json["shell_id"] = instance.instance.shellId; json["shell_id"] = instance.instance.shellId;
json["config_path"] = instance.instance.configPath; json["config_path"] = instance.instance.configPath;
json["launch_time"] = instance.instance.launchTime.toString(Qt::ISODate); json["launch_time"] = instance.instance.launchTime.toString(Qt::ISODate);
@ -319,12 +364,18 @@ int listInstances(CommandState& cmd) {
.arg(remMinutes) .arg(remMinutes)
.arg(remSeconds); .arg(remSeconds);
auto isDead = instance.pid == -1;
auto gray = !cmd.log.noColor && isDead;
qCInfo(logBare).noquote().nospace() qCInfo(logBare).noquote().nospace()
<< "Instance " << instance.instance.instanceId << ":\n" << (gray ? "\033[90m" : "") << "Instance " << instance.instance.instanceId
<< " Process ID: " << instance.pid << '\n' << (isDead ? " (dead)" : "") << ":\n"
<< " Process ID: " << instance.instance.pid << '\n'
<< " Shell ID: " << instance.instance.shellId << '\n' << " Shell ID: " << instance.instance.shellId << '\n'
<< " Config path: " << instance.instance.configPath << '\n' << " Config path: " << instance.instance.configPath << '\n'
<< " Launch time: " << launchTimeStr << " (running for " << runtimeStr << ")\n"; << " Launch time: " << launchTimeStr
<< (isDead ? "" : " (running for " + runtimeStr + ")") << '\n'
<< (gray ? "\033[0m" : "");
} }
} }
} }

View file

@ -128,6 +128,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
.configPath = args.configPath, .configPath = args.configPath,
.shellId = shellId, .shellId = shellId,
.launchTime = qs::Common::LAUNCH_TIME, .launchTime = qs::Common::LAUNCH_TIME,
.pid = getpid(),
}; };
#if CRASH_REPORTER #if CRASH_REPORTER

View file

@ -61,6 +61,7 @@ struct CommandState {
QStringOption id; QStringOption id;
pid_t pid = -1; // NOLINT (include) pid_t pid = -1; // NOLINT (include)
bool all = false; bool all = false;
bool includeDead = false;
} instance; } instance;
struct { struct {

View file

@ -163,6 +163,9 @@ int parseCommand(int argc, char** argv, CommandState& state) {
sub->add_flag("-j,--json", state.output.json, "Output the list as a json."); sub->add_flag("-j,--json", state.output.json, "Output the list as a json.");
sub->add_flag("--show-dead", state.instance.includeDead)
->description("Include dead instances in the list.");
addConfigSelection(sub, true)->excludes(all); addConfigSelection(sub, true)->excludes(all);
addLoggingOptions(sub, false, true); addLoggingOptions(sub, false, true);