Output cache exception since 1.8.1

Topics: Core, General, Installing Orchard, Troubleshooting
Jul 30, 2014 at 8:22 AM
Edited Jul 30, 2014 at 8:30 AM
We just upgraded our orchard cloud service to the latest 1.8.1 version (from 1.8.0). Since then, we have an exception on every page supposed to be cached by the Azure Output Cache if we leave the feature "Microsoft Azure Output Cache" enabled:
  • Page "http://www.example.org/nocache/" is marked with "[OutputCache(NoStore = true, Duration = 0)]" and does not throw
  • Page "http://www.example.org/cache/" is not marked with the attribute and throws an exception
  • If the user hits ctrl-F5 in his brower on the faulted page, it displays correctly (the only difference being the Cache-Control HTTP header set to "no-cache")
  • After the user logs in, pages don't throw exceptions anymore (supposedly because output cache is bypassed for authenticated pages)
We have left the default configuration concerning the output and database cache settings:
  • Caching enabled (co-located role with cache size = 30%, storage account credentials correctly set and orchard's default named caches left as is)
  • Orchard.Azure.OutputCache.HostIdentifier = "Orchard.Azure.Web"
  • Orchard.Azure.OutputCache.CacheName = "OutputCache"
  • Orchard.Azure.OutputCache.AuthorizationToken = ""
  • Orchard.Azure.DatabaseCache.HostIdentifier = "Orchard.Azure.Web"
  • Orchard.Azure.DatabaseCache.CacheName = "DatabaseCache"
  • Orchard.Azure.DatabaseCache.AuthorizationToken = ""
I suppose these settings should not be edited as long as we use the default In-Role Azure Cache, but this is not clear in the documentation.

Here are the details of the only exception I can catch when entering in remote debugging mode (which is also thrown in other methods but not always propagated to the user):
Object reference not set to an instance of an object.

Stack trace: 
   at Orchard.Azure.Services.Caching.CacheClientConfiguration.GetHashCode() in C:\Orchard\src\Orchard.Web\Modules\Orchard.Azure\Services\Caching\CacheClientConfiguration.cs:line 62
   at System.Collections.Generic.ObjectEqualityComparer`1.GetHashCode(T obj)
   at System.Collections.Concurrent.ConcurrentDictionary`2.TryGetValue(TKey key, TValue& value)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Orchard.Azure.Services.Caching.Output.AzureOutputCacheStorageProvider.get_Cache() in C:\Orchard\src\Orchard.Web\Modules\Orchard.Azure\Services\Caching\Output\AzureOutputCacheStorageProvider.cs:line 87
   at Orchard.Azure.Services.Caching.Output.AzureOutputCacheStorageProvider.<>c__DisplayClassf.<GetCacheItem>b__e() in C:\Orchard\src\Orchard.Web\Modules\Orchard.Azure\Services\Caching\Output\AzureOutputCacheStorageProvider.cs:line 173
   at Orchard.Azure.Services.Caching.Output.AzureOutputCacheStorageProvider.Retry[T](Func`1 function, Int32 times) in C:\Orchard\src\Orchard.Web\Modules\Orchard.Azure\Services\Caching\Output\AzureOutputCacheStorageProvider.cs:line 123
   at Orchard.Azure.Services.Caching.Output.AzureOutputCacheStorageProvider.SafeCall[T](Func`1 function) in C:\Orchard\src\Orchard.Web\Modules\Orchard.Azure\Services\Caching\Output\AzureOutputCacheStorageProvider.cs:line 106
   at Orchard.Azure.Services.Caching.Output.AzureOutputCacheStorageProvider.GetCacheItem(String key) in C:\Orchard\src\Orchard.Web\Modules\Orchard.Azure\Services\Caching\Output\AzureOutputCacheStorageProvider.cs:line 173
   at Orchard.OutputCache.Filters.OutputCacheFilter.OnActionExecuting(ActionExecutingContext filterContext) in C:\Orchard\src\Orchard.Web\Modules\Orchard.OutputCache\Filters\OutputCacheFilter.cs:line 252
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex)
The null object is "CacheClientConfiguration.AuthorizationToken", thus the config sample I provided above. Why should I fill an authorization token for In-Role caching?

To add to my pain, no orchard log is being written in the WADLogsTable table in Azure, but many (like, a lot) other diagnostic logs (see below) are written there, which makes it impossible to use. So the aforementioned exception should not be taken as the unquestionable source of the issue.
<Complaint> Add hard complaint :0
<CASClient> Updated partition table to (1-1921) generation: 635421474568213132:1000000000000000000000000 with transfer (1-1921) generation: 635421474568213132:1000000000000000000000000;
Jul 30, 2014 at 2:46 PM
Sebastien fixed a bunch of issues with the Azure cache provider a while back, I think that's when the dictionary was introduced which uses the CacheClientConfiguration as its key. I think in his scenario the authorization key was probably always set, so he never ran into this issue. But to me it's pretty obviously a bug, the GetHashCode() method of CacheClientConfiguration should never throw even if one of it's properties is null.

I have committed a fix in the 1.8.x branch (commit 15e95d4e). Please try it out and let me know if it solves the issue for you.
Marked as answer by Decorum on 7/30/2014 at 7:46 AM
Jul 30, 2014 at 3:31 PM
Thank you, I will try the fix as soon as possible and keep you informed.

By the way, why is the authorization token used in the hash process at all? I mean, when a user creates an account on my website I don't check the unicity based on a hash of the login/password, but only the login. I think there can't exist 2 cache configs with the same HostIdentifier/CacheName tuple, right? Which should be required properties anyway.
Jul 30, 2014 at 3:38 PM
Can't really comment on that, Sebastien would be able to I guess. I just made the smallest possible fix. But generally, I think it's dangerous to assume that the GetHashCode() override of a class will be used in a specific context where some properties might matter but others not. GetHashCode() should generally guarantee that if any property differs between two instances the returned hash should be different.
Jul 30, 2014 at 5:13 PM
I am ok with the change. I just created the hash with all the properties related to the configuration settings. BTW, what is the purpose of a cache configuration without AuthorizationToken?
Jul 30, 2014 at 6:02 PM
Edited Jul 30, 2014 at 6:04 PM
The fix does not seem to solve the issue we are experiencing. I inspected the changes, you wrote:
hash = hash * 23 + AuthorizationToken != null ? AuthorizationToken.GetHashCode() : 0;
Where you should have written:
hash = hash * 23 + (AuthorizationToken != null ? AuthorizationToken.GetHashCode() : 0);
Otherwise the line is interpreted as:
hash = (hash * 23 + AuthorizationToken) != null ? AuthorizationToken.GetHashCode() : 0;
which still fails, and more importantly, invalidates any previous value of the 'hash' variable in case AuthorizationToken is set.

The orchard documentation on Azure caching is still unclear to me. It states there are 2 types of caching in Azure: Role-based Cache and Cache Service. It does not state what changes should be made in the configuration to use one type or the other. I deduced (maybe that's where I'm wrong?) that the default configuration (the one I gave above) is for Role-based Cache, and that you uncheck "enable caching" in the "caching" tab and fill the HostIdentifier/CacheName/AuthorizationToken properties in the "settings" tab only when you use Azure Cache service (which provides these parameters in the management console once you create a caching service).

There is no such thing as an HostIdentifier or AuthorizationToken for In-Role caching, because the cache is stored in the cloud service's cluster itself (except for the runtime state which is stored in the storage account, for which we gave credentials in the "caching" tab of the cloud service role properties). At least that is how I understand it.
Jul 30, 2014 at 6:20 PM
I'll fix the code right now.

Regarding the documentation, this implementation should work on both services. BUT, these services are also begin deprecated in favor of the new Redis Cache service. I have already pushed the Redis provider on 1.x. I am using the Cache Service on weblogs.asp.net so it's pretty stable. I will switch to Redis very soon, and you should do so, maybe after I have done it myself and reported any issue with the module.
Jul 30, 2014 at 7:55 PM
Wow, that's really embarrassing. Sorry for that goofy non-fix. I swear I'm actually not that incompetent in real life. ;)

Regarding the documentation, I wrote it and I suppose it could be clearer, but in all fairness in the section configuration reference it is clearly specified which configuration settings apply to which caching service and which values they should be set to. You are correct in your understanding that HostIdentifier and AuthorizationToken are not used for role-based caching.
Jul 30, 2014 at 8:02 PM
No problem.

I missed that part of the configuration reference indeed, my bad!