Skip to content

Commit

Permalink
Add ProcessBider, allows waiting for an arbitrary process to end
Browse files Browse the repository at this point in the history
  • Loading branch information
oblivioncth committed Nov 26, 2023
1 parent 3447fc2 commit fae59cb
Show file tree
Hide file tree
Showing 11 changed files with 1,677 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ project(Qx

# Get helper scripts
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchOBCMake.cmake)
fetch_ob_cmake("v0.3.2.1")
fetch_ob_cmake("928cbafc2036f97cfaeb50cd892209b6e1037b6d")

# Initialize project according to standard rules
include(OB/Project)
Expand Down
10 changes: 10 additions & 0 deletions lib/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
qx_add_component("Core"
HEADERS_PRIVATE
qx-json_p.h
qx-processbider_p.h
qx-system_p.h
__private/qx-processwaiter.h
__private/qx-processwaiter_win.h
__private/qx-processwaiter_linux.h
HEADERS_API
qx-abstracterror.h
qx-algorithm.h
Expand All @@ -25,6 +29,7 @@ qx_add_component("Core"
qx-iostream.h
qx-json.h
qx-list.h
qx-processbider.h
qx-progressgroup.h
qx-table.h
qx-versionnumber.h
Expand Down Expand Up @@ -53,6 +58,7 @@ qx_add_component("Core"
qx-iostream_win.cpp
qx-json.cpp
qx-json_p.cpp
qx-processbider.cpp
qx-progressgroup.cpp
qx-versionnumber.cpp
qx-string.cpp
Expand All @@ -65,6 +71,10 @@ qx_add_component("Core"
qx-systemerror_linux.cpp
qx-systemerror_win.cpp
__private/qx-internalerror.cpp
__private/qx-processwaiter.cpp
__private/qx-processwaiter.h
__private/qx-processwaiter_win.cpp
__private/qx-processwaiter_linux.cpp
DOC_ONLY
qx-regularexpression.dox
qx-bytearray.dox
Expand Down
129 changes: 129 additions & 0 deletions lib/core/include/qx/core/qx-processbider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#ifndef QX_PROCCESSBIDER_H
#define QX_PROCCESSBIDER_H

// Shared Lib Support
#include "qx/core/qx_core_export.h"

// Qt Includes
#include <QObject>

// Inter-component Includes
#include <qx/core/qx-abstracterror.h>

using namespace std::chrono_literals;

namespace Qx
{

class QX_CORE_EXPORT ProcessBiderError final : public AbstractError<"Qx::ProcessBiderError", 6>
{
friend class ProcessBider;
//-Class Enums-------------------------------------------------------------
public:
enum Type
{
NoError,
FailedToHook,
FailedToClose
};

//-Class Variables-------------------------------------------------------------
private:
static inline const QHash<Type, QString> ERR_STRINGS{
{NoError, u""_s},
{FailedToHook, u"Could not hook the process in order to bide on it."_s},
{FailedToClose, u"Could not close the bided process."_s}
};

//-Instance Variables-------------------------------------------------------------
private:
Type mType;
QString mProcessName;

//-Constructor-------------------------------------------------------------
private:
ProcessBiderError(Type t, const QString& pn);

//-Instance Functions-------------------------------------------------------------
private:
quint32 deriveValue() const override;
QString derivePrimary() const override;
QString deriveSecondary() const override;

public:
bool isValid() const;
Type type() const;
QString processName() const;
};

class QX_CORE_EXPORT ProcessBider : public QObject
{
friend class ProcessBiderManager;
Q_OBJECT
//-Class Types----------------------------------------------------------------------------------------------
public:
enum ResultType { Fail, Expired, Abandoned };

//-Instance Members------------------------------------------------------------------------------------------
private:
// Data
QString mName;
std::chrono::milliseconds mGrace;
#ifdef __linux__
std::chrono::milliseconds mPollRate;
#endif
bool mInitialGrace;

// Functional
bool mBiding;

//-Constructor----------------------------------------------------------------------------------------------
public:
explicit ProcessBider(QObject* parent = nullptr, const QString& processName = {});

//-Instance Functions----------------------------------------------------------------------------------------------
public:
bool isBiding() const;
QString processName() const;
std::chrono::milliseconds respawnGrace() const;
bool initialGrace() const;

void setProcessName(const QString& name);
void setRespawnGrace(std::chrono::milliseconds grace);
void setInitialGrace(bool initialGrace);

#ifdef __linux__
std::chrono::milliseconds pollRate() const;
void setPollRate(std::chrono::milliseconds rate);
#endif

//-Slots------------------------------------------------------------------------------------------------------------
private slots:
void handleResultReady(ResultType result);
void handleCloseFailure();

public slots:
void start();
void stop();
void closeProcess(std::chrono::milliseconds timeout = 1000ms, bool force = false);

//-Signals------------------------------------------------------------------------------------------------------------
signals:
void started();
void established();
void graceStarted();
void processStopped();
void processClosing();
void stopped();
void errorOccurred(ProcessBiderError error);
void finished(ResultType result);

/*! @cond */
// Temporary until using PIMPL or changing implementation otherwise
void __startClose(std::chrono::milliseconds timeout, bool force);
/*! @endcond */
};

}

#endif // QX_PROCCESSBIDER_H
70 changes: 70 additions & 0 deletions lib/core/src/__private/qx-processwaiter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Unit Includes
#include "qx-processwaiter.h"

namespace Qx
{
/*! @cond */

//===============================================================================================================
// AbstractProcessWaiter
//===============================================================================================================

//-Constructor----------------------------------------------------------------------------------------------
//Public:
AbstractProcessWaiter::AbstractProcessWaiter(QObject* parent) :
QObject(parent),
mId(0)
{}

//-Instance Functions---------------------------------------------------------------------------------------------
//Private:
void AbstractProcessWaiter::postDeadWait(bool died)
{
// Move out callback incase the callback replaces itself
auto cb = std::move(mDeadWaitCallback);
mDeadWaitCallback = {};

// Call
cb(died);
}

void AbstractProcessWaiter::timerEvent(QTimerEvent* event)
{
Q_UNUSED(event);
mDeadWaitTimer.stop();
postDeadWait(false);
}

//Protected:
void AbstractProcessWaiter::waitForDead(std::chrono::milliseconds timeout, std::function<void(bool)> callback)
{
Q_ASSERT(!mDeadWaitCallback); // Current implementation doesn't support multiple callbacks

// Store callback
mDeadWaitCallback = std::move(callback);

// One-shot wait on dead signal
connect(this, &AbstractProcessWaiter::dead, this, [this]{
if(mDeadWaitTimer.isActive()) // In case timer already expired and this was behind in queue
{
mDeadWaitTimer.stop();
postDeadWait(true);
}
}, Qt::ConnectionType(Qt::DirectConnection | Qt::SingleShotConnection));
mDeadWaitTimer.start(timeout, this);
}

//Public:
void AbstractProcessWaiter::close(std::chrono::milliseconds timeout, bool force)
{
// If waiting happened to stop, ignore
if(!isWaiting())
return;

closeImpl(timeout, force);
}

void AbstractProcessWaiter::setId(quint32 id) { mId = id; }

/*! @endcond */
}
60 changes: 60 additions & 0 deletions lib/core/src/__private/qx-processwaiter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#ifndef QX_PROCCESSWAITER_H
#define QX_PROCCESSWAITER_H

// Qt Includes
#include <QObject>
#include <QBasicTimer>

namespace Qx
{
/*! @cond */

class AbstractProcessWaiter : public QObject
{
Q_OBJECT
//-Class Members------------------------------------------------------------------------------------------
protected:
static const int CLEAN_KILL_GRACE_MS = 5000;

//-Instance Members------------------------------------------------------------------------------------------
protected:
// Data
quint32 mId;

// Functional
QBasicTimer mDeadWaitTimer;
std::function<void(bool)> mDeadWaitCallback;

//-Constructor----------------------------------------------------------------------------------------------
public:
explicit AbstractProcessWaiter(QObject* parent);

//-Instance Functions----------------------------------------------------------------------------------------------
private:
void postDeadWait(bool died);
void timerEvent(QTimerEvent* event) override;

protected:
void waitForDead(std::chrono::milliseconds timeout, std::function<void(bool)> callback);
virtual void closeImpl(std::chrono::milliseconds timeout, bool force) = 0;

public:
virtual bool wait() = 0;
virtual bool isWaiting() const = 0;
void close(std::chrono::milliseconds timeout, bool force);
void setId(quint32 id);

//-Slots------------------------------------------------------------------------------------------------------------
protected slots:
virtual void handleProcessSignaled() = 0;

//-Signals------------------------------------------------------------------------------------------------------------
signals:
void dead();
void closeFailed();
};

/*! @endcond */
}

#endif // QX_PROCCESSWAITER_H
Loading

0 comments on commit fae59cb

Please sign in to comment.