Пример #1
0
        public void Initialize_WhenCalledMultipleTimesConcurrently_ExecutesOnce()
        {
            var executionCount = 0;
            var config         = CachedObjectFactory
                                 .ConfigureFor(GetValue)
                                 .WithRefreshInterval(TimeSpan.FromMinutes(1));

            using var cachedObject = BuildCachedObject(config);

            var tasks = Enumerable
                        .Range(0, 10)
                        .Select(_ => Task.Run(() => cachedObject.InitializeAsync()))
                        .ToArray();

            Task.WaitAll(tasks);

            executionCount.Should().Be(1);

            async Task <DateTime> GetValue()
            {
                await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);

                Interlocked.Increment(ref executionCount);
                return(DateTime.UtcNow);
            }
        }
        public async Task InitializationIsDeferredUntilRequired()
        {
            using var source = CachedObjectFactory
                               .ConfigureFor(() => DateTime.UtcNow)
                               .Build();

            using var mapped = source.Map(x => DateTime.UtcNow);

            DateTime dateOfSourceInitialization      = default;
            DateTime dateOfDestinationInitialization = default;

            source.OnInitialized += (_, __) => dateOfSourceInitialization = DateTime.UtcNow;
            mapped.OnInitialized += (_, __) => dateOfDestinationInitialization = DateTime.UtcNow;

            await source.InitializeAsync().ConfigureAwait(false);

            await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);

            await mapped.InitializeAsync().ConfigureAwait(false);

            source.Value.Should().BeCloseTo(dateOfSourceInitialization, TimeSpan.FromMilliseconds(100));
            mapped.Value.Should().BeCloseTo(dateOfDestinationInitialization, TimeSpan.FromMilliseconds(100));
            var diff = mapped.Value - source.Value;

            diff.Should().BeCloseTo(TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(100));
        }
Пример #3
0
        public void UpdateableCachedObject_WithRefreshSchedule_RefreshesValueAtExpectedTimes(string cronExpression, int intervalSeconds)
        {
            var countdown = new CountdownEvent(3);

            var refreshDates = new List <DateTime>();

            var cachedObject = CachedObjectFactory
                               .ConfigureFor(async() =>
            {
                var start = DateTime.UtcNow;
                await Task.Delay(TimeSpan.FromMilliseconds(50)).ConfigureAwait(false);
                return(start);
            })
                               .WithUpdates <bool>((_, __) => DateTime.UtcNow)
                               .WithRefreshSchedule(cronExpression, true)
                               .OnValueRefresh(r =>
            {
                if (r.IsResultOfInitialization)
                {
                    return;
                }

                refreshDates.Add(r.NewValue);
                if (!countdown.IsSet)
                {
                    countdown.Signal();
                }
            })
                               .Build();

            RunTest(cachedObject, countdown, refreshDates, intervalSeconds);
        }
        public void ValueIsUpdatedEachTimeSourceIsUpdated(bool async)
        {
            using var source = CachedObjectFactory
                               .ConfigureFor(() => DateTime.UtcNow.Ticks)
                               .WithUpdates <bool>((_, __) => DateTime.UtcNow.Ticks)
                               .Build();

            using var signal = new AutoResetEvent(false);

            using var mapped = async
                ? source.MapAsync(x => Task.Delay(TimeSpan.FromMilliseconds(20)).ContinueWith(_ => - x))
                : source.Map(x => - x);

            mapped.Initialize();
            mapped.OnValueRefreshed += (_, __) => signal.Set();

            for (var i = 0; i < 10; i++)
            {
                source.UpdateValue(true);

                signal.WaitOne(TimeSpan.FromSeconds(1)).Should().BeTrue();

                mapped.Version.Should().Be(i + 2);
                mapped.Value.Should().Be(-source.Value);
            }
        }
        public void FunctionProvidedToMapSourceUpdates_ValueIsUpdatedEachTimeSourceIsUpdated(bool async)
        {
            using var source = CachedObjectFactory
                               .ConfigureFor(() => 1)
                               .WithUpdates <int>((current, updates) => current + updates)
                               .Build();

            using var signal = new AutoResetEvent(false);

            using var mapped = async
                ? source.MapAsync(x => Task.Delay(TimeSpan.FromMilliseconds(20)).ContinueWith(_ => - x), (current, sourceValue, updates) => Task.FromResult(current + updates))
                : source.Map(x => - x, (current, sourceValue, updates) => current + updates);

            mapped.Initialize();
            mapped.OnValueUpdated += (_, __) => signal.Set();

            for (var i = 0; i < 10; i++)
            {
                source.UpdateValue(i);

                signal.WaitOne(TimeSpan.FromSeconds(1)).Should().BeTrue();

                mapped.Version.Should().Be(i + 2);
                mapped.Value.Should().Be(source.Value - 2);
            }
        }
        public void DisposePropagatesToAllChildren()
        {
            using var source = CachedObjectFactory
                               .ConfigureFor(() => DateTime.UtcNow)
                               .Build();

            var cachedObjects = new ICachedObject <DateTime> [10];

            var previous = source;

            for (var i = 0; i < 10; i++)
            {
                var current = previous.Map(x => x.AddDays(1));
                cachedObjects[i] = current;
                previous         = current;
            }

            cachedObjects.Last().Initialize();

            foreach (var cachedObject in cachedObjects)
            {
                cachedObject.State.Should().Be(CachedObjectState.Ready);
            }

            source.Dispose();

            foreach (var cachedObject in cachedObjects)
            {
                cachedObject.State.Should().Be(CachedObjectState.Disposed);
            }
        }
        public void MappedCachedObjectsCanBeChained()
        {
            using var source = CachedObjectFactory
                               .ConfigureFor(() => DateTime.UtcNow)
                               .Build();

            var cachedObjects = new ICachedObject <DateTime> [10];

            var previous = source;

            for (var i = 0; i < 10; i++)
            {
                var current = previous.Map(x => x.AddDays(1));
                cachedObjects[i] = current;
                previous         = current;
            }

            foreach (var cachedObject in cachedObjects)
            {
                cachedObject.State.Should().Be(CachedObjectState.PendingInitialization);
            }

            cachedObjects.Last().Initialize();

            foreach (var cachedObject in cachedObjects)
            {
                cachedObject.State.Should().Be(CachedObjectState.Ready);
            }

            for (var i = 0; i < 10; i++)
            {
                cachedObjects[i].Value.Should().Be(source.Value.AddDays(i + 1));
            }
        }
Пример #8
0
        public void WithUpdateSchedule_UpdatesValueAtExpectedTimes(string cronExpression, int intervalSeconds)
        {
            var countdown = new CountdownEvent(3);

            var updateDates = new List <DateTime>();

            var cachedObject = CachedObjectFactory
                               .ConfigureFor(() => DateTime.UtcNow)
                               .WithUpdatesAsync(async _ =>
            {
                var start = DateTime.UtcNow;
                await Task.Delay(TimeSpan.FromMilliseconds(50)).ConfigureAwait(false);
                return(start);
            }, (current, next) => Task.FromResult(next))
                               .WithUpdateSchedule(cronExpression, true)
                               .OnValueUpdate(e =>
            {
                updateDates.Add(e.NewValue);
                if (!countdown.IsSet)
                {
                    countdown.Signal();
                }
            })
                               .Build();

            RunTest(cachedObject, countdown, updateDates, intervalSeconds);
        }
Пример #9
0
        public void Initialize_IfFails_CanBeCalledAgain()
        {
            var count = 0;

            var config = CachedObjectFactory
                         .ConfigureFor(GetValue)
                         .WithRefreshInterval(TimeSpan.FromSeconds(1));

            using var cachedObject = BuildCachedObject(config);

            Action action = () => cachedObject.Initialize();

            for (var i = 0; i < 5; i++)
            {
                action.Should().Throw <Exception>();
            }

            cachedObject.State.Should().Be(CachedObjectState.PendingInitialization);

            action.Should().NotThrow();

            cachedObject.State.Should().Be(CachedObjectState.Ready);

            DateTime GetValue()
            {
                if (count++ < 5)
                {
                    throw new Exception();
                }

                return(DateTime.UtcNow);
            }
        }
        public void ValueIsMappedFromSourceCorrectly(bool async)
        {
            using var source = CachedObjectFactory
                               .ConfigureFor(() => DateTime.UtcNow.Ticks)
                               .Build();

            using var mapped = async
                ? source.MapAsync(x => Task.Delay(TimeSpan.FromMilliseconds(20)).ContinueWith(_ => - x))
                : source.Map(x => - x);

            mapped.Value.Should().Be(-source.Value);
        }
Пример #11
0
        public void Value_IfCalledWhenNotInitialized_WillInitializeValue()
        {
            var config = CachedObjectFactory
                         .ConfigureFor(() => DateTime.UtcNow)
                         .WithRefreshInterval(TimeSpan.FromMinutes(1));

            using var cachedObject = BuildCachedObject(config);

            cachedObject.State.Should().Be(CachedObjectState.PendingInitialization);
            var value = cachedObject.Value;

            cachedObject.State.Should().Be(CachedObjectState.Ready);

            value.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(100));
        }
Пример #12
0
        public void WithUpdateFunc_ValueIsUpdatedCorrectly()
        {
            using var cachedObject = CachedObjectFactory
                                     .ConfigureFor(() => 1)
                                     .WithUpdates(_ => 1, (current, updates) => current + updates)
                                     .Build();

            cachedObject.Value.Should().Be(1);

            for (var i = 2; i < 100; i++)
            {
                cachedObject.UpdateValue();
                cachedObject.Value.Should().Be(i);
                cachedObject.Version.Should().Be(i);
            }
        }
Пример #13
0
        public async Task UpdateAsync_RefreshValueAsync_UnderlyingTasksAlwaysRunOneAtATime()
        {
            var concurrentExecutions = 0;

            var cachedObject = CachedObjectFactory
                               .ConfigureFor(RefreshValue)
                               .WithUpdatesAsync(_ => Task.FromResult(1), UpdateValue)
                               .Build();

            cachedObject.Initialize();

            var tasks = Enumerable
                        .Range(0, 10)
                        .SelectMany(_ => new[] { cachedObject.UpdateValueAsync(), cachedObject.RefreshValueAsync() })
                        .ToList();

            await Task.WhenAll(tasks).ConfigureAwait(false);

            async Task <int> RefreshValue()
            {
                if (Interlocked.Increment(ref concurrentExecutions) > 1)
                {
                    throw new Exception();
                }

                await Task.Delay(TimeSpan.FromMilliseconds(50)).ConfigureAwait(false);

                Interlocked.Decrement(ref concurrentExecutions);

                return(0);
            }

            async Task <int> UpdateValue(int current, int input)
            {
                if (Interlocked.Increment(ref concurrentExecutions) > 1)
                {
                    throw new Exception();
                }

                await Task.Delay(TimeSpan.FromMilliseconds(50)).ConfigureAwait(false);

                Interlocked.Decrement(ref concurrentExecutions);

                return(current + input);
            }
        }
Пример #14
0
        public async Task UpdateAsync_RefreshValueAsync_RefreshesTakePriority()
        {
            var lockObj = new object();

            var cachedObject = CachedObjectFactory
                               .ConfigureFor(RefreshValue)
                               .WithUpdatesAsync(_ => Task.FromResult(1), UpdateValue)
                               .Build();

            cachedObject.Initialize();

            var tasksInOrderOfCompletion = new List <(Task Task, bool IsRefresh)>();

            var updates   = Enumerable.Range(0, 10).Select(_ => RunTask(cachedObject.UpdateValueAsync(), false)).ToList();
            var refreshes = Enumerable.Range(0, 10).Select(_ => RunTask(cachedObject.RefreshValueAsync(), true)).ToList();

            await Task.WhenAll(updates.Concat(refreshes)).ConfigureAwait(false);

            for (var i = 0; i < 20; i++)
            {
                if (i == 0)
                {
                    tasksInOrderOfCompletion[i].IsRefresh.Should().BeFalse();
                }
                else if (i <= 10)
                {
                    tasksInOrderOfCompletion[i].IsRefresh.Should().BeTrue();
                }
                else
                {
                    tasksInOrderOfCompletion[i].IsRefresh.Should().BeFalse();
                }
            }

            Task RunTask(Task task, bool isRefresh)
            {
                return(task.ContinueWith(t =>
                {
                    lock (lockObj)
                        tasksInOrderOfCompletion.Add((t, isRefresh));
                }));
            }
Пример #15
0
        public async Task Status_UpdatedCorrectly()
        {
            var config = CachedObjectFactory
                         .ConfigureFor(GetValue)
                         .WithRefreshInterval(TimeSpan.FromMinutes(1));

            using var cachedObject = BuildCachedObject(config);

            cachedObject.State.Should().Be(CachedObjectState.PendingInitialization);

            var task = cachedObject.InitializeAsync();

            cachedObject.State.Should().Be(CachedObjectState.InitializationInProgress);

            await task.ConfigureAwait(false);

            cachedObject.State.Should().Be(CachedObjectState.Ready);

            cachedObject.Dispose();

            cachedObject.State.Should().Be(CachedObjectState.Disposed);
        public void IfMappingFunctionThrowsException_LaterUpdatesAreStillProcessed()
        {
            using var source = CachedObjectFactory
                               .ConfigureFor(() => DateTime.UtcNow)
                               .Build();

            var count = 0;

            using var mapped = source.Map(x =>
            {
                if (count++ % 2 == 1)
                {
                    throw new Exception();
                }

                return(x);
            });

            using var signal = new AutoResetEvent(false);

            mapped.Initialize();
            mapped.OnValueRefreshed        += (_, __) => signal.Set();
            mapped.OnValueRefreshException += (_, __) => signal.Set();

            for (var i = 0; i < 10; i++)
            {
                source.RefreshValue();

                signal.WaitOne(TimeSpan.FromSeconds(1)).Should().BeTrue();

                if (i % 2 == 0)
                {
                    mapped.Value.Should().BeBefore(source.Value);
                }
                else
                {
                    mapped.Value.Should().Be(source.Value);
                }
            }
        }
        public async Task MultipleConcurrentCallsToInitialize_MappingFunctionCalledOnce()
        {
            using var source = CachedObjectFactory
                               .ConfigureFor(() => DateTime.UtcNow)
                               .Build();

            var executions = 0;

            using var mapped = source.Map(x =>
            {
                Interlocked.Increment(ref executions);
                Thread.Sleep(TimeSpan.FromSeconds(1));
                return(x);
            });

            var tasks = Enumerable
                        .Range(0, 10).Select(_ => Task.Run(() => mapped.InitializeAsync()))
                        .ToArray();

            await Task.WhenAll(tasks).ConfigureAwait(false);

            executions.Should().Be(1);
        }
Пример #18
0
        public void OnceInitialized_ValueIsRefreshedAtRegularIntervals()
        {
            var values = new List <DateTime>();

            var countdown = new CountdownEvent(5);

            var config = CachedObjectFactory
                         .ConfigureFor(GetValue)
                         .WithRefreshInterval(TimeSpan.FromSeconds(1));

            using var cachedObject = BuildCachedObject(config);

            cachedObject.Initialize();

            countdown.Wait();

            for (var i = 1; i < 5; i++)
            {
                var diff = values[i] - values[i - 1];
                diff.Should().BeGreaterThan(TimeSpan.FromSeconds(0.5)).And.BeLessThan(TimeSpan.FromSeconds(1.5));
            }

            DateTime GetValue()
            {
                var now = DateTime.UtcNow;

                values.Add(now);

                if (!countdown.IsSet)
                {
                    countdown.Signal();
                }

                return(now);
            }
        }
Пример #19
0
 /// <summary>
 /// コンストラクタで、ITextureCachedObject派生クラスを生成するfactoryを渡してやる。
 ///
 /// 例)
 ///		TextureLoader loader = new TextureLoader(delegate {
 ///           return new GlTexture(); });
 /// </summary>
 /// <remarks>
 /// ディフォルトではcache size = 64MB
 /// OpenGLを描画に使っている場合、テクスチャサイズは2のべき乗でalignされる。
 /// たとえば、640×480の画像ならば1024×512(32bpp)になる。
 /// よって、1024×512×4byte = 2097152 ≒ 2MB消費する。
 /// 50MBだと640×480の画像をおおよそ25枚読み込めると考えて良いだろう。
 /// </remarks>
 /// <param name="factory"></param>
 public TextureLoader(CachedObjectFactory factory)
 {
     Factory = factory;
 }