예제 #1
0
        public bool Match(CartRuleContext context, RuleExpression expression)
        {
            var sessionKey = context.SessionKey;
            var lockKey    = "rule:cart:carttotalrule:" + sessionKey.ToString();

            if (KeyedLock.IsLockHeld(lockKey))
            {
                return(false);
            }

            // We must prevent the rule from indirectly calling itself. It would cause a stack overflow on cart page.
            using (KeyedLock.Lock(lockKey))
            {
                var cart = _shoppingCartService.GetCartItems(context.Customer, ShoppingCartType.ShoppingCart, context.Store.Id);

                var cartTotal = ((decimal?)_orderTotalCalculationService.GetShoppingCartTotal(cart)) ?? decimal.Zero;

                // Currency values must be rounded, otherwise unexpected results may occur.
                var money = new Money(cartTotal, context.WorkContext.WorkingCurrency);
                cartTotal = money.RoundedAmount;

                var result = expression.Operator.Match(cartTotal, expression.Value);
                return(result);
            }
        }
예제 #2
0
        public virtual bool TryGet(string key, out object value)
        {
            value = null;

            if (!Enabled)
            {
                return(false);
            }

            key = HashKey(key);
            var now = DateTime.UtcNow;

            var entry = _cache.Get <DbCacheEntry>(key, independent: true);

            if (entry != null)
            {
                if (entry.HasExpired(now))
                {
                    using (KeyedLock.Lock(key))
                    {
                        InvalidateItemUnlocked(entry);
                    }
                }
                else
                {
                    value = entry.Value;
                    return(true);
                }
            }

            return(false);
        }
예제 #3
0
        public void LockOnSameKey2()
        {
            var kl = new KeyedLock <int>();
            var cd = new ConcurrentDictionary <int, int>();

            var inLock = false;

            Parallel.ForEach(new int[] { 1, 1, 1, 1, 1, 1 }, (key) =>
            {
                TestContext.WriteLine($"{System.Threading.Thread.CurrentThread.ManagedThreadId} X");

                kl.Lock <int>(key, () =>
                {
                    Assert.IsFalse(inLock);

                    inLock = true;
                    TestContext.WriteLine($"{System.Threading.Thread.CurrentThread.ManagedThreadId} XX");
                    System.Threading.Thread.Sleep(10);
                    TestContext.WriteLine($"{System.Threading.Thread.CurrentThread.ManagedThreadId} XXX");
                    inLock = false;
                    return(key);
                });

                TestContext.WriteLine($"{System.Threading.Thread.CurrentThread.ManagedThreadId} XXXX");
            });
        }
예제 #4
0
        public bool Match(CartRuleContext context, RuleExpression expression)
        {
            var lockKey = $"rule:cart:cartsubtotalrule:{Thread.CurrentThread.ManagedThreadId}-{expression.Id}";

            if (KeyedLock.IsLockHeld(lockKey))
            {
                //$"locked: {lockKey}".Dump();
                return(false);
            }

            // We must prevent the rule from indirectly calling itself. It would cause a stack overflow on cart page.
            using (KeyedLock.Lock(lockKey))
            {
                var cart = _shoppingCartService.GetCartItems(context.Customer, ShoppingCartType.ShoppingCart, context.Store.Id);

                _orderTotalCalculationService.GetShoppingCartSubTotal(cart, out _, out _, out var cartSubtotal, out _);

                // Currency values must be rounded, otherwise unexpected results may occur.
                var money = new Money(cartSubtotal, context.WorkContext.WorkingCurrency);
                cartSubtotal = money.RoundedAmount;

                var result = expression.Operator.Match(cartSubtotal, expression.Value);
                //$"unlocked {result}: {lockKey}".Dump();
                return(result);
            }
        }
예제 #5
0
        public T Get <T>(string key, Func <T> acquirer, TimeSpan?duration = null, bool independent = false)
        {
            if (TryGet(key, independent, out T value))
            {
                return(value);
            }

            if (_scopeAccessor.Value.HasScope(key))
            {
                throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key));
            }

            // Get the (semaphore) locker specific to this key
            using (KeyedLock.Lock("cache:" + key, TimeSpan.FromMinutes(1)))
            {
                // Atomic operation must be outer locked
                if (!TryGet(key, independent, out value))
                {
                    using (_scopeAccessor.Value.BeginScope(key))
                    {
                        value = acquirer();
                        Put(key, value, duration, _scopeAccessor.Value.Current.Dependencies);
                        return(value);
                    }
                }
            }

            return(value);
        }
예제 #6
0
        public virtual DbCacheEntry Put(string key, object value, IEnumerable <string> dependentEntitySets, TimeSpan?duration)
        {
            if (!Enabled || IsToxic(dependentEntitySets))
            {
                return(null);
            }

            key = HashKey(key);

            using (KeyedLock.Lock(key))
            {
                var entitySets = dependentEntitySets.Distinct().ToArray();
                var entry      = new DbCacheEntry
                {
                    Key         = key,
                    Value       = value,
                    EntitySets  = entitySets,
                    CachedOnUtc = DateTime.UtcNow,
                    Duration    = duration
                };

                _cache.Put(key, entry);

                foreach (var entitySet in entitySets)
                {
                    var lookup = GetLookupSet(entitySet);
                    lookup.Add(key);
                }

                return(entry);
            }
        }
예제 #7
0
        public CachedAssetEntry InsertAsset(string virtualPath, IEnumerable <string> virtualPathDependencies, string content, params string[] processorCodes)
        {
            if (!_isEnabled)
            {
                return(null);
            }

            Guard.NotEmpty(virtualPath, nameof(virtualPath));
            Guard.NotEmpty(content, nameof(content));

            var cacheDirectoryName = ResolveCacheDirectoryName(virtualPath, out string themeName, out int storeId);

            using (KeyedLock.Lock(BuildLockKey(cacheDirectoryName)))
            {
                CacheFolder.CreateDirectory(cacheDirectoryName);

                try
                {
                    // Save main content file
                    // TODO: determine correct file extension
                    CreateFileFromEntries(cacheDirectoryName, "asset.css", new[] { content });

                    // Save dependencies file
                    var deps = ResolveVirtualPathDependencies(virtualPath, virtualPathDependencies, themeName);
                    CreateFileFromEntries(cacheDirectoryName, "asset.dependencies", deps);

                    // Save hash file
                    var currentHash = BundleTable.VirtualPathProvider.GetFileHash(virtualPath, deps);
                    CreateFileFromEntries(cacheDirectoryName, "asset.hash", new[] { currentHash });

                    // Save codes file
                    CreateFileFromEntries(cacheDirectoryName, "asset.pcodes", processorCodes);

                    var entry = new CachedAssetEntry
                    {
                        Content                 = content,
                        HashCode                = currentHash,
                        OriginalVirtualPath     = virtualPath,
                        VirtualPathDependencies = deps,
                        PhysicalPath            = CacheFolder.MapPath(cacheDirectoryName),
                        ThemeName               = themeName,
                        StoreId                 = storeId,
                        ProcessorCodes          = processorCodes
                    };

                    SetupEvictionObserver(entry);

                    Logger.DebugFormat("Succesfully inserted asset '{0}' to cache.", virtualPath);

                    return(entry);
                }
                catch (Exception ex)
                {
                    Logger.ErrorFormat(ex, "Error while inserting asset '{0}' to the asset cache.", virtualPath);
                    InvalidateAssetInternal(cacheDirectoryName, themeName, storeId);
                }
            }

            return(null);
        }
예제 #8
0
        public T Get <T>(string key, Func <T> acquirer, TimeSpan?duration = null, bool independent = false, bool allowRecursion = false)
        {
            if (TryGet(key, independent, out T value))
            {
                return(value);
            }

            if (!allowRecursion && _scopeAccessor.Value.HasScope(key))
            {
                throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key));
            }

            // Get the (semaphore) locker specific to this key
            using (KeyedLock.Lock("cache:" + key, TimeSpan.FromSeconds(5)))
            {
                // Atomic operation must be outer locked
                if (!TryGet(key, independent, out value))
                {
                    var scope = !allowRecursion?_scopeAccessor.Value.BeginScope(key) : ActionDisposable.Empty;

                    using (scope)
                    {
                        value = acquirer();
                        var dependencies = !allowRecursion ? _scopeAccessor.Value.Current?.Dependencies : (IEnumerable <string>)null;
                        Put(key, value, duration, dependencies);
                        return(value);
                    }
                }
            }

            return(value);
        }
예제 #9
0
        public virtual void InvalidateItem(string key)
        {
            if (!Enabled)
            {
                return;
            }

            Guard.NotEmpty(key, nameof(key));

            using (KeyedLock.Lock(key))
            {
                InvalidateItemUnlocked(key);
            }
        }
예제 #10
0
        public bool InvalidateAsset(string virtualPath)
        {
            Guard.NotEmpty(virtualPath, nameof(virtualPath));

            var cacheDirectoryName = ResolveCacheDirectoryName(virtualPath, out string themeName, out int storeId);

            using (KeyedLock.Lock(BuildLockKey(cacheDirectoryName)))
            {
                if (CacheFolder.DirectoryExists(cacheDirectoryName))
                {
                    return(InvalidateAssetInternal(cacheDirectoryName, themeName, storeId));
                }

                return(false);
            }
        }
예제 #11
0
        public string Build(BundleType type, IEnumerable <string> files)
        {
            if (files == null || !files.Any())
            {
                return(string.Empty);
            }

            var bundleVirtualPath = this.GetBundleVirtualPath(type, files);
            var bundleFor         = BundleTable.Bundles.GetBundleFor(bundleVirtualPath);

            if (bundleFor == null)
            {
                using (KeyedLock.Lock("BundleBuilder.Build." + bundleVirtualPath))
                {
                    bundleFor = BundleTable.Bundles.GetBundleFor(bundleVirtualPath);
                    if (bundleFor == null)
                    {
                        var nullOrderer = new NullOrderer();

                        Bundle bundle = (type == BundleType.Script) ?
                                        new CustomScriptBundle(bundleVirtualPath) as Bundle :
                                        new SmartStyleBundle(bundleVirtualPath) as Bundle;
                        bundle.Orderer = nullOrderer;

                        bundle.Include(files.ToArray());

                        BundleTable.Bundles.Add(bundle);
                    }
                }
            }

            if (type == BundleType.Script)
            {
                return(Scripts.Render(bundleVirtualPath).ToString());

                //// Uncomment this if you want to bypass asset caching on mobile browsers
                //return Scripts.RenderFormat("<script src='{0}?" + CommonHelper.GenerateRandomDigitCode(5) + "'></script>",
                //	files.Select(x => VirtualPathUtility.ToAppRelative(x)).ToArray()).ToString();
            }

            return(Styles.Render(bundleVirtualPath).ToString());
        }
예제 #12
0
        public void LockOnDifferentKey2()
        {
            var kl = new KeyedLock <int>();
            var cd = new ConcurrentDictionary <int, int>();

            var inLock1 = false;
            var inLock2 = false;

            Parallel.ForEach(new int[] { 1, 2, 1, 2, 1, 2 }, (key) =>
            {
                TestContext.WriteLine($"{System.Threading.Thread.CurrentThread.ManagedThreadId} X");

                kl.Lock <int>(key, () =>
                {
                    if (key == 1)
                    {
                        Assert.IsFalse(inLock1);

                        inLock1 = true;
                        TestContext.WriteLine($"{System.Threading.Thread.CurrentThread.ManagedThreadId} XX");
                        System.Threading.Thread.Sleep(10);
                        TestContext.WriteLine($"{System.Threading.Thread.CurrentThread.ManagedThreadId} XXX");
                        inLock1 = false;
                    }
                    else
                    {
                        Assert.IsFalse(inLock2);

                        inLock2 = true;
                        TestContext.WriteLine($"{System.Threading.Thread.CurrentThread.ManagedThreadId} XX");
                        System.Threading.Thread.Sleep(10);
                        TestContext.WriteLine($"{System.Threading.Thread.CurrentThread.ManagedThreadId} XXX");
                        inLock2 = false;
                    }

                    return(key);
                });

                TestContext.WriteLine($"{System.Threading.Thread.CurrentThread.ManagedThreadId} XXXX");
            });
        }
예제 #13
0
        public CachedAssetEntry GetAsset(string virtualPath)
        {
            if (!_isEnabled)
            {
                return(null);
            }

            Guard.NotEmpty(virtualPath, nameof(virtualPath));

            var cacheDirectoryName = ResolveCacheDirectoryName(virtualPath, out string themeName, out int storeId);

            if (CacheFolder.DirectoryExists(cacheDirectoryName))
            {
                try
                {
                    var deps = CacheFolder.ReadFile(CacheFolder.Combine(cacheDirectoryName, "asset.dependencies"));
                    var hash = CacheFolder.ReadFile(CacheFolder.Combine(cacheDirectoryName, "asset.hash"));

                    if (!TryValidate(virtualPath, deps, hash, out IEnumerable <string> parsedDeps, out string currentHash))
                    {
                        Logger.DebugFormat("Invalidating cached asset for '{0}' because it is not valid anymore.", virtualPath);
                        InvalidateAssetInternal(cacheDirectoryName, themeName, storeId);
                        return(null);
                    }

                    // TODO: determine correct file extension
                    var content = CacheFolder.ReadFile(CacheFolder.Combine(cacheDirectoryName, "asset.css"));
                    if (content == null)
                    {
                        using (KeyedLock.Lock(BuildLockKey(cacheDirectoryName)))
                        {
                            InvalidateAssetInternal(cacheDirectoryName, themeName, storeId);
                            return(null);
                        }
                    }

                    var codes = ParseFileContent(CacheFolder.ReadFile(CacheFolder.Combine(cacheDirectoryName, "asset.pcodes")));

                    var entry = new CachedAssetEntry
                    {
                        Content                 = content,
                        HashCode                = currentHash,
                        OriginalVirtualPath     = virtualPath,
                        VirtualPathDependencies = parsedDeps,
                        PhysicalPath            = CacheFolder.MapPath(cacheDirectoryName),
                        ThemeName               = themeName,
                        StoreId                 = storeId,
                        ProcessorCodes          = codes.ToArray()
                    };

                    SetupEvictionObserver(entry);

                    Logger.DebugFormat("Succesfully read asset '{0}' from cache.", virtualPath);

                    return(entry);
                }
                catch (Exception ex)
                {
                    Logger.ErrorFormat(ex, "Error while resolving asset '{0}' from the asset cache.", virtualPath);
                }
            }

            return(null);
        }
예제 #14
0
        private async Task <ActionResult> HandleImageAsync(
            ProcessImageQuery query,
            CachedImageResult cachedImage,
            string nameWithoutExtension,
            string mime,
            string extension,
            Func <string, Task <byte[]> > getSourceBufferAsync)
        {
            string prevMime = null;

            if (extension != cachedImage.Extension)
            {
                // The query requests another format.
                // Adjust extension and mime type fo proper ETag creation.
                extension = cachedImage.Extension;
                prevMime  = mime;
                mime      = MimeTypes.MapNameToMimeType(cachedImage.FileName);
            }

            try
            {
                if (!cachedImage.Exists)
                {
                    // Lock concurrent requests to same resource
                    using (await KeyedLock.LockAsync("MediaController.HandleImage." + cachedImage.Path))
                    {
                        _imageCache.RefreshInfo(cachedImage);

                        // File could have been processed by another request in the meantime, check again.
                        if (!cachedImage.Exists)
                        {
                            // Call inner function
                            byte[] source = await getSourceBufferAsync(prevMime);

                            if (source == null || source.Length == 0)
                            {
                                return(NotFound(mime));
                            }

                            source = await ProcessAndPutToCacheAsync(cachedImage, source, query);

                            return(new CachedFileResult(mime, cachedImage.LastModifiedUtc.GetValueOrDefault(), () => source, source.LongLength));
                        }
                    }
                }

                if (Request.HttpMethod == "HEAD")
                {
                    return(new HttpStatusCodeResult(200));
                }

                if (cachedImage.IsRemote && !_streamRemoteMedia)
                {
                    // Redirect to existing remote file
                    Response.ContentType = mime;
                    return(Redirect(_imageCache.GetPublicUrl(cachedImage.Path)));
                }
                else
                {
                    // Open existing stream
                    return(new CachedFileResult(cachedImage.File, mime));
                }
            }
            catch (Exception ex)
            {
                if (!(ex is ProcessImageException))
                {
                    // ProcessImageException is logged already in ImageProcessor
                    Logger.ErrorFormat(ex, "Error processing media file '{0}'.", cachedImage.Path);
                }
                return(new HttpStatusCodeResult(500, ex.Message));
            }
        }
예제 #15
0
        public async Task ExecuteAsync(MediaHandlerContext context)
        {
            if (!IsProcessable(context))
            {
                return;
            }

            var query    = context.ImageQuery;
            var pathData = context.PathData;

            var cachedImage = ImageCache.Get(context.MediaFileId, pathData, query);

            if (!pathData.Extension.IsCaseInsensitiveEqual(cachedImage.Extension))
            {
                // The query requests another format.
                // Adjust extension and mime type fo proper ETag creation.
                pathData.Extension = cachedImage.Extension;
                pathData.MimeType  = cachedImage.MimeType;
            }

            var exists = cachedImage.Exists;

            if (exists && cachedImage.FileSize == 0)
            {
                // Empty file means: thumb extraction failed before and will most likely fail again.
                // Don't bother proceeding.
                context.Exception = ExceptionFactory.ExtractThumbnail(cachedImage.FileName);
                context.Executed  = true;
                return;
            }

            if (!exists)
            {
                // Lock concurrent requests to same resource
                using (await KeyedLock.LockAsync("ImageHandlerBase.Execute." + cachedImage.Path))
                {
                    ImageCache.RefreshInfo(cachedImage);

                    // File could have been processed by another request in the meantime, check again.
                    if (!cachedImage.Exists)
                    {
                        // Call inner function
                        var sourceFile = context.SourceFile;
                        if (sourceFile == null || sourceFile.Size == 0)
                        {
                            context.Executed = true;
                            return;
                        }

                        var inputStream = sourceFile.OpenRead();
                        if (inputStream == null)
                        {
                            context.Exception = ExceptionFactory.ExtractThumbnail(sourceFile.Path, T("Admin.Media.Exception.NullInputStream"));
                            context.Executed  = true;
                            return;
                        }

                        try
                        {
                            await ProcessImageAsync(context, cachedImage, inputStream);
                        }
                        catch (Exception ex)
                        {
                            Logger.Error(ex);

                            if (ex is ExtractThumbnailException)
                            {
                                // Thumbnail extraction failed and we must assume that it always will fail.
                                // Therefore we create an empty file to prevent repetitive processing.
                                using (var memStream = new MemoryStream())
                                {
                                    await ImageCache.PutAsync(cachedImage, memStream);
                                }
                            }

                            context.Exception = ex;
                            context.Executed  = true;
                            return;
                        }
                        finally
                        {
                            if (inputStream != null)
                            {
                                inputStream.Dispose();
                            }
                        }

                        if (context.ResultImage != null)
                        {
                            ImageCache.Put(cachedImage, context.ResultImage);
                            context.ResultFile = cachedImage.File;
                        }

                        context.Executed = true;
                        return;
                    }
                }
            }

            // Cached image existed already
            context.ResultFile = cachedImage.File;
            context.Executed   = true;
        }
예제 #16
0
 public KeyedLockTests()
 {
     _mutex = new KeyedLock();
 }
 internal Releaser(KeyedLock parent)
 {
     _parent = parent;
 }
예제 #18
0
        private IAsset TranslateInternal(IAsset asset)
        {
            IAsset result;
            var    chronometer = EngineContext.Current.Resolve <IChronometer>();

            using (chronometer.Step("Translate asset {0}".FormatInvariant(asset.VirtualPath)))
            {
                bool validationMode = ThemeHelper.IsStyleValidationRequest();

                if (validationMode || !TryGetCachedAsset(asset, out result))
                {
                    using (KeyedLock.Lock("CachedAsset:" + asset.VirtualPath))
                    {
                        if (validationMode || !TryGetCachedAsset(asset, out result))
                        {
                            using (chronometer.Step("Compile asset {0}".FormatInvariant(asset.VirtualPath)))
                            {
                                result = _inner.Translate(asset);

                                var cachedAsset = new CachedAsset
                                {
                                    AssetTypeCode           = AssetTypeCode.Css,
                                    IsStylesheet            = true,
                                    Minified                = result.Minified,
                                    Combined                = result.Combined,
                                    Content                 = result.Content,
                                    OriginalAssets          = asset.OriginalAssets,
                                    VirtualPath             = asset.VirtualPath,
                                    VirtualPathDependencies = result.VirtualPathDependencies,
                                    Url = asset.Url
                                };

                                result = AssetTranslorUtil.PostProcessAsset(cachedAsset, this.IsDebugMode);

                                if (!validationMode)
                                {
                                    var pCodes = new List <string>(3);
                                    if (cachedAsset.Minified)
                                    {
                                        pCodes.Add(DefaultAssetCache.MinificationCode);
                                    }
                                    if (cachedAsset.RelativePathsResolved)
                                    {
                                        pCodes.Add(DefaultAssetCache.UrlRewriteCode);
                                    }
                                    if (cachedAsset.Autoprefixed)
                                    {
                                        pCodes.Add(DefaultAssetCache.AutoprefixCode);
                                    }

                                    AssetCache.InsertAsset(
                                        cachedAsset.VirtualPath,
                                        cachedAsset.VirtualPathDependencies,
                                        cachedAsset.Content,
                                        pCodes.ToArray());
                                }
                            }
                        }
                    }
                }
            }

            return(result);
        }