diff --git a/Libraries/Opc.Ua.PubSub/IntervalRunner.cs b/Libraries/Opc.Ua.PubSub/IntervalRunner.cs index fe8dbd9bea..a8a0d9b0ba 100644 --- a/Libraries/Opc.Ua.PubSub/IntervalRunner.cs +++ b/Libraries/Opc.Ua.PubSub/IntervalRunner.cs @@ -42,7 +42,8 @@ namespace Opc.Ua.PubSub private readonly object m_lock = new object(); private double m_interval = kMinInterval; - private DateTime m_nextPublishTime = DateTime.MinValue; + private double m_nextPublishTick = 0; + // event used to cancel run private CancellationTokenSource m_cancellationToken = new CancellationTokenSource(); @@ -154,30 +155,47 @@ namespace Opc.Ua.PubSub /// private async Task ProcessAsync() { + lock (m_lock) + { + m_nextPublishTick = HiResClock.Ticks; + } do { - int sleepCycle = 0; - DateTime now = DateTime.UtcNow; - DateTime nextPublishTime = DateTime.MinValue; - - lock (m_lock) + if (m_cancellationToken.IsCancellationRequested) { - sleepCycle = Convert.ToInt32(m_interval); - - nextPublishTime = m_nextPublishTime; + break; } - if (nextPublishTime > now) + long nowTick = HiResClock.Ticks; + double nextPublishTick = 0; + + lock(m_lock) { - sleepCycle = (int)Math.Min((nextPublishTime - now).TotalMilliseconds, sleepCycle); - sleepCycle = (int)Math.Max(kMinInterval, sleepCycle); + nextPublishTick = m_nextPublishTick; + } + + double sleepCycle = (nextPublishTick - nowTick) / HiResClock.TicksPerMillisecond; + if (sleepCycle > 16) + { + // Use Task.Delay if sleep cycle is larger await Task.Delay(TimeSpan.FromMilliseconds(sleepCycle), m_cancellationToken.Token).ConfigureAwait(false); - } + // Still ticks to consume (spurious wakeup too early), improbable + nowTick = HiResClock.Ticks; + if (nowTick < nextPublishTick) + { + SpinWait.SpinUntil(() => HiResClock.Ticks >= nextPublishTick); + } + } + else if (sleepCycle >= 0 && sleepCycle <= 16) + { + SpinWait.SpinUntil(() => HiResClock.Ticks >= nextPublishTick); + } + lock (m_lock) { - var nextCycle = Convert.ToInt32(m_interval); - m_nextPublishTime = DateTime.UtcNow.AddMilliseconds(nextCycle); + var nextCycle = (long)m_interval * HiResClock.TicksPerMillisecond; + m_nextPublishTick += nextCycle; if (IntervalAction != null && CanExecuteFunc != null && CanExecuteFunc()) { diff --git a/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPublisherTests.cs b/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPublisherTests.cs index 8b38c9d292..f30ce8baf7 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPublisherTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPublisherTests.cs @@ -37,10 +37,11 @@ using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Opc.Ua.PubSub.Tests.Configuration { - [TestFixture(Description = "Tests for UAPublisher class")] + [TestFixture(Description = "Tests for UAPublisher class"), SingleThreaded] public class UaPublisherTests { - static IList s_publishTimes = new List(); + static List s_publishTicks = new List(); + static object s_lock = new Object(); [Test(Description = "Test that PublishMessage method is called after a UAPublisher is started.")] [Combinatorial] @@ -53,12 +54,17 @@ namespace Opc.Ua.PubSub.Tests.Configuration [Values(10)] int publishTimeInSeconds) { //Arrange - s_publishTimes.Clear(); + s_publishTicks.Clear(); var mockConnection = new Mock(); mockConnection.Setup(x => x.CanPublish(It.IsAny())).Returns(true); mockConnection.Setup(x => x.CreateNetworkMessages(It.IsAny(), It.IsAny())) - .Callback(() => s_publishTimes.Add(DateTime.Now)); + .Callback(() => { + lock (s_lock) + { + s_publishTicks.Add(HiResClock.Ticks); + } + }); WriterGroupDataType writerGroupDataType = new WriterGroupDataType(); writerGroupDataType.PublishingInterval = publishingInterval; @@ -70,25 +76,38 @@ namespace Opc.Ua.PubSub.Tests.Configuration //wait so many seconds Thread.Sleep(publishTimeInSeconds * 1000); publisher.Stop(); + int faultIndex = -1; double faultDeviation = 0; - s_publishTimes = (from t in s_publishTimes + s_publishTicks = (from t in s_publishTicks orderby t select t).ToList(); //Assert - for (int i = 1; i < s_publishTimes.Count; i++) + for (int i = 1; i < s_publishTicks.Count; i++) { - double interval = s_publishTimes[i].Subtract(s_publishTimes[i - 1]).TotalMilliseconds; - double deviation = Math.Abs(publishingInterval - interval); - if (deviation >= maxDeviation && deviation > faultDeviation) + double interval = (s_publishTicks[i] - s_publishTicks[i - 1])/HiResClock.TicksPerMillisecond; + if (interval != 0) { - faultIndex = i; - faultDeviation = deviation; + double deviation = -1; + if (interval != publishingInterval) + { + deviation = Math.Abs(publishingInterval - interval); + } + if (deviation >= maxDeviation && deviation > faultDeviation) + { + faultIndex = i; + faultDeviation = deviation; + } } } - Assert.IsTrue(faultIndex < 0, "publishingInterval={0}, maxDeviation={1}, publishTimeInSecods={2}, deviation[{3}] = {4} has maximum deviation", publishingInterval, maxDeviation, publishTimeInSeconds, faultIndex, faultDeviation); + Assert.IsTrue(faultIndex < 0, "publishingInterval={0}, maxDeviation={1}, publishTimeInSecods={2}, deviation[{3}] = {4} as max deviation", + publishingInterval, + maxDeviation, + publishTimeInSeconds, + faultIndex, + faultDeviation); } } }