diff --git a/CHANGELOG.md b/CHANGELOG.md index 02a6280..7550f71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ Changelog =================== +v5.3.0 +- Fixed Thread safety issues. + v5.2.0 - Added es-CO culture, thanks to @canro91 diff --git a/src/DateTimeExtensions/Common/ConcurrentLazyDictionary.cs b/src/DateTimeExtensions/Common/ConcurrentLazyDictionary.cs new file mode 100644 index 0000000..2b23c77 --- /dev/null +++ b/src/DateTimeExtensions/Common/ConcurrentLazyDictionary.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Concurrent; + +namespace DateTimeExtensions.Common +{ + internal class ConcurrentLazyDictionary + { + private ConcurrentDictionary> innerDictionary; + + public ConcurrentLazyDictionary() + { + innerDictionary = new ConcurrentDictionary>(); + } + + public TValue GetOrAdd(TKey key, Func valueFactory) + { + var lazyValue = innerDictionary.GetOrAdd(key, new Lazy(valueFactory)); + return lazyValue.Value; + } + } +} diff --git a/src/DateTimeExtensions/DateTimeExtensions.csproj b/src/DateTimeExtensions/DateTimeExtensions.csproj index 3ac0bdc..454530e 100644 --- a/src/DateTimeExtensions/DateTimeExtensions.csproj +++ b/src/DateTimeExtensions/DateTimeExtensions.csproj @@ -15,9 +15,9 @@ false false false - 5.2.0 - 5.2.0.0 - 5.2.0.0 + 5.3.0 + 5.3.0.0 + 5.3.0.0 https://github.com/joaomatossilva/DateTimeExtensions/raw/master/assets/datetimeextensions-60-logo.png true $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb diff --git a/src/DateTimeExtensions/WorkingDays/CultureStrategies/HolidayStrategyBase.cs b/src/DateTimeExtensions/WorkingDays/CultureStrategies/HolidayStrategyBase.cs index 7a4a54b..9e0efab 100644 --- a/src/DateTimeExtensions/WorkingDays/CultureStrategies/HolidayStrategyBase.cs +++ b/src/DateTimeExtensions/WorkingDays/CultureStrategies/HolidayStrategyBase.cs @@ -18,6 +18,7 @@ #endregion +using DateTimeExtensions.Common; using System; using System.Collections.Generic; using System.Linq; @@ -29,26 +30,18 @@ public abstract class HolidayStrategyBase : IHolidayStrategy { protected readonly IList InnerHolidays; - private readonly IDictionary> holidaysObservancesCache; + private readonly ConcurrentLazyDictionary> holidaysObservancesCache; protected HolidayStrategyBase() { - holidaysObservancesCache = new Dictionary>(); + holidaysObservancesCache = new ConcurrentLazyDictionary>(); this.InnerHolidays = new List(); } public bool IsHoliDay(DateTime day) { - this.CheckYearHasMap(day.Year); - return holidaysObservancesCache[day.Year].Any(m => m.Key.Date == day.Date); - } - - private void CheckYearHasMap(int year) - { - if (!holidaysObservancesCache.ContainsKey(year)) - { - holidaysObservancesCache.Add(year, this.BuildObservancesMap(year)); - } + var map = holidaysObservancesCache.GetOrAdd(day.Year, () => BuildObservancesMap(day.Year)); + return map.Any(m => m.Key.Date == day.Date); } protected virtual IDictionary BuildObservancesMap(int year) @@ -66,8 +59,8 @@ public virtual IEnumerable Holidays public virtual IEnumerable GetHolidaysOfYear(int year) { - this.CheckYearHasMap(year); - return holidaysObservancesCache[year].Select(m => m.Value); + var map = holidaysObservancesCache.GetOrAdd(year, () => BuildObservancesMap(year)); + return map.Select(m => m.Value); } } } \ No newline at end of file diff --git a/src/DateTimeExtensions/WorkingDays/NthDayOfWeekAfterDayHoliday.cs b/src/DateTimeExtensions/WorkingDays/NthDayOfWeekAfterDayHoliday.cs index 204672a..fff449a 100644 --- a/src/DateTimeExtensions/WorkingDays/NthDayOfWeekAfterDayHoliday.cs +++ b/src/DateTimeExtensions/WorkingDays/NthDayOfWeekAfterDayHoliday.cs @@ -18,10 +18,8 @@ #endregion +using DateTimeExtensions.Common; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace DateTimeExtensions.WorkingDays { @@ -30,7 +28,7 @@ public class NthDayOfWeekAfterDayHoliday : Holiday private readonly DayOfWeek dayOfWeek; private readonly int count; private readonly Holiday baseHoliday; - private readonly IDictionary dayCache; + private readonly ConcurrentLazyDictionary dayCache; public NthDayOfWeekAfterDayHoliday(string name, int count, DayOfWeek dayOfWeek, int month, int day) : this(name, count, dayOfWeek, new FixedHoliday(name, month, day)) @@ -56,18 +54,12 @@ public NthDayOfWeekAfterDayHoliday(string name, int count, DayOfWeek dayOfWeek, this.count = count; this.dayOfWeek = dayOfWeek; this.baseHoliday = baseHoliday; - dayCache = new Dictionary(); + dayCache = new ConcurrentLazyDictionary(); } public override DateTime? GetInstance(int year) { - if (dayCache.ContainsKey(year)) - { - return dayCache[year]; - } - var day = CalculateDayInYear(year); - dayCache.Add(year, day); - return day; + return dayCache.GetOrAdd(year, () => CalculateDayInYear(year)); } public override bool IsInstanceOf(DateTime date) diff --git a/src/DateTimeExtensions/WorkingDays/NthDayOfWeekInMonthHoliday.cs b/src/DateTimeExtensions/WorkingDays/NthDayOfWeekInMonthHoliday.cs index c51ee91..2bf4345 100644 --- a/src/DateTimeExtensions/WorkingDays/NthDayOfWeekInMonthHoliday.cs +++ b/src/DateTimeExtensions/WorkingDays/NthDayOfWeekInMonthHoliday.cs @@ -18,6 +18,7 @@ #endregion +using DateTimeExtensions.Common; using System; using System.Collections.Generic; using System.Linq; @@ -37,7 +38,7 @@ public class NthDayOfWeekInMonthHoliday : Holiday private DayOfWeek dayOfWeek; private CountDirection direction; private int month; - private IDictionary dayCache; + private ConcurrentLazyDictionary dayCache; public NthDayOfWeekInMonthHoliday(string name, int count, DayOfWeek dayOfWeek, int month, CountDirection direction) @@ -47,18 +48,12 @@ public NthDayOfWeekInMonthHoliday(string name, int count, DayOfWeek dayOfWeek, i this.dayOfWeek = dayOfWeek; this.month = month; this.direction = direction; - dayCache = new Dictionary(); + dayCache = new ConcurrentLazyDictionary(); } public override DateTime? GetInstance(int year) { - if (dayCache.ContainsKey(year)) - { - return dayCache[year]; - } - var day = CalculateDayInYear(year); - dayCache.Add(year, day); - return day; + return dayCache.GetOrAdd(year, () => CalculateDayInYear(year)); } public override bool IsInstanceOf(DateTime date) diff --git a/tests/DateTimeExtensions.Tests/ThreadSafeTests.cs b/tests/DateTimeExtensions.Tests/ThreadSafeTests.cs new file mode 100644 index 0000000..d73b6b9 --- /dev/null +++ b/tests/DateTimeExtensions.Tests/ThreadSafeTests.cs @@ -0,0 +1,25 @@ +using DateTimeExtensions.WorkingDays; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DateTimeExtensions.Tests +{ + [TestFixture] + public class ThreadSafeTests + { + [Test] + public void AddWorkingDays_MultipleThreads_CanCalculate() + { + //Arrange + var culture = new WorkingDayCultureInfo("en-US"); + var startDate = new DateTime(2018,5,1); + + //Act + Parallel.ForEach(Enumerable.Range(1,10), (i) => startDate.AddWorkingDays(i, culture)); + } + } +}