From 13e1815e065f04efb861e804fee253b5d84176a0 Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Fri, 12 Jun 2026 14:26:04 +0200 Subject: [PATCH 1/3] Add integration test for timestamp behavior Signed-off-by: Konrad Breitsprecher --- .../IntegrationTests/ITest_AsyncSimTask.cpp | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp b/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp index e97fb04e0..aaa1312c6 100644 --- a/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp +++ b/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp @@ -362,4 +362,124 @@ TEST(ITest_AsyncSimTask, test_async_simtask_other_simulation_steps_completed_han cd.AssertTimestampsAlwaysOutsideStep(stepLastOpen); } +// Verify timestamp behavior from the synchronized participant perspective: +// - Sync sender: outgoing event carries virtual send time. +// - Async sender: outgoing events will have an invalid timestamp +// - Sync receiver: incoming event from async or sync sender is timestamped with sync receive time. +// - Async receiver: incoming event from async or sync sender keeps the timestamp (async sender: invalid timestamp, sync sender: send time) +TEST(ITest_AsyncSimTask, test_timestamp_handling) +{ + SimTestHarness testHarness({"Sync"}, "silkit://localhost:0", false, false, {"Async", "Async2"}); + + const auto dataSpecSyncToAsync = SilKit::Services::PubSub::PubSubSpec{"TopicSyncToAsync", "A"}; + const auto dataSpecAsyncToSync = SilKit::Services::PubSub::PubSubSpec{"TopicAsyncToSync", "A"}; + const auto dataSpecAsyncToAsync = SilKit::Services::PubSub::PubSubSpec{"TopicAsyncToAsync", "A"}; + const auto dataSpecSyncToSync = SilKit::Services::PubSub::PubSubSpec{"TopicSyncToSync", "A"}; + + auto* syncParticipant = testHarness.GetParticipant("Sync"); + auto* asyncParticipant = testHarness.GetParticipant("Async"); + auto* async2Participant = testHarness.GetParticipant("Async2"); + + auto* syncLifecycleService = syncParticipant->GetOrCreateLifecycleService(); + auto* asyncLifecycleService = asyncParticipant->GetOrCreateLifecycleService(); + auto* async2LifecycleService = async2Participant->GetOrCreateLifecycleService(); + auto* syncTimeSyncService = syncParticipant->GetOrCreateTimeSyncService(); + + auto* syncPublisher = syncParticipant->Participant()->CreateDataPublisher("SyncPub", dataSpecSyncToAsync); + auto* asyncPublisher = asyncParticipant->Participant()->CreateDataPublisher("AsyncPub", dataSpecAsyncToSync); + auto* asyncPublisherToAsync2 = asyncParticipant->Participant()->CreateDataPublisher("AsyncPubToAsync2", dataSpecAsyncToAsync); + auto* syncPublisherToSync = syncParticipant->Participant()->CreateDataPublisher("SyncPubToSync", dataSpecSyncToSync); + + std::atomic syncSendTimeNs{std::chrono::nanoseconds::min().count()}; + std::atomic syncCurrentStepNs{std::chrono::nanoseconds::min().count()}; + std::atomic syncToAsyncReceived{false}; + std::atomic asyncToSyncReceived{false}; + std::atomic asyncToAsyncReceived{false}; + std::atomic syncToSyncReceived{false}; + std::atomic syncSent{false}; + + asyncParticipant->Participant()->CreateDataSubscriber( + "AsyncSub", dataSpecSyncToAsync, + [asyncPublisher, asyncPublisherToAsync2, &syncSendTimeNs, + &syncToAsyncReceived](SilKit::Services::PubSub::IDataSubscriber* /*subscriber*/, + const SilKit::Services::PubSub::DataMessageEvent& dataMessageEvent) { + const auto expectedTimestamp = std::chrono::nanoseconds{syncSendTimeNs.load()}; + EXPECT_NE(expectedTimestamp, std::chrono::nanoseconds::min()); + EXPECT_EQ(dataMessageEvent.timestamp, expectedTimestamp); + + if (!syncToAsyncReceived.exchange(true)) + { + asyncPublisher->Publish(std::vector{0xA5}); + asyncPublisherToAsync2->Publish(std::vector{0x3C}); + } + }); + + syncParticipant->Participant()->CreateDataSubscriber( + "SyncSub", dataSpecAsyncToSync, + [&syncCurrentStepNs, &asyncToSyncReceived](SilKit::Services::PubSub::IDataSubscriber* /*subscriber*/, + const SilKit::Services::PubSub::DataMessageEvent& dataMessageEvent) { + const auto currentStep = std::chrono::nanoseconds{syncCurrentStepNs.load()}; + + EXPECT_TRUE(dataMessageEvent.timestamp == currentStep) + << "Incoming async message should be timestamped with the sync receiver time."; + asyncToSyncReceived = true; + }); + + syncParticipant->Participant()->CreateDataSubscriber( + "SyncSubFromSync", dataSpecSyncToSync, + [&syncCurrentStepNs, &syncToSyncReceived](SilKit::Services::PubSub::IDataSubscriber* /*subscriber*/, + const SilKit::Services::PubSub::DataMessageEvent& dataMessageEvent) { + const auto currentStep = std::chrono::nanoseconds{syncCurrentStepNs.load()}; + + EXPECT_TRUE(dataMessageEvent.timestamp == currentStep) + << "Incoming sync message should be timestamped with the sync receiver time."; + syncToSyncReceived = true; + }); + + async2Participant->Participant()->CreateDataSubscriber( + "Async2Sub", dataSpecAsyncToAsync, + [&asyncToAsyncReceived](SilKit::Services::PubSub::IDataSubscriber* /*subscriber*/, + const SilKit::Services::PubSub::DataMessageEvent& dataMessageEvent) { + EXPECT_EQ(dataMessageEvent.timestamp, std::chrono::nanoseconds::min()) + << "Outgoing async messages must have invalid timestamps."; + asyncToAsyncReceived = true; + }); + + syncTimeSyncService->SetSimulationStepHandler( + [syncLifecycleService, asyncLifecycleService, async2LifecycleService, syncPublisher, syncPublisherToSync, + &syncSendTimeNs, &syncCurrentStepNs, &syncSent, &syncToAsyncReceived, &asyncToSyncReceived, + &asyncToAsyncReceived, &syncToSyncReceived](std::chrono::nanoseconds now, + std::chrono::nanoseconds /*duration*/) { + syncCurrentStepNs = now.count(); + + if (!syncSent && now >= 1ms) + { + syncSendTimeNs = now.count(); + syncPublisher->Publish(std::vector{0x5A}); + syncPublisherToSync->Publish(std::vector{0xC3}); + syncSent = true; + } + + if (syncToAsyncReceived && asyncToSyncReceived && asyncToAsyncReceived && syncToSyncReceived) + { + asyncLifecycleService->Stop("Timestamp handling verified"); + async2LifecycleService->Stop("Timestamp handling verified"); + syncLifecycleService->Stop("Timestamp handling verified"); + } + else if (now >= 20ms) + { + asyncLifecycleService->Stop("Timestamp handling timed out"); + async2LifecycleService->Stop("Timestamp handling timed out"); + syncLifecycleService->Stop("Timestamp handling timed out"); + } + }, + 1ms); + + ASSERT_TRUE(testHarness.Run(5s)); + ASSERT_TRUE(syncToAsyncReceived.load()); + ASSERT_TRUE(asyncToSyncReceived.load()); + ASSERT_TRUE(asyncToAsyncReceived.load()); + ASSERT_TRUE(syncToSyncReceived.load()); +} + } // anonymous namespace From 4cd4092c75dc00e861897a3ca1299bae091fc610 Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Mon, 15 Jun 2026 12:13:17 +0200 Subject: [PATCH 2/3] Add +dt tolerance for the reception Signed-off-by: Konrad Breitsprecher --- SilKit/IntegrationTests/ITest_AsyncSimTask.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp b/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp index aaa1312c6..fb8b11c52 100644 --- a/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp +++ b/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp @@ -420,8 +420,8 @@ TEST(ITest_AsyncSimTask, test_timestamp_handling) const SilKit::Services::PubSub::DataMessageEvent& dataMessageEvent) { const auto currentStep = std::chrono::nanoseconds{syncCurrentStepNs.load()}; - EXPECT_TRUE(dataMessageEvent.timestamp == currentStep) - << "Incoming async message should be timestamped with the sync receiver time."; + EXPECT_TRUE((dataMessageEvent.timestamp == currentStep) || (dataMessageEvent.timestamp == currentStep + 1ms)) + << "Incoming async message should be timestamped with the sync receiver time (or +1ms tolerance)."; asyncToSyncReceived = true; }); @@ -431,8 +431,8 @@ TEST(ITest_AsyncSimTask, test_timestamp_handling) const SilKit::Services::PubSub::DataMessageEvent& dataMessageEvent) { const auto currentStep = std::chrono::nanoseconds{syncCurrentStepNs.load()}; - EXPECT_TRUE(dataMessageEvent.timestamp == currentStep) - << "Incoming sync message should be timestamped with the sync receiver time."; + EXPECT_TRUE((dataMessageEvent.timestamp == currentStep) || (dataMessageEvent.timestamp == currentStep + 1ms)) + << "Incoming sync message should be timestamped with the sync receiver time (or +1ms tolerance)."; syncToSyncReceived = true; }); From db214f247a7860080fb676c1a3a177bb332628a6 Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Mon, 15 Jun 2026 12:42:08 +0200 Subject: [PATCH 3/3] Add -dt tolerance for the reception Signed-off-by: Konrad Breitsprecher --- SilKit/IntegrationTests/ITest_AsyncSimTask.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp b/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp index fb8b11c52..589293df2 100644 --- a/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp +++ b/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp @@ -420,8 +420,9 @@ TEST(ITest_AsyncSimTask, test_timestamp_handling) const SilKit::Services::PubSub::DataMessageEvent& dataMessageEvent) { const auto currentStep = std::chrono::nanoseconds{syncCurrentStepNs.load()}; - EXPECT_TRUE((dataMessageEvent.timestamp == currentStep) || (dataMessageEvent.timestamp == currentStep + 1ms)) - << "Incoming async message should be timestamped with the sync receiver time (or +1ms tolerance)."; + EXPECT_TRUE((dataMessageEvent.timestamp == currentStep) || (dataMessageEvent.timestamp == currentStep + 1ms) + || (dataMessageEvent.timestamp == currentStep - 1ms)) + << "Incoming async message should be timestamped with the sync receiver time (+/-1ms tolerance)."; asyncToSyncReceived = true; }); @@ -431,8 +432,9 @@ TEST(ITest_AsyncSimTask, test_timestamp_handling) const SilKit::Services::PubSub::DataMessageEvent& dataMessageEvent) { const auto currentStep = std::chrono::nanoseconds{syncCurrentStepNs.load()}; - EXPECT_TRUE((dataMessageEvent.timestamp == currentStep) || (dataMessageEvent.timestamp == currentStep + 1ms)) - << "Incoming sync message should be timestamped with the sync receiver time (or +1ms tolerance)."; + EXPECT_TRUE((dataMessageEvent.timestamp == currentStep) || (dataMessageEvent.timestamp == currentStep + 1ms) + || (dataMessageEvent.timestamp == currentStep - 1ms)) + << "Incoming sync message should be timestamped with the sync receiver time (+/-1ms tolerance)."; syncToSyncReceived = true; });