From ebdfbf092f8a67f6191671e76c0af778cb6caf1e Mon Sep 17 00:00:00 2001 From: Jan Fellner Date: Fri, 24 Jul 2026 15:08:19 +0200 Subject: [PATCH 1/3] UCAAS-1445: retain telemetry invoke context via CloneForTelemetryRetention Add final CloneForTelemetryRetention on SnaccInvokeContext, clear lib async borrows on the clone, and call PrepareForTelemetry for product hooks. Wire SnaccTelemetry to the new path and extend tests. Made-with: Cursor --- cpp-lib/include/SnaccROSEInterfaces.h | 17 +++++++++++++---- cpp-lib/src/SnaccROSEBase.cpp | 18 ++++++++++++++---- cpp-lib/src/SnaccTelemetry.cpp | 5 +---- cpp-lib/tests/telemetry_tests.cpp | 14 +++++++++++++- .../test_support/sample_runtime_harness.h | 18 ++++++++++++++---- 5 files changed, 55 insertions(+), 17 deletions(-) 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..ba95de4 100644 --- a/cpp-lib/tests/telemetry_tests.cpp +++ b/cpp-lib/tests/telemetry_tests.cpp @@ -52,6 +52,15 @@ 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); +} + +void ExpectTelemetryInvokeContextLibBorrowsCleared(const SnaccInvokeContext& ctx) +{ + EXPECT_FALSE(ctx.HasAsyncCompletion()); + EXPECT_EQ(nullptr, ctx.AsyncResultBuffer()); + EXPECT_EQ(nullptr, ctx.AsyncErrorBuffer()); } 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. From c4c5febe9786f492d46ce8bfffa869ce607dc6e4 Mon Sep 17 00:00:00 2001 From: Jan Fellner Date: Fri, 24 Jul 2026 15:21:29 +0200 Subject: [PATCH 2/3] UCAAS-1445: bump snacclib to 7.0.10 (release 24.07.2026) Made-with: Cursor --- version.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From b1338115b469af84d9b15dc7090cb7aba6c28929 Mon Sep 17 00:00:00 2001 From: Jan Fellner Date: Fri, 24 Jul 2026 15:30:34 +0200 Subject: [PATCH 3/3] UCAAS-1445: fix telemetry test helper declaration order Made-with: Cursor --- cpp-lib/tests/telemetry_tests.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp-lib/tests/telemetry_tests.cpp b/cpp-lib/tests/telemetry_tests.cpp index ba95de4..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) { @@ -56,13 +63,6 @@ void ExpectOutboundWaitTelemetry(const SnaccTelemetryData* pTelemetry, const Sna ExpectTelemetryInvokeContextLibBorrowsCleared(*pTelemetry->m_pctx); } -void ExpectTelemetryInvokeContextLibBorrowsCleared(const SnaccInvokeContext& ctx) -{ - EXPECT_FALSE(ctx.HasAsyncCompletion()); - EXPECT_EQ(nullptr, ctx.AsyncResultBuffer()); - EXPECT_EQ(nullptr, ctx.AsyncErrorBuffer()); -} - long SendAsyncGetSettings(RuntimeEndpoint& client, AsyncInvokeLatch& latch, AsnGetSettingsResult& result, AsnRequestError& error, int timeoutMs) { AsnGetSettingsArgument argument;