/// <summary>
 /// Returns false when (a) the specified AsyncWrite value already exists, (b) the queue is full, or (c) the thread pool queue is full
 /// </summary>
 /// <param name="w"></param>
 /// <returns></returns>
 public bool Queue(AsyncWrite w,WriterDelegate writerDelegate )
 {
     lock (_sync) {
         if (GetQueuedBufferBytes() + w.GetBufferLength() > MaxQueueBytes) return false; //Because we would use too much ram.
         if (c.ContainsKey(HashTogether(w.RelativePath, w.ModifiedDateUtc))) return false; //We already have a queued write for this data.
         if (!ThreadPool.QueueUserWorkItem(delegate(object state){
             AsyncWrite job = state as AsyncWrite;
             writerDelegate(job);
         }, w)) return false; //thread pool refused
         return true;
     }
 }
Beispiel #2
0
        /// <summary>
        /// May return either a physical file name or a MemoryStream with the data. 
        /// Faster than GetCachedFile, as writes are (usually) asynchronous. If the write queue is full, the write is forced to be synchronous again.
        /// Identical to GetCachedFile() when asynchronous=false
        /// </summary>
        /// <param name="keyBasis"></param>
        /// <param name="extension"></param>
        /// <param name="writeCallback"></param>
        /// <param name="sourceModifiedUtc"></param>
        /// <param name="timeoutMs"></param>
        /// <returns></returns>
        public CacheResult GetCachedFile(string keyBasis, string extension, ResizeImageDelegate writeCallback, DateTime sourceModifiedUtc, int timeoutMs, bool asynchronous)
        {
            Stopwatch sw = null;
            if (lp.Logger != null) { sw = new Stopwatch(); sw.Start(); }

            bool hasModifiedDate = !sourceModifiedUtc.Equals(DateTime.MinValue);

            //Hash the modified date if needed.
            if (hashModifiedDate && hasModifiedDate)
                keyBasis += "|" + sourceModifiedUtc.Ticks.ToString(NumberFormatInfo.InvariantInfo);

            //Relative to the cache directory. Not relative to the app or domain root
            string relativePath = new UrlHasher().hash(keyBasis, subfolders, "/") + '.' + extension;

            //Physical path
            string physicalPath = PhysicalCachePath.TrimEnd('\\', '/') + System.IO.Path.DirectorySeparatorChar +
                    relativePath.Replace('/', System.IO.Path.DirectorySeparatorChar);

            CacheResult result = new CacheResult(CacheQueryResult.Hit, physicalPath, relativePath);

            bool compareModifiedDates = hasModifiedDate && !hashModifiedDate;

            bool asyncFailed = false;

             //On the first check, verify the file exists using System.IO directly (the last 'true' parameter).
            if (!asynchronous) {
                //On the first check, verify the file exists using System.IO directly (the last 'true' parameter)
                //May throw an IOException if the file cannot be opened, and is locked by an external processes for longer than timeoutMs.
                //This method may take longer than timeoutMs under absolute worst conditions.
                if (!TryWriteFile(result, physicalPath, relativePath, writeCallback, sourceModifiedUtc, timeoutMs, true)) {
                    //On failure
                    result.Result = CacheQueryResult.Failed;
                }
            } else if (!compareModifiedDates ? !Index.existsCertain(relativePath, physicalPath) : !Index.modifiedDateMatchesCertainExists(sourceModifiedUtc, relativePath, physicalPath)) {

                //Looks like a miss. Let's enter a lock for the creation of the file. This is a different locking system than for writing to the file - far less contention, as it doesn't include the
                //This prevents two identical requests from duplicating efforts. Different requests don't lock.

                //Lock execution using relativePath as the sync basis. Ignore casing differences. This prevents duplicate entries in the write queue and wasted CPU/RAM usage.
                if (!QueueLocks.TryExecute(relativePath.ToUpperInvariant(), timeoutMs,
                    delegate() {

                        //Now, if the item we seek is in the queue, we have a memcached hit. If not, we should check the index. It's possible the item has been written to disk already.
                        //If both are a miss, we should see if there is enough room in the write queue. If not, switch to in-thread writing.

                        AsyncWrite t = CurrentWrites.Get(relativePath, sourceModifiedUtc);

                        if (t != null) result.Data = t.GetReadonlyStream();

                        //On the second check, use cached data for speed. The cached data should be updated if another thread updated a file (but not if another process did).
                        if (t == null &&
                            (!compareModifiedDates ? !Index.exists(relativePath, physicalPath) : !Index.modifiedDateMatches(sourceModifiedUtc, relativePath, physicalPath))) {

                                result.Result = CacheQueryResult.Miss;
                            //Still a miss, we even rechecked the filesystem. Write to memory.
                            MemoryStream ms = new MemoryStream(4096);  //4K initial capacity is minimal, but this array will get copied around alot, better to underestimate.
                            //Read, resize, process, and encode the image. Lots of exceptions thrown here.
                            writeCallback(ms);
                            ms.Position = 0;

                            AsyncWrite w = new AsyncWrite(CurrentWrites,ms, physicalPath, relativePath, sourceModifiedUtc);
                            if (CurrentWrites.Queue(w, delegate(AsyncWrite job) {
                                try {
                                    Stopwatch swio = new Stopwatch();

                                    swio.Start();
                                    //TODO: perhaps a different timeout?
                                    if (!TryWriteFile(null, job.PhysicalPath, job.RelativePath, delegate(Stream s) { ((MemoryStream)job.GetReadonlyStream()).WriteTo(s); }, job.ModifiedDateUtc, timeoutMs, true)) {
                                        swio.Stop();
                                        //We failed to lock the file.
                                        if (lp.Logger != null)
                                            lp.Logger.Warn("Failed to flush async write, timeout exceeded after {1}ms - {0}",  result.RelativePath, swio.ElapsedMilliseconds);

                                    } else {
                                        swio.Stop();
                                        if (lp.Logger != null)
                                            lp.Logger.Trace("{0}ms: Async write started {1}ms after enqueue for {2}", swio.ElapsedMilliseconds.ToString().PadLeft(4), DateTime.UtcNow.Subtract(w.JobCreatedAt).Subtract(swio.Elapsed).TotalMilliseconds, result.RelativePath);
                                    }

                                } catch (Exception ex) {
                                    if (lp.Logger != null) {
                                        lp.Logger.Error("Failed to flush async write, {0} {1}\n{2}",ex.ToString(), result.RelativePath,ex.StackTrace);
                                    }
                                } finally {
                                    CurrentWrites.Remove(job); //Remove from the queue, it's done or failed.
                                }

                            })) {
                                //We queued it! Send back a read-only memory stream
                                result.Data = w.GetReadonlyStream();
                            } else {
                                asyncFailed = false;
                                //We failed to queue it - either the ThreadPool was exhausted or we exceeded the MB limit for the write queue.
                                //Write the MemoryStream to disk using the normal method.
                                //This is nested inside a queuelock because if we failed here, the next one will also. Better to force it to wait until the file is written to disk.
                                if (!TryWriteFile(result, physicalPath, relativePath, delegate(Stream s) { ms.WriteTo(s); }, sourceModifiedUtc, timeoutMs, false)) {
                                    if (lp.Logger != null)
                                        lp.Logger.Warn("Failed to queue async write, also failed to lock for sync writing: {0}", result.RelativePath);

                                }
                            }

                        }

                    })) {
                    //On failure
                    result.Result = CacheQueryResult.Failed;
                }

            }
            if (lp.Logger != null) {
                sw.Stop();
                lp.Logger.Trace("{0}ms: {3}{1} for {2}, Key: {4}", sw.ElapsedMilliseconds.ToString(NumberFormatInfo.InvariantInfo).PadLeft(4), result.Result.ToString(), result.RelativePath, asynchronous ? (asyncFailed ? "Fallback to sync  " : "Async ") : "", keyBasis);
            }
            //Fire event
            if (CacheResultReturned != null) CacheResultReturned(this, result);
            return result;
        }
 /// <summary>
 /// Removes the specified object based on its relativepath and modifieddateutc values.
 /// </summary>
 /// <param name="w"></param>
 public void Remove(AsyncWrite w)
 {
     lock (_sync) {
         c.Remove(HashTogether(w.RelativePath, w.ModifiedDateUtc));
     }
 }