ImageResizer  3.4.0
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Events
Public Member Functions | Protected Attributes | Properties | Events | List of all members
ImageResizer.Plugins.DiskCache.CustomDiskCache Class Reference

Handles access to a disk-based file cache. Handles locking and versioning. Supports subfolders for scalability. More...

Collaboration diagram for ImageResizer.Plugins.DiskCache.CustomDiskCache:
Collaboration graph
[legend]

Public Member Functions

 CustomDiskCache (ILoggerProvider lp, string physicalCachePath, int subfolders, bool hashModifiedDate)
 
 CustomDiskCache (ILoggerProvider lp, string physicalCachePath, int subfolders, bool hashModifiedDate, long asyncMaxQueuedBytes)
 
CacheResult GetCachedFile (string keyBasis, string extension, ResizeImageDelegate writeCallback, DateTime sourceModifiedUtc, int timeoutMs)
 If the cached data exists and is up-to-date, returns the path to it. Otherwise, this function tries to cache the data and return the path. More...
 
CacheResult GetCachedFile (string keyBasis, string extension, ResizeImageDelegate writeCallback, DateTime sourceModifiedUtc, int timeoutMs, bool asynchronous)
 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 More...
 

Protected Attributes

string physicalCachePath
 
int subfolders
 
bool hashModifiedDate
 
ILoggerProvider lp
 
LockProvider locks = new LockProvider()
 
LockProvider queueLocks = new LockProvider()
 

Properties

string PhysicalCachePath [get]
 
LockProvider Locks [get]
 Provides string-based locking for file write access. More...
 
LockProvider QueueLocks [get]
 Provides string-based locking for image resizing (not writing, just processing). Prevents duplication of efforts in asynchronous mode, where 'Locks' is not being used. More...
 
AsyncWriteCollection CurrentWrites [get]
 Contains all the queued and in-progress writes to the cache. More...
 
CacheIndex Index [get]
 Provides an in-memory index of the cache. More...
 

Events

CacheResultHandler CacheResultReturned
 Fired immediately before GetCachedFile return the result value. More...
 

Detailed Description

Handles access to a disk-based file cache. Handles locking and versioning. Supports subfolders for scalability.

Definition at line 20 of file CustomDiskCache.cs.

Member Function Documentation

CacheResult ImageResizer.Plugins.DiskCache.CustomDiskCache.GetCachedFile ( string  keyBasis,
string  extension,
ResizeImageDelegate  writeCallback,
DateTime  sourceModifiedUtc,
int  timeoutMs 
)
inline

If the cached data exists and is up-to-date, returns the path to it. Otherwise, this function tries to cache the data and return the path.

Parameters
keyBasisThe basis for the key. Should not include the modified date, that is handled inside the function.
extensionThe extension to use for the cached file.
writeCallbackA method that accepts a Stream argument and writes the data to it.
sourceModifiedUtcThe modified date of the source file. Should be DateTime.MinValue if not available
timeoutMs
Returns

Definition at line 90 of file CustomDiskCache.cs.

90  {
91  return GetCachedFile(keyBasis, extension, writeCallback, sourceModifiedUtc, timeoutMs, false);
92  }
CacheResult GetCachedFile(string keyBasis, string extension, ResizeImageDelegate writeCallback, DateTime sourceModifiedUtc, int timeoutMs)
If the cached data exists and is up-to-date, returns the path to it. Otherwise, this function tries t...
CacheResult ImageResizer.Plugins.DiskCache.CustomDiskCache.GetCachedFile ( string  keyBasis,
string  extension,
ResizeImageDelegate  writeCallback,
DateTime  sourceModifiedUtc,
int  timeoutMs,
bool  asynchronous 
)
inline

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

Parameters
keyBasis
extension
writeCallback
sourceModifiedUtc
timeoutMs
Returns

Definition at line 106 of file CustomDiskCache.cs.

106  {
107  Stopwatch sw = null;
108  if (lp.Logger != null) { sw = new Stopwatch(); sw.Start(); }
109 
110 
111  bool hasModifiedDate = !sourceModifiedUtc.Equals(DateTime.MinValue);
112 
113 
114  //Hash the modified date if needed.
115  if (hashModifiedDate && hasModifiedDate)
116  keyBasis += "|" + sourceModifiedUtc.Ticks.ToString(NumberFormatInfo.InvariantInfo);
117 
118  //Relative to the cache directory. Not relative to the app or domain root
119  string relativePath = new UrlHasher().hash(keyBasis, subfolders, "/") + '.' + extension;
120 
121  //Physical path
122  string physicalPath = PhysicalCachePath.TrimEnd('\\', '/') + System.IO.Path.DirectorySeparatorChar +
123  relativePath.Replace('/', System.IO.Path.DirectorySeparatorChar);
124 
125 
126  CacheResult result = new CacheResult(CacheQueryResult.Hit, physicalPath, relativePath);
127 
128 
129  bool compareModifiedDates = hasModifiedDate && !hashModifiedDate;
130 
131  bool asyncFailed = false;
132 
133 
134  //2013-apr-25: What happens if the file is still being written to disk - it's present but not complete? To handle that, we use mayBeLocked.
135 
136  bool mayBeLocked = Locks.MayBeLocked(relativePath.ToUpperInvariant());
137 
138  //On the first check, verify the file exists using System.IO directly (the last 'true' parameter).
139  if (!asynchronous) {
140  //On the first check, verify the file exists using System.IO directly (the last 'true' parameter)
141  //May throw an IOException if the file cannot be opened, and is locked by an external processes for longer than timeoutMs.
142  //This method may take longer than timeoutMs under absolute worst conditions.
143  if (!TryWriteFile(result, physicalPath, relativePath, writeCallback, sourceModifiedUtc, timeoutMs, !mayBeLocked)) {
144  //On failure
145  result.Result = CacheQueryResult.Failed;
146  }
147  }
148  else if ((!compareModifiedDates ? !Index.existsCertain(relativePath, physicalPath) : !Index.modifiedDateMatchesCertainExists(sourceModifiedUtc, relativePath, physicalPath)) || mayBeLocked)
149  {
150 
151  //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
152  //This prevents two identical requests from duplicating efforts. Different requests don't lock.
153 
154  //Lock execution using relativePath as the sync basis. Ignore casing differences. This prevents duplicate entries in the write queue and wasted CPU/RAM usage.
155  if (!QueueLocks.TryExecute(relativePath.ToUpperInvariant(), timeoutMs,
156  delegate() {
157 
158  //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.
159  //If both are a miss, we should see if there is enough room in the write queue. If not, switch to in-thread writing.
160 
161  AsyncWrite t = CurrentWrites.Get(relativePath, sourceModifiedUtc);
162 
163  if (t != null) result.Data = t.GetReadonlyStream();
164 
165 
166 
167  //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).
168  //When t == null, and we're inside QueueLocks, all work on the file must be finished, so we have no need to consult mayBeLocked.
169  if (t == null &&
170  (!compareModifiedDates ? !Index.exists(relativePath, physicalPath) : !Index.modifiedDateMatches(sourceModifiedUtc, relativePath, physicalPath)))
171  {
172 
173  result.Result = CacheQueryResult.Miss;
174  //Still a miss, we even rechecked the filesystem. Write to memory.
175  MemoryStream ms = new MemoryStream(4096); //4K initial capacity is minimal, but this array will get copied around alot, better to underestimate.
176  //Read, resize, process, and encode the image. Lots of exceptions thrown here.
177  writeCallback(ms);
178  ms.Position = 0;
179 
180  AsyncWrite w = new AsyncWrite(CurrentWrites,ms, physicalPath, relativePath, sourceModifiedUtc);
181  if (CurrentWrites.Queue(w, delegate(AsyncWrite job) {
182  try {
183  Stopwatch swio = new Stopwatch();
184 
185  swio.Start();
186  //TODO: perhaps a different timeout?
187  if (!TryWriteFile(null, job.PhysicalPath, job.RelativePath, delegate(Stream s) { ((MemoryStream)job.GetReadonlyStream()).WriteTo(s); }, job.ModifiedDateUtc, timeoutMs, true)) {
188  swio.Stop();
189  //We failed to lock the file.
190  if (lp.Logger != null)
191  lp.Logger.Warn("Failed to flush async write, timeout exceeded after {1}ms - {0}", result.RelativePath, swio.ElapsedMilliseconds);
192 
193  } else {
194  swio.Stop();
195  if (lp.Logger != null)
196  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);
197  }
198 
199  } catch (Exception ex) {
200  if (lp.Logger != null) {
201  lp.Logger.Error("Failed to flush async write, {0} {1}\n{2}",ex.ToString(), result.RelativePath,ex.StackTrace);
202  }
203  } finally {
204  CurrentWrites.Remove(job); //Remove from the queue, it's done or failed.
205  }
206 
207  })) {
208  //We queued it! Send back a read-only memory stream
209  result.Data = w.GetReadonlyStream();
210  } else {
211  asyncFailed = false;
212  //We failed to queue it - either the ThreadPool was exhausted or we exceeded the MB limit for the write queue.
213  //Write the MemoryStream to disk using the normal method.
214  //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.
215  if (!TryWriteFile(result, physicalPath, relativePath, delegate(Stream s) { ms.WriteTo(s); }, sourceModifiedUtc, timeoutMs, false)) {
216  if (lp.Logger != null)
217  lp.Logger.Warn("Failed to queue async write, also failed to lock for sync writing: {0}", result.RelativePath);
218 
219  }
220  }
221 
222  }
223 
224  })) {
225  //On failure
226  result.Result = CacheQueryResult.Failed;
227  }
228 
229  }
230  if (lp.Logger != null) {
231  sw.Stop();
232  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);
233  }
234  //Fire event
235  if (CacheResultReturned != null) CacheResultReturned(this, result);
236  return result;
237  }
LockProvider QueueLocks
Provides string-based locking for image resizing (not writing, just processing). Prevents duplication...
bool TryExecute(string key, int timeoutMs, LockCallback success)
Attempts to execute the 'success' callback inside a lock based on 'key'. If successful, returns true. If the lock cannot be acquired within 'timoutMs', returns false In a worst-case scenario, it could take up to twice as long as 'timeoutMs' to return false.
Definition: LockProvider.cs:53
MemoryStream GetReadonlyStream()
Wraps the data in a readonly MemoryStream so it can be accessed on another thread ...
Definition: AsyncWrite.cs:63
AsyncWriteCollection CurrentWrites
Contains all the queued and in-progress writes to the cache.
CacheIndex Index
Provides an in-memory index of the cache.
bool Queue(AsyncWrite w, WriterDelegate writerDelegate)
Returns false when (a) the specified AsyncWrite value already exists, (b) the queue is full...
CacheResultHandler CacheResultReturned
Fired immediately before GetCachedFile return the result value.

Property Documentation

AsyncWriteCollection ImageResizer.Plugins.DiskCache.CustomDiskCache.CurrentWrites
get

Contains all the queued and in-progress writes to the cache.

Definition at line 65 of file CustomDiskCache.cs.

CacheIndex ImageResizer.Plugins.DiskCache.CustomDiskCache.Index
get

Provides an in-memory index of the cache.

Definition at line 75 of file CustomDiskCache.cs.

LockProvider ImageResizer.Plugins.DiskCache.CustomDiskCache.Locks
get

Provides string-based locking for file write access.

Definition at line 49 of file CustomDiskCache.cs.

LockProvider ImageResizer.Plugins.DiskCache.CustomDiskCache.QueueLocks
get

Provides string-based locking for image resizing (not writing, just processing). Prevents duplication of efforts in asynchronous mode, where 'Locks' is not being used.

Definition at line 57 of file CustomDiskCache.cs.

Event Documentation

CacheResultHandler ImageResizer.Plugins.DiskCache.CustomDiskCache.CacheResultReturned

Fired immediately before GetCachedFile return the result value.

Definition at line 42 of file CustomDiskCache.cs.


The documentation for this class was generated from the following file: