From 8f6ddfab94db9c659b6f26759e5f2a5927691dc6 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 9 Nov 2010 11:17:09 -0800 Subject: [PATCH] Adding clock based cache invalidation --HG-- branch : dev --- .../Caching/ClockCachingTests.cs | 85 +++++++++++++++++++ .../Orchard.Framework.Tests.csproj | 1 + src/Orchard.Tests/Stubs/StubClock.cs | 10 +++ src/Orchard/Services/Clock.cs | 38 +++++++++ 4 files changed, 134 insertions(+) create mode 100644 src/Orchard.Tests/Caching/ClockCachingTests.cs diff --git a/src/Orchard.Tests/Caching/ClockCachingTests.cs b/src/Orchard.Tests/Caching/ClockCachingTests.cs new file mode 100644 index 000000000..ac6618406 --- /dev/null +++ b/src/Orchard.Tests/Caching/ClockCachingTests.cs @@ -0,0 +1,85 @@ +using System; +using Autofac; +using NUnit.Framework; +using Orchard.Caching; +using Orchard.Services; +using Orchard.Tests.Stubs; + +namespace Orchard.Tests.Caching { + [TestFixture] + public class ClockCachingTests { + private IContainer _container; + private ICacheManager _cacheManager; + private StubClock _clock; + + [SetUp] + public void Init() { + var builder = new ContainerBuilder(); + builder.RegisterModule(new CacheModule()); + builder.RegisterType().As(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As(); + builder.RegisterInstance(_clock = new StubClock()); + builder.RegisterType().As(); + _container = builder.Build(); + _cacheManager = _container.Resolve(new TypedParameter(typeof(Type), GetType())); + } + + [Test] + public void WhenAbsoluteShouldHandleAbsoluteTime() { + var inOneSecond = _clock.UtcNow.AddSeconds(1); + var cached = 0; + + // each call after the specified datetime will be reevaluated + Func retrieve = () + => _cacheManager.Get("testItem", + ctx => { + ctx.Monitor(_clock.WhenUtc(inOneSecond)); + return ++cached; + }); + + Assert.That(retrieve(), Is.EqualTo(1)); + + for ( var i = 0; i < 10; i++ ) { + Assert.That(retrieve(), Is.EqualTo(1)); + } + + _clock.Advance(TimeSpan.FromSeconds(1)); + + Assert.That(retrieve(), Is.EqualTo(2)); + Assert.That(retrieve(), Is.EqualTo(3)); + Assert.That(retrieve(), Is.EqualTo(4)); + } + + [Test] + public void WhenAbsoluteShouldHandleAbsoluteTimeSpan() { + var cached = 0; + + // each cached value has a lifetime of the specified duration + Func retrieve = () + => _cacheManager.Get("testItem", + ctx => { + ctx.Monitor(_clock.When(TimeSpan.FromSeconds(1))); + return ++cached; + }); + + Assert.That(retrieve(), Is.EqualTo(1)); + + for ( var i = 0; i < 10; i++ ) { + Assert.That(retrieve(), Is.EqualTo(1)); + } + + _clock.Advance(TimeSpan.FromSeconds(1)); + + for ( var i = 0; i < 10; i++ ) { + Assert.That(retrieve(), Is.EqualTo(2)); + } + + _clock.Advance(TimeSpan.FromSeconds(1)); + + for ( var i = 0; i < 10; i++ ) { + Assert.That(retrieve(), Is.EqualTo(3)); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 896f61908..ac5055160 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -160,6 +160,7 @@ + diff --git a/src/Orchard.Tests/Stubs/StubClock.cs b/src/Orchard.Tests/Stubs/StubClock.cs index e5c60bb5a..cc0b4f9df 100644 --- a/src/Orchard.Tests/Stubs/StubClock.cs +++ b/src/Orchard.Tests/Stubs/StubClock.cs @@ -1,4 +1,5 @@ using System; +using Orchard.Caching; using Orchard.Services; namespace Orchard.Tests.Stubs { @@ -16,5 +17,14 @@ namespace Orchard.Tests.Stubs { public DateTime FutureMoment(TimeSpan span) { return UtcNow.Add(span); } + + + public IVolatileToken When(TimeSpan duration) { + return new Clock.AbsoluteExpirationToken(this, duration); + } + + public IVolatileToken WhenUtc(DateTime absoluteUtc) { + return new Clock.AbsoluteExpirationToken(this, absoluteUtc); + } } } diff --git a/src/Orchard/Services/Clock.cs b/src/Orchard/Services/Clock.cs index 6e1b5a38b..e72f7fae1 100644 --- a/src/Orchard/Services/Clock.cs +++ b/src/Orchard/Services/Clock.cs @@ -4,6 +4,16 @@ using Orchard.Caching; namespace Orchard.Services { public interface IClock : IVolatileProvider { DateTime UtcNow { get; } + + /// + /// Each retrieved value is cached during the specified amount of time. + /// + IVolatileToken When(TimeSpan duration); + + /// + /// The cache is active until the specified time. Each subsequent access won't be cached. + /// + IVolatileToken WhenUtc(DateTime absoluteUtc); } public class Clock : IClock { @@ -11,5 +21,33 @@ namespace Orchard.Services { get { return DateTime.UtcNow; } } + public IVolatileToken When(TimeSpan duration) { + return new AbsoluteExpirationToken(this, duration); + } + + public IVolatileToken WhenUtc(DateTime absoluteUtc) { + return new AbsoluteExpirationToken(this, absoluteUtc); + } + + public class AbsoluteExpirationToken : IVolatileToken { + private readonly IClock _clock; + private readonly DateTime _invalidateUtc; + + public AbsoluteExpirationToken(IClock clock, DateTime invalidateUtc) { + _clock = clock; + _invalidateUtc = invalidateUtc; + } + + public AbsoluteExpirationToken(IClock clock, TimeSpan duration) { + _clock = clock; + _invalidateUtc = _clock.UtcNow.Add(duration); + } + + public bool IsCurrent { + get { + return _clock.UtcNow < _invalidateUtc; + } + } + } } }