Advertisement
3psil0N

Umbraco U4-6626 workaround

May 26th, 2015
688
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 6.87 KB | None | 0 0
  1.     /// <summary>
  2.     /// This module implements a safer way of loading the XML cache that avoids http://issues.umbraco.org/issue/U4-6626
  3.     /// using a file system exclusive lock instead of a named semaphore.
  4.     ///
  5.     /// This module must be registered BEFORE <see cref="UmbracoModule" /> in web.config.
  6.     /// </summary>
  7.     public class XmlCacheSafeLoaderModule : IHttpModule
  8.     {
  9.         const int c_RetryDelaySeconds = 120;
  10.         const string c_LockFileVirtualPath = "~/App_Data/TEMP/XmlCacheSafeLoader.lock";
  11.  
  12.         static readonly string[] s_AspNetHandlerExtensions = { ".aspx", ".ashx", ".asmx", ".axd", ".svc" };
  13.         static readonly CancellationTokenSource s_ModuleExecutionCancellation = new CancellationTokenSource();
  14.  
  15.         public void Init(HttpApplication app)
  16.         {
  17.             app.BeginRequest += (sender, args) =>
  18.             {
  19.                 var httpContext = new HttpContextWrapper(((HttpApplication)sender).Context);
  20.                 UmbracoContext.EnsureContext(
  21.                     httpContext,
  22.                     ApplicationContext.Current,
  23.                     new WebSecurity(httpContext, ApplicationContext.Current),
  24.                     true);
  25.             };
  26.  
  27.             app.PostResolveRequestCache += (sender, e) =>
  28.             {
  29.                 var httpContext = new HttpContextWrapper(((HttpApplication)sender).Context);
  30.                 ProcessRequest(httpContext);
  31.             };
  32.         }
  33.  
  34.         static void ProcessRequest(HttpContextBase httpContext)
  35.         {
  36.             if (s_ModuleExecutionCancellation.IsCancellationRequested
  37.                 || httpContext.Request.HttpMethod.ToUpperInvariant() != "GET"
  38.                 || IsClientSideRequest(httpContext.Request.Url)
  39.                 || IsDefaultBackOfficeRequest(httpContext.Request.Url))
  40.             {
  41.                 return;
  42.             }
  43.  
  44.             if (UmbracoContext.Current == null)
  45.             {
  46.                 throw new InvalidOperationException(
  47.                     "The UmbracoContext.Current is null, ProcessRequest cannot proceed unless there is a current UmbracoContext");
  48.             }
  49.  
  50.             var lockFilePath = httpContext.Server.MapPath(c_LockFileVirtualPath);
  51.             IDisposable fileSystemLock;
  52.  
  53.             try
  54.             {
  55.                 fileSystemLock = File.Open(lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
  56.             }
  57.             catch (Exception ex)
  58.             {
  59.  
  60.  
  61.                 var response = httpContext.Response;
  62.                 response.Clear();
  63.                 response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
  64.                 response.TrySkipIisCustomErrors = true;
  65.                 response.ContentType = "text/plain";
  66.  
  67.                 LogHelper.Error<XmlCacheSafeLoaderModule>(
  68.                     string.Format(
  69.                         "Failed to acquire XML cache file system lock due to error [{0}].",
  70.                         ex.Message),
  71.                     ex);
  72.  
  73.                 response.Write(
  74.                     string.Format(
  75.                         "Please wait until website restart is complete. This page will automatically refresh every {0} seconds.",
  76.                         c_RetryDelaySeconds));
  77.  
  78.                 response.Headers["Retry-After"] = c_RetryDelaySeconds.ToString();
  79.                 response.Headers["Refresh"] = c_RetryDelaySeconds + ";URL=" + httpContext.Request.Url;
  80.                 response.Flush();
  81.                 response.End();
  82.                 return;
  83.             }
  84.  
  85.             var stopwatch = Stopwatch.StartNew();
  86.             try
  87.             {
  88.                 using (fileSystemLock)
  89.                 {
  90.                     LogHelper.Warn<XmlCacheSafeLoaderModule>("Acquired file system lock.");
  91.                     ClosePossiblyOpenStuckSemaphoreLeftByUmbraco725();
  92.  
  93.                     LogHelper.Warn<XmlCacheSafeLoaderModule>("Loading XML cache...");
  94.                     var contentInstance = content.Instance;
  95.  
  96.                     // This should always return false because the XML is loaded in the content constructor
  97.                     // which is called when you get the singleton "Instance" property. But to protect from
  98.                     // future code changes in that class, we check it anyway.
  99.                     while (contentInstance.isInitializing)
  100.                     {
  101.                         Thread.Sleep(1000);
  102.                     }
  103.                 }
  104.  
  105.                 LogHelper.Warn<XmlCacheSafeLoaderModule>(
  106.                     "XML cache load complete. Released file system lock after {0}",
  107.                     () => stopwatch.Elapsed);
  108.  
  109.                 s_ModuleExecutionCancellation.Cancel();
  110.                 LogHelper.Info<XmlCacheSafeLoaderModule>("Module deactivated.");
  111.             }
  112.             catch (Exception ex)
  113.             {
  114.                 LogHelper.Error<XmlCacheSafeLoaderModule>(
  115.                     "Failed to load the XML file. Released file system lock after " + stopwatch.Elapsed,
  116.                     ex);
  117.                 throw;
  118.             }
  119.         }
  120.  
  121.         // The name of the semaphore was found by decompiling the umbraco.content.InitializeFileLock() method.
  122.         // This works as of Umbraco 7.2.5 but may be useless in the future.
  123.         static void ClosePossiblyOpenStuckSemaphoreLeftByUmbraco725()
  124.         {
  125.             Semaphore stuckSemaphore;
  126.             var semaphoreName = HostingEnvironment.ApplicationID + "/XmlStore/XmlFile";
  127.  
  128.             if (Semaphore.TryOpenExisting(semaphoreName, out stuckSemaphore))
  129.             {
  130.                 stuckSemaphore.Dispose();
  131.                 LogHelper.Warn<XmlCacheSafeLoaderModule>("Closed left-over semaphore {0}", () => semaphoreName);
  132.             }
  133.             else
  134.             {
  135.                 LogHelper.Info<XmlCacheSafeLoaderModule>(
  136.                     "Semaphore {0} was not found. Continuing...",
  137.                     () => semaphoreName);
  138.             }
  139.         }
  140.  
  141.         public void Dispose()
  142.         {
  143.         }
  144.  
  145.         // Copy of internal Umbraco.Core.UriExtensions.IsClientSideRequest :-(
  146.         static bool IsClientSideRequest(Uri url)
  147.         {
  148.             string extension = Path.GetExtension(url.LocalPath);
  149.             return !extension.IsNullOrWhiteSpace() && !s_AspNetHandlerExtensions.Any(s => extension.InvariantEquals(s));
  150.         }
  151.  
  152.         // Copy of internal Umbraco.Core.UriExtensions.IsDefaultBackOfficeRequest :-(
  153.         static bool IsDefaultBackOfficeRequest(Uri url)
  154.         {
  155.             return url.AbsolutePath.InvariantEquals(GlobalSettings.Path.TrimEnd("/"))
  156.                    || url.AbsolutePath.InvariantEquals(GlobalSettings.Path.EnsureEndsWith('/'))
  157.                    || (url.AbsolutePath.InvariantEquals(GlobalSettings.Path.EnsureEndsWith('/') + "Default")
  158.                        || url.AbsolutePath.InvariantEquals(GlobalSettings.Path.EnsureEndsWith('/') + "Default/"));
  159.         }
  160.     }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement