Kilka dni temu, w zgodzie z tym co wykazała ankieta – Programiści to stworzenia nocne – kodowałem sobie jakieś mało znaczące rzeczy pomiędzy 2 a 3 nad ranem. Jako, że niewiele się o tej porze dzieje, można spokojnie skupić się na programowaniu :).

Jakież było moje zdziwienie, gdy o godzinie 2:28 nagle zaczęły spływać raporty błędów z dotnetomaniaka. Sam fakt pojawienia się błędów nie jest jeszcze jakiś niezwykły – wiadomo, od czasu do czasu może pójść coś nie tak. Bardziej interesujący był komunikat który pojawił się w mailu. A brzmiał on: Absolute time cannot be less than current time.


WTF – pomyślałem? Jaki czas i czemu nie może być mniejszy od obecnego? Dopiero po chwili dotarło do mnie co się dzieje. No tak – idiotyczna zmiana czasu (akurat ta jest mniej idiotyczna bo wracamy do naturalnego dla nas czasu ze sztucznego letniego).

Code Snippet
  1. public override ICollection<ITag> FindByUsage(int top)
  2. {
  3.     Check.Argument.IsNotNegativeOrZero(top, “top”);
  4.  
  5.     string cacheKey = “tagsByUsage:{0}”.FormatWith(top);
  6.  
  7.     ICollection<ITag> result;
  8.  
  9.     Cache.TryGet(cacheKey, out result);
  10.  
  11.     if (result == null)
  12.     {
  13.         result = base.FindByUsage(top);
  14.  
  15.         if ((!result.IsNullOrEmpty()) && (!Cache.Contains(cacheKey)))
  16.         {
  17.             Cache.Set(cacheKey, result, SystemTime.Now().AddMinutes(_cacheDurationInMinutes));
  18.         }
  19.     }
  20.  
  21.     return result;
  22. }

Powyższy kod powodował problem, a dokładniej wnętrzności metody Cache.Set. A jak ona wygląda?

Code Snippet
  1. public void Set(string key, T value, DateTime absoluteExpiration)
  2. {
  3.     Check.Argument.IsNotEmpty(key, “key”);
  4.     Check.Argument.IsNotInPast(absoluteExpiration, “absoluteExpiration”);
  5.  
  6.     RemoveIfExists(key);
  7.  
  8.     _manager.Add(key, value, CacheItemPriority.Normal, null, new AbsoluteTime(absoluteExpiration.ToLocalTime()));
  9. }

Na początku sądziłem, że problem leży w EnterpriseLibrary Caching Block, z którego korzysta portal, ale okazało się, że w nim wszystko jest w porządku. Co prawda wyjątek leci od nich, ale dostają czas w przeszłości – co mogą zrobić? W przypadku użycia drugiej metody (z parametrem TimeSpan), kod zachowuje się poprawnie.

Zmiana czasu z 3:00 na 2:00 spowodowała, że fragment próbujący ustawić czas wygaśnięcia obiektu w cache będzie wcześniejszy niż czas dodania tegoż obiektu. I stąd komunikat. Problemem jest użycie czasu UTC i zmiana go do LocalTime przy generowaniu obiektu AbsoluteTime.

Czyj to tak na prawdę jest problem? KiGGa czy EntLib’a? Wydaje mi się, że bardziej KiGG’a bo używa metody z parametrem AbsoluteTime, który ustawia nieprawidłowo. Robi to jednak nieświadomie, bo błędne działanie jest tylko raz w roku.

Pytanie tylko jak pamiętać o takich przypadkach i co ciekawsze jak je testować i zabezpieczać się aby nie występowały? Czy lepiej olać to i nie przejmować się tak rzadko występującymi problemami?