diff --git a/cpp-lib/include/SnaccROSEInterfaces.h b/cpp-lib/include/SnaccROSEInterfaces.h index c1b5d78..2998b19 100644 --- a/cpp-lib/include/SnaccROSEInterfaces.h +++ b/cpp-lib/include/SnaccROSEInterfaces.h @@ -154,6 +154,11 @@ class SnaccInvokeContext { public: static std::shared_ptr Create(const SnaccInvokeContextInit& init); + + /*! Builds the invoke-context snapshot stored on @ref SnaccTelemetryData. + Calls @ref Clone(), clears lib-owned async decode borrows on the clone, then @ref PrepareForTelemetry(). */ + virtual std::shared_ptr CloneForTelemetryRetention() const final; + virtual std::shared_ptr Clone() const; SnaccInvokeContext& operator=(const SnaccInvokeContext&) = delete; @@ -161,10 +166,6 @@ class SnaccInvokeContext SnaccInvokeContext& operator=(SnaccInvokeContext&&) = delete; virtual ~SnaccInvokeContext(); - // Called immediately before the context is transferred into telemetry retention. - // Override if derived types keep borrowed data that must be detached or copied. - virtual void PrepareForTelemetry(); - // Meaning in OnInvoke_: Authentication Header from the ROSE Invoke (Pointer to the object in the invoke) // Meaning in Invoke_: Authentication Header that is dispatched along with the invoke (create it with new, cleanup is done inside) // SNACC::ROSEAuthRequest* pInvokeAuth{}; @@ -220,6 +221,14 @@ class SnaccInvokeContext // Allows derived context types to copy the base invoke-context state into a richer concrete type. SnaccInvokeContext(const SnaccInvokeContext& other); + /*! Called on a telemetry-retained clone after @ref CloneForTelemetryRetention() copied dispatch state. + Dispatch often stores non-owning raw pointers (stub @c ROSEInvoke, transport, session objects) + that are only valid while snacclib is still inside the invoke scope. Before the clone is + stored on @ref SnaccTelemetryData, override this to copy any needed fields into owned members, + then clear those pointers so telemetry cannot outlive the stub stack frame. The live dispatch + context is not modified. Lib-owned async result/error buffers are already cleared before this hook. */ + virtual void PrepareForTelemetry(); + std::string m_strOperationName; int m_iInvokeTimeout{-1}; SnaccInvokeAsyncCallback m_asyncCallback; diff --git a/cpp-lib/src/SnaccROSEBase.cpp b/cpp-lib/src/SnaccROSEBase.cpp index 2d9435f..ded8b82 100644 --- a/cpp-lib/src/SnaccROSEBase.cpp +++ b/cpp-lib/src/SnaccROSEBase.cpp @@ -271,6 +271,20 @@ std::shared_ptr SnaccInvokeContext::Clone() const return std::shared_ptr(new SnaccInvokeContext(*this)); } +std::shared_ptr SnaccInvokeContext::CloneForTelemetryRetention() const +{ + auto retained = Clone(); + retained->m_asyncCallback = {}; + retained->m_pAsyncResult = nullptr; + retained->m_pAsyncError = nullptr; + retained->PrepareForTelemetry(); + return retained; +} + +void SnaccInvokeContext::PrepareForTelemetry() +{ +} + SnaccScopedInvokeMessage::SnaccScopedInvokeMessage(long invokeID, unsigned int uiOperationID, SNACC::AsnType* pArgument) : m_pInvoke(new SNACC::ROSEInvoke()) { @@ -323,10 +337,6 @@ SnaccInvokeContext::~SnaccInvokeContext() } } -void SnaccInvokeContext::PrepareForTelemetry() -{ -} - void SnaccInvokeContext::SetInvokeTimeout(int iTimeoutMs) { m_iInvokeTimeout = iTimeoutMs; diff --git a/cpp-lib/src/SnaccTelemetry.cpp b/cpp-lib/src/SnaccTelemetry.cpp index 6b0f1f6..f1923c1 100644 --- a/cpp-lib/src/SnaccTelemetry.cpp +++ b/cpp-lib/src/SnaccTelemetry.cpp @@ -130,10 +130,7 @@ void SnaccTelemetryData::finalize(Outcome outcome, Stage stage, Reason reason, s std::shared_ptr pTelemetryctx; if (pctx) - { - pTelemetryctx = pctx->Clone(); - pTelemetryctx->PrepareForTelemetry(); - } + pTelemetryctx = pctx->CloneForTelemetryRetention(); m_Duration = std::chrono::duration_cast(std::chrono::steady_clock::now() - m_ChronoCreated); m_Outcome = outcome; diff --git a/cpp-lib/tests/telemetry_tests.cpp b/cpp-lib/tests/telemetry_tests.cpp index 02a318a..83308be 100644 --- a/cpp-lib/tests/telemetry_tests.cpp +++ b/cpp-lib/tests/telemetry_tests.cpp @@ -32,6 +32,13 @@ const SnaccTelemetryData* FindOutboundWaitTelemetry(const std::vector roseResult, const bool bExpectRequestData, const bool bExpectResponseData) { @@ -52,6 +59,8 @@ void ExpectOutboundWaitTelemetry(const SnaccTelemetryData* pTelemetry, const Sna EXPECT_GT(pTelemetry->m_stResponseData.value(), 0u); } EXPECT_GE(pTelemetry->m_Duration.count(), 0); + if (pTelemetry->m_pctx) + ExpectTelemetryInvokeContextLibBorrowsCleared(*pTelemetry->m_pctx); } long SendAsyncGetSettings(RuntimeEndpoint& client, AsyncInvokeLatch& latch, AsnGetSettingsResult& result, AsnRequestError& error, int timeoutMs) @@ -265,7 +274,7 @@ class TelemetryRuntimeTest : public RuntimeTestBase } // Verifies that outbound telemetry receives a cloned custom context and that - // PrepareForTelemetry only touches the retained clone. + // retention cleanup only touches the retained clone. void AssertCustomContextIsClonedAndPrepared(const TransportEncoding encoding) { InitializeEndpoints(encoding); @@ -293,6 +302,9 @@ class TelemetryRuntimeTest : public RuntimeTestBase EXPECT_EQ("client-session", pSessionCtx->m_strInvokeSessionId); EXPECT_TRUE(pSessionCtx->WasPreparedForTelemetry()); EXPECT_EQ("prepared:custom", pSessionCtx->TelemetryNote()); + ExpectTelemetryInvokeContextLibBorrowsCleared(*pSessionCtx); + EXPECT_EQ(nullptr, pSessionCtx->DispatchBorrowForTest()); + EXPECT_NE(nullptr, dynamic_cast(pCtx.get())->DispatchBorrowForTest()); } // Verifies that outbound transport failures are reported at OUTBOUND_SEND. diff --git a/cpp-lib/tests/test_support/sample_runtime_harness.h b/cpp-lib/tests/test_support/sample_runtime_harness.h index 6f92bf2..fef3b3f 100644 --- a/cpp-lib/tests/test_support/sample_runtime_harness.h +++ b/cpp-lib/tests/test_support/sample_runtime_harness.h @@ -260,9 +260,10 @@ class SessionInvokeContext : public SnaccInvokeContext return std::shared_ptr(new SessionInvokeContext(*this)); } - // Placeholder hook for future telemetry tests that may need to detach data. + // Records that PrepareForTelemetry() ran on the telemetry-retained clone. void PrepareForTelemetry() override { + m_pDispatchBorrow = nullptr; m_bPreparedForTelemetry = true; if (!m_strTelemetryNote.empty()) m_strTelemetryNote = "prepared:" + m_strTelemetryNote; @@ -280,12 +281,18 @@ class SessionInvokeContext : public SnaccInvokeContext return m_strTelemetryNote; } - // Returns whether PrepareForTelemetry() has already run on this concrete instance. + // Returns whether retention cleanup has already run on this concrete instance. bool WasPreparedForTelemetry() const { return m_bPreparedForTelemetry; } + // Simulates a product-owned dispatch borrow; cleared in PrepareForTelemetry() on the telemetry clone. + const SNACC::ROSEInvoke* DispatchBorrowForTest() const + { + return m_pDispatchBorrow; + } + const std::string m_strLocalSessionId; // session id of the endpoint creating the context const std::string m_strInvokeSessionId; // session id carried on the invoke payload itself @@ -294,7 +301,8 @@ class SessionInvokeContext : public SnaccInvokeContext SessionInvokeContext(const SnaccInvokeContextInit& init, const std::string& localSessionId) : SnaccInvokeContext(init), m_strLocalSessionId(localSessionId), - m_strInvokeSessionId(GetInvokeSessionId(init.m_pInvoke)) + m_strInvokeSessionId(GetInvokeSessionId(init.m_pInvoke)), + m_pDispatchBorrow(init.m_pInvoke) { } @@ -304,12 +312,14 @@ class SessionInvokeContext : public SnaccInvokeContext m_strLocalSessionId(other.m_strLocalSessionId), m_strInvokeSessionId(other.m_strInvokeSessionId), m_bPreparedForTelemetry(other.m_bPreparedForTelemetry), - m_strTelemetryNote(other.m_strTelemetryNote) + m_strTelemetryNote(other.m_strTelemetryNote), + m_pDispatchBorrow(other.m_pDispatchBorrow) { } bool m_bPreparedForTelemetry = false; // indicates whether the telemetry clone was normalized for retention std::string m_strTelemetryNote; // extra test data used to verify clone and prepare semantics + const SNACC::ROSEInvoke* m_pDispatchBorrow = nullptr; }; // Copies invoke-context fields while the runtime reference is still alive. diff --git a/version.h b/version.h index 86d5eee..df883d1 100644 --- a/version.h +++ b/version.h @@ -1,8 +1,8 @@ #ifndef VERSION_H #define VERSION_H -#define VERSION "7.0.9" -#define VERSION_RC 7, 0, 9 -#define RELDATE "16.07.2026" +#define VERSION "7.0.10" +#define VERSION_RC 7, 0, 10 +#define RELDATE "24.07.2026" #endif // VERSION_H