-
Notifications
You must be signed in to change notification settings - Fork 1
Claude/route logging diagnostics #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
05b460e
574d454
1162a97
602a676
9a56a3f
cb8df9a
02d9a03
ef200c4
3dfc900
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,8 @@ | |
|
|
||
| #include <QJsonArray> | ||
|
|
||
| AdapterClient::AdapterClient(AdapterProcess* pProcess, QObject* parent) : QObject(parent), _pProcess(pProcess) | ||
| AdapterClient::AdapterClient(AdapterProcess* pProcess, QObject* parent, int handshakeTimeoutMs) | ||
| : QObject(parent), _pProcess(pProcess), _handshakeTimeoutMs(handshakeTimeoutMs) | ||
| { | ||
| Q_ASSERT(pProcess); | ||
| _pProcess->setParent(this); | ||
|
|
@@ -16,6 +17,7 @@ AdapterClient::AdapterClient(AdapterProcess* pProcess, QObject* parent) : QObjec | |
| connect(_pProcess, &AdapterProcess::errorReceived, this, &AdapterClient::onErrorReceived); | ||
| connect(_pProcess, &AdapterProcess::processError, this, &AdapterClient::onProcessError); | ||
| connect(_pProcess, &AdapterProcess::processFinished, this, &AdapterClient::onProcessFinished); | ||
| connect(_pProcess, &AdapterProcess::notificationReceived, this, &AdapterClient::onNotificationReceived); | ||
| } | ||
|
|
||
| AdapterClient::~AdapterClient() = default; | ||
|
|
@@ -35,7 +37,7 @@ void AdapterClient::prepareAdapter(const QString& adapterPath) | |
|
|
||
| qCInfo(scopeComm) << "AdapterClient: process started, sending initialize"; | ||
| _state = State::INITIALIZING; | ||
| _handshakeTimer.start(cHandshakeTimeoutMs); | ||
| _handshakeTimer.start(_handshakeTimeoutMs); | ||
| _pProcess->sendRequest("adapter.initialize", QJsonObject()); | ||
| } | ||
|
|
||
|
|
@@ -50,7 +52,7 @@ void AdapterClient::provideConfig(QJsonObject config, QStringList registerExpres | |
| _pendingExpressions = registerExpressions; | ||
| _pendingConfig = config; | ||
| _state = State::CONFIGURING; | ||
| _handshakeTimer.start(cHandshakeTimeoutMs); | ||
| _handshakeTimer.start(_handshakeTimeoutMs); | ||
| QJsonObject params; | ||
| params["config"] = _pendingConfig; | ||
| _pProcess->sendRequest("adapter.configure", params); | ||
|
|
@@ -91,12 +93,13 @@ void AdapterClient::stopSession() | |
| { | ||
| _state = State::STOPPING; | ||
| _pProcess->sendRequest("adapter.shutdown", QJsonObject()); | ||
| _handshakeTimer.start(_handshakeTimeoutMs); | ||
| } | ||
| else | ||
| { | ||
| _state = State::STOPPING; | ||
| _pProcess->stop(); | ||
| _state = State::IDLE; | ||
| emit sessionStopped(); | ||
| /* sessionStopped is emitted from onProcessFinished once the process exits */ | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -112,7 +115,7 @@ void AdapterClient::onResponseReceived(int id, const QString& method, const QJso | |
| qCWarning(scopeComm) << "AdapterClient: unexpected non-object result for" << method; | ||
| _handshakeTimer.stop(); | ||
| _state = State::IDLE; | ||
| QTimer::singleShot(0, this, [this]() { _pProcess->stop(); }); | ||
| _pProcess->stop(); | ||
| emit sessionError(QString("Unexpected non-object result for %1").arg(method)); | ||
|
Comment on lines
117
to
119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not switch back to
Also applies to: 130-132, 147-175 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
@@ -126,7 +129,7 @@ void AdapterClient::onErrorReceived(int id, const QString& method, const QJsonOb | |
|
|
||
| State previousState = _state; | ||
| _state = State::IDLE; | ||
| QTimer::singleShot(0, this, [this]() { _pProcess->stop(); }); | ||
| _pProcess->stop(); | ||
|
|
||
| if (previousState != State::STOPPING) | ||
| { | ||
|
|
@@ -144,7 +147,12 @@ void AdapterClient::onProcessError(const QString& message) | |
| void AdapterClient::onProcessFinished() | ||
| { | ||
| _handshakeTimer.stop(); | ||
| if (_state != State::IDLE) | ||
| if (_state == State::STOPPING) | ||
| { | ||
| _state = State::IDLE; | ||
| emit sessionStopped(); | ||
| } | ||
| else if (_state != State::IDLE) | ||
| { | ||
| _state = State::IDLE; | ||
| emit sessionError("Adapter process exited unexpectedly"); | ||
|
|
@@ -154,9 +162,35 @@ void AdapterClient::onProcessFinished() | |
| void AdapterClient::onHandshakeTimeout() | ||
| { | ||
| qCWarning(scopeComm) << "AdapterClient: handshake timed out in state" << static_cast<int>(_state); | ||
| bool wasStopping = (_state == State::STOPPING); | ||
| _state = State::IDLE; | ||
| QTimer::singleShot(0, this, [this]() { _pProcess->stop(); }); | ||
| emit sessionError("Adapter handshake timed out"); | ||
| _pProcess->stop(); | ||
| if (wasStopping) | ||
| { | ||
| emit sessionStopped(); | ||
| } | ||
| else | ||
| { | ||
| emit sessionError("Adapter handshake timed out"); | ||
| } | ||
| } | ||
|
|
||
| void AdapterClient::onNotificationReceived(QString method, QJsonValue params) | ||
| { | ||
| if (method != QStringLiteral("adapter.diagnostic")) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (!params.isObject()) | ||
| { | ||
| qCWarning(scopeComm) << "AdapterClient: adapter.diagnostic params is not an object"; | ||
| return; | ||
| } | ||
|
|
||
| QJsonObject obj = params.toObject(); | ||
| emit diagnosticReceived(obj.value(QStringLiteral("level")).toString(), | ||
| obj.value(QStringLiteral("message")).toString()); | ||
jgeudens marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| void AdapterClient::handleLifecycleResponse(const QString& method, const QJsonObject& result) | ||
|
|
@@ -215,8 +249,7 @@ void AdapterClient::handleLifecycleResponse(const QString& method, const QJsonOb | |
| { | ||
| qCInfo(scopeComm) << "AdapterClient: shutdown acknowledged"; | ||
| _pProcess->stop(); | ||
| _state = State::IDLE; | ||
| emit sessionStopped(); | ||
| /* sessionStopped is emitted from onProcessFinished once the process exits */ | ||
| } | ||
| else | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,9 @@ class AdapterClient : public QObject | |
| Q_OBJECT | ||
|
|
||
| public: | ||
| explicit AdapterClient(AdapterProcess* pProcess, QObject* parent = nullptr); | ||
| explicit AdapterClient(AdapterProcess* pProcess, | ||
| QObject* parent = nullptr, | ||
| int handshakeTimeoutMs = cHandshakeTimeoutMs); | ||
| ~AdapterClient(); | ||
|
|
||
| /*! | ||
|
|
@@ -71,7 +73,10 @@ class AdapterClient : public QObject | |
| void requestStatus(); | ||
|
|
||
| /*! | ||
| * \brief Send adapter.shutdown and terminate the adapter process. | ||
| * \brief Send adapter.shutdown and signal the adapter process to stop. | ||
| * | ||
| * The sessionStopped() signal is emitted asynchronously once the process | ||
| * has fully exited. | ||
| */ | ||
| void stopSession(); | ||
|
|
||
|
|
@@ -114,6 +119,13 @@ class AdapterClient : public QObject | |
| */ | ||
| void sessionStopped(); | ||
|
|
||
| /*! | ||
| * \brief Emitted when an adapter.diagnostic notification is received from the adapter. | ||
| * \param level Severity level string: "debug", "info", or "warning". | ||
| * \param message The diagnostic message from the adapter. | ||
|
Comment on lines
+122
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Signal docs are stale for diagnostic severity levels. The comment lists only 🤖 Prompt for AI Agents |
||
| */ | ||
| void diagnosticReceived(QString level, QString message); | ||
|
|
||
| protected: | ||
| enum class State | ||
| { | ||
|
|
@@ -135,6 +147,7 @@ private slots: | |
| void onProcessError(const QString& message); | ||
| void onProcessFinished(); | ||
| void onHandshakeTimeout(); | ||
| void onNotificationReceived(QString method, QJsonValue params); | ||
|
|
||
| private: | ||
| void handleLifecycleResponse(const QString& method, const QJsonObject& result); | ||
|
|
@@ -143,6 +156,7 @@ private slots: | |
|
|
||
| AdapterProcess* _pProcess; | ||
| QTimer _handshakeTimer; | ||
| int _handshakeTimeoutMs; | ||
| QJsonObject _pendingConfig; | ||
| QStringList _pendingExpressions; | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,10 @@ | |
| #include "util/scopelogging.h" | ||
|
|
||
| #include <QJsonDocument> | ||
| #include <QTimer> | ||
|
|
||
| static constexpr int cStartTimeoutMs = 3000; | ||
| static constexpr int cStopTimeoutMs = 3000; | ||
|
|
||
| AdapterProcess::AdapterProcess(QObject* parent) : QObject(parent) | ||
| { | ||
|
|
@@ -49,11 +51,12 @@ void AdapterProcess::stop() | |
| if (_pProcess->state() != QProcess::NotRunning) | ||
| { | ||
| _pProcess->closeWriteChannel(); | ||
| if (!_pProcess->waitForFinished(3000)) | ||
| { | ||
| _pProcess->kill(); | ||
| _pProcess->waitForFinished(1000); | ||
| } | ||
| QTimer::singleShot(cStopTimeoutMs, this, [this]() { | ||
| if (_pProcess->state() != QProcess::NotRunning) | ||
| { | ||
| _pProcess->kill(); | ||
| } | ||
| }); | ||
|
Comment on lines
51
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cd src/ProtocolAdapter && wc -l adapterprocess.cpp adapterprocess.hRepository: ModbusScope/NotOnlyModbusScope Length of output: 130 🏁 Script executed: cd src/ProtocolAdapter && head -80 adapterprocess.cpp | tail -40Repository: ModbusScope/NotOnlyModbusScope Length of output: 1138 🏁 Script executed: cd src/ProtocolAdapter && grep -n "void.*start\|void.*stop\|void.*onProcessFinished" adapterprocess.cpp | head -20Repository: ModbusScope/NotOnlyModbusScope Length of output: 194 🏁 Script executed: cd src/ProtocolAdapter && cat adapterprocess.hRepository: ModbusScope/NotOnlyModbusScope Length of output: 3744 🏁 Script executed: cd src/ProtocolAdapter && sed -n '20,50p' adapterprocess.cppRepository: ModbusScope/NotOnlyModbusScope Length of output: 1035 🏁 Script executed: cd src/ProtocolAdapter && sed -n '172,195p' adapterprocess.cppRepository: ModbusScope/NotOnlyModbusScope Length of output: 332 🏁 Script executed: cd src/ProtocolAdapter && grep -n "QTimer\|_pTimer\|killTimer\|stopTimer" adapterprocess.cpp adapterprocess.hRepository: ModbusScope/NotOnlyModbusScope Length of output: 194 Cancel any pending stop timeout before a restart. The Additionally, the lambda contains multiple statements and violates the coding guideline: "Avoid lambda expressions with more than 2 captures or multiple statements; use named functions instead for clarity". Refactor this into a named private slot or function. 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -100,11 +103,10 @@ bool AdapterProcess::writeFramed(const QByteArray& json) | |
| qint64 written = _pProcess->write(frame); | ||
| if (written != frame.size()) | ||
| { | ||
| emit processError( | ||
| QString("Failed to write to adapter process (wrote %1 of %2 bytes, error: %3)") | ||
| .arg(written) | ||
| .arg(frame.size()) | ||
| .arg(_pProcess->errorString())); | ||
| emit processError(QString("Failed to write to adapter process (wrote %1 of %2 bytes, error: %3)") | ||
| .arg(written) | ||
| .arg(frame.size()) | ||
| .arg(_pProcess->errorString())); | ||
| return false; | ||
| } | ||
| return true; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defaults now diverge from the published contract.
persistentandint32LittleEndianwere changed totrue, butadapters/json-rpc-spec.mdstill documents both defaults asfalse. This creates a behavioural/API mismatch for clients consumingadapter.describe.Proposed fix (if the default change was unintentional)
Also applies to: 26-26
🤖 Prompt for AI Agents