| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,5 @@ | |||
| 1 | 1 | # Specify version format | |
| 2 | - version: "5.0.14.{build}" | ||
| 2 | + version: "5.0.15.{build}" | ||
| 3 | 3 | ||
| 4 | 4 | # Image to use | |
| 5 | 5 | image: Visual Studio 2019 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,7 @@ | |||
| 2 | 2 | using System.Collections.Generic; | |
| 3 | 3 | using System.IO; | |
| 4 | 4 | using System.Text; | |
| 5 | + using System.Threading; | ||
| 5 | 6 | using System.Web; | |
| 6 | 7 | ||
| 7 | 8 | namespace NetCoreServer | |
@@ -21,11 +22,12 @@ public class FileCache : IDisposable | |||
| 21 | 22 | /// <summary> | |
| 22 | 23 | /// Is the file cache empty? | |
| 23 | 24 | /// </summary> | |
| 24 | - public bool Empty { get { lock (_lock) return _entriesByKey.Count == 0; } } | ||
| 25 | + public bool Empty => _entriesByKey.Count == 0; | ||
| 26 | + | ||
| 25 | 27 | /// <summary> | |
| 26 | 28 | /// Get the file cache size | |
| 27 | 29 | /// </summary> | |
| 28 | - public int Size { get { lock (_lock) return _entriesByKey.Count; } } | ||
| 30 | + public int Size => _entriesByKey.Count; | ||
| 29 | 31 | ||
| 30 | 32 | /// <summary> | |
| 31 | 33 | /// Add a new cache value with the given timeout into the file cache | |
@@ -36,7 +38,7 @@ public class FileCache : IDisposable | |||
| 36 | 38 | /// <returns>'true' if the cache value was added, 'false' if the given key was not added</returns> | |
| 37 | 39 | public bool Add(string key, byte[] value, TimeSpan timeout = new TimeSpan()) | |
| 38 | 40 | { | |
| 39 | - lock (_lock) | ||
| 41 | + using (new WriteLock(_lockEx)) | ||
| 40 | 42 | { | |
| 41 | 43 | // Try to find and remove the previous key | |
| 42 | 44 | _entriesByKey.Remove(key); | |
@@ -48,14 +50,14 @@ public class FileCache : IDisposable | |||
| 48 | 50 | } | |
| 49 | 51 | } | |
| 50 | 52 | ||
| 51 | - /// <summary> | ||
| 53 | + /// <summary> | ||
| 52 | 54 | /// Try to find the cache value by the given key | |
| 53 | 55 | /// </summary> | |
| 54 | 56 | /// <param name="key">Key to find</param> | |
| 55 | 57 | /// <returns>'true' and cache value if the cache value was found, 'false' if the given key was not found</returns> | |
| 56 | 58 | public (bool, byte[]) Find(string key) | |
| 57 | 59 | { | |
| 58 | - lock (_lock) | ||
| 60 | + using (new ReadLock(_lockEx)) | ||
| 59 | 61 | { | |
| 60 | 62 | // Try to find the given key | |
| 61 | 63 | if (!_entriesByKey.TryGetValue(key, out var cacheValue)) | |
@@ -72,7 +74,7 @@ public class FileCache : IDisposable | |||
| 72 | 74 | /// <returns>'true' if the cache value was removed, 'false' if the given key was not found</returns> | |
| 73 | 75 | public bool Remove(string key) | |
| 74 | 76 | { | |
| 75 | - lock (_lock) | ||
| 77 | + using (new WriteLock(_lockEx)) | ||
| 76 | 78 | { | |
| 77 | 79 | return _entriesByKey.Remove(key); | |
| 78 | 80 | } | |
@@ -98,16 +100,18 @@ public bool Remove(string key) | |||
| 98 | 100 | // Try to find and remove the previous path | |
| 99 | 101 | RemovePathInternal(path); | |
| 100 | 102 | ||
| 101 | - // Insert the cache path | ||
| 102 | - if (!InsertPathInternal(path, prefix, timeout, handler)) | ||
| 103 | - return false; | ||
| 104 | - | ||
| 105 | - lock (_lock) | ||
| 103 | + using (new WriteLock(_lockEx)) | ||
| 106 | 104 | { | |
| 107 | 105 | // Add the given path to the cache | |
| 108 | 106 | _pathsByKey.Add(path, new FileCacheEntry(this, prefix, path, filter, handler, timeout)); | |
| 107 | + // Create entries by path map | ||
| 108 | + _entriesByPath[path] = new HashSet<string>(); | ||
| 109 | 109 | } | |
| 110 | 110 | ||
| 111 | + // Insert the cache path | ||
| 112 | + if (!InsertPathInternal(path, path, prefix, timeout, handler)) | ||
| 113 | + return false; | ||
| 114 | + | ||
| 111 | 115 | return true; | |
| 112 | 116 | } | |
| 113 | 117 | ||
@@ -118,7 +122,7 @@ public bool Remove(string key) | |||
| 118 | 122 | /// <returns>'true' if the cache path was found, 'false' if the given path was not found</returns> | |
| 119 | 123 | public bool FindPath(string path) | |
| 120 | 124 | { | |
| 121 | - lock (_lock) | ||
| 125 | + using (new ReadLock(_lockEx)) | ||
| 122 | 126 | { | |
| 123 | 127 | // Try to find the given key | |
| 124 | 128 | return _pathsByKey.ContainsKey(path); | |
@@ -140,14 +144,15 @@ public bool RemovePath(string path) | |||
| 140 | 144 | /// </summary> | |
| 141 | 145 | public void Clear() | |
| 142 | 146 | { | |
| 143 | - lock (_lock) | ||
| 147 | + using (new WriteLock(_lockEx)) | ||
| 144 | 148 | { | |
| 145 | 149 | // Stop all file system watchers | |
| 146 | 150 | foreach (var fileCacheEntry in _pathsByKey) | |
| 147 | 151 | fileCacheEntry.Value.StopWatcher(); | |
| 148 | 152 | ||
| 149 | 153 | // Clear all cache entries | |
| 150 | 154 | _entriesByKey.Clear(); | |
| 155 | + _entriesByPath.Clear(); | ||
| 151 | 156 | _pathsByKey.Clear(); | |
| 152 | 157 | } | |
| 153 | 158 | } | |
@@ -156,7 +161,10 @@ public void Clear() | |||
| 156 | 161 | ||
| 157 | 162 | #region Cache implementation | |
| 158 | 163 | ||
| 159 | - private readonly object _lock = new object(); | ||
| 164 | + private readonly ReaderWriterLockSlim _lockEx = new ReaderWriterLockSlim(); | ||
| 165 | + private Dictionary<string, MemCacheEntry> _entriesByKey = new Dictionary<string, MemCacheEntry>(); | ||
| 166 | + private Dictionary<string, HashSet<string>> _entriesByPath = new Dictionary<string, HashSet<string>>(); | ||
| 167 | + private Dictionary<string, FileCacheEntry> _pathsByKey = new Dictionary<string, FileCacheEntry>(); | ||
| 160 | 168 | ||
| 161 | 169 | private class MemCacheEntry | |
| 162 | 170 | { | |
@@ -244,7 +252,7 @@ private static void OnCreated(object sender, FileSystemEventArgs e, FileCache ca | |||
| 244 | 252 | if (IsDirectory(file)) | |
| 245 | 253 | return; | |
| 246 | 254 | ||
| 247 | - cache.InsertFileInternal(file, key, entry._timespan, entry._handler); | ||
| 255 | + cache.InsertFileInternal(entry._path, file, key, entry._timespan, entry._handler); | ||
| 248 | 256 | } | |
| 249 | 257 | ||
| 250 | 258 | private static void OnChanged(object sender, FileSystemEventArgs e, FileCache cache, FileCacheEntry entry) | |
@@ -262,22 +270,15 @@ private static void OnChanged(object sender, FileSystemEventArgs e, FileCache ca | |||
| 262 | 270 | if (IsDirectory(file)) | |
| 263 | 271 | return; | |
| 264 | 272 | ||
| 265 | - cache.InsertFileInternal(file, key, entry._timespan, entry._handler); | ||
| 273 | + cache.InsertFileInternal(entry._path, file, key, entry._timespan, entry._handler); | ||
| 266 | 274 | } | |
| 267 | 275 | ||
| 268 | 276 | private static void OnDeleted(object sender, FileSystemEventArgs e, FileCache cache, FileCacheEntry entry) | |
| 269 | 277 | { | |
| 270 | 278 | var key = e.FullPath.Replace(entry._path, entry._prefix); | |
| 271 | 279 | var file = e.FullPath; | |
| 272 | 280 | ||
| 273 | - // Skip missing files | ||
| 274 | - if (!File.Exists(file)) | ||
| 275 | - return; | ||
| 276 | - // Skip directory updates | ||
| 277 | - if (IsDirectory(file)) | ||
| 278 | - return; | ||
| 279 | - | ||
| 280 | - cache.RemoveFileInternal(key); | ||
| 281 | + cache.RemoveFileInternal(entry._path, key); | ||
| 281 | 282 | } | |
| 282 | 283 | ||
| 283 | 284 | private static void OnRenamed(object sender, RenamedEventArgs e, FileCache cache, FileCacheEntry entry) | |
@@ -291,18 +292,15 @@ private static void OnRenamed(object sender, RenamedEventArgs e, FileCache cache | |||
| 291 | 292 | if (!File.Exists(newFile)) | |
| 292 | 293 | return; | |
| 293 | 294 | // Skip directory updates | |
| 294 | - if (IsDirectory(oldFile) || IsDirectory(newFile)) | ||
| 295 | + if (IsDirectory(newFile)) | ||
| 295 | 296 | return; | |
| 296 | 297 | ||
| 297 | - cache.RemoveFileInternal(oldKey); | ||
| 298 | - cache.InsertFileInternal(newFile, newKey, entry._timespan, entry._handler); | ||
| 298 | + cache.RemoveFileInternal(entry._path, oldKey); | ||
| 299 | + cache.InsertFileInternal(entry._path, newFile, newKey, entry._timespan, entry._handler); | ||
| 299 | 300 | } | |
| 300 | 301 | }; | |
| 301 | 302 | ||
| 302 | - private Dictionary<string, MemCacheEntry> _entriesByKey = new Dictionary<string, MemCacheEntry>(); | ||
| 303 | - private Dictionary<string, FileCacheEntry> _pathsByKey = new Dictionary<string, FileCacheEntry>(); | ||
| 304 | - | ||
| 305 | - private bool InsertFileInternal(string file, string key, TimeSpan timeout, InsertHandler handler) | ||
| 303 | + private bool InsertFileInternal(string path, string file, string key, TimeSpan timeout, InsertHandler handler) | ||
| 306 | 304 | { | |
| 307 | 305 | try | |
| 308 | 306 | { | |
@@ -314,23 +312,35 @@ private bool InsertFileInternal(string file, string key, TimeSpan timeout, Inser | |||
| 314 | 312 | if (!handler(this, key, content, timeout)) | |
| 315 | 313 | return false; | |
| 316 | 314 | ||
| 315 | + using (new WriteLock(_lockEx)) | ||
| 316 | + { | ||
| 317 | + // Update entries by path map | ||
| 318 | + _entriesByPath[path].Add(key); | ||
| 319 | + } | ||
| 320 | + | ||
| 317 | 321 | return true; | |
| 318 | 322 | } | |
| 319 | 323 | catch (Exception) { return false; } | |
| 320 | 324 | } | |
| 321 | 325 | ||
| 322 | - private bool RemoveFileInternal(string key) | ||
| 326 | + private bool RemoveFileInternal(string path, string key) | ||
| 323 | 327 | { | |
| 324 | 328 | try | |
| 325 | 329 | { | |
| 326 | 330 | key = key.Replace('\\', '/'); | |
| 327 | 331 | ||
| 332 | + using (new WriteLock(_lockEx)) | ||
| 333 | + { | ||
| 334 | + // Update entries by path map | ||
| 335 | + _entriesByPath[path].Remove(key); | ||
| 336 | + } | ||
| 337 | + | ||
| 328 | 338 | return Remove(key); | |
| 329 | 339 | } | |
| 330 | 340 | catch (Exception) { return false; } | |
| 331 | 341 | } | |
| 332 | 342 | ||
| 333 | - private bool InsertPathInternal(string path, string prefix, TimeSpan timeout, InsertHandler handler) | ||
| 343 | + private bool InsertPathInternal(string root, string path, string prefix, TimeSpan timeout, InsertHandler handler) | ||
| 334 | 344 | { | |
| 335 | 345 | try | |
| 336 | 346 | { | |
@@ -342,7 +352,7 @@ private bool InsertPathInternal(string path, string prefix, TimeSpan timeout, In | |||
| 342 | 352 | string key = keyPrefix + HttpUtility.UrlDecode(Path.GetFileName(item)); | |
| 343 | 353 | ||
| 344 | 354 | // Recursively insert sub-directory | |
| 345 | - if (!InsertPathInternal(item, key, timeout, handler)) | ||
| 355 | + if (!InsertPathInternal(root, item, key, timeout, handler)) | ||
| 346 | 356 | return false; | |
| 347 | 357 | } | |
| 348 | 358 | ||
@@ -351,7 +361,7 @@ private bool InsertPathInternal(string path, string prefix, TimeSpan timeout, In | |||
| 351 | 361 | string key = keyPrefix + HttpUtility.UrlDecode(Path.GetFileName(item)); | |
| 352 | 362 | ||
| 353 | 363 | // Insert file into the cache | |
| 354 | - if (!InsertFileInternal(item, key, timeout, handler)) | ||
| 364 | + if (!InsertFileInternal(root, item, key, timeout, handler)) | ||
| 355 | 365 | return false; | |
| 356 | 366 | } | |
| 357 | 367 | ||
@@ -362,7 +372,7 @@ private bool InsertPathInternal(string path, string prefix, TimeSpan timeout, In | |||
| 362 | 372 | ||
| 363 | 373 | private bool RemovePathInternal(string path) | |
| 364 | 374 | { | |
| 365 | - lock (_lock) | ||
| 375 | + using (new WriteLock(_lockEx)) | ||
| 366 | 376 | { | |
| 367 | 377 | // Try to find the given path | |
| 368 | 378 | if (!_pathsByKey.TryGetValue(path, out var cacheValue)) | |
@@ -371,7 +381,12 @@ private bool RemovePathInternal(string path) | |||
| 371 | 381 | // Stop the file system watcher | |
| 372 | 382 | cacheValue.StopWatcher(); | |
| 373 | 383 | ||
| 374 | - // Erase cache path | ||
| 384 | + // Remove path entries | ||
| 385 | + foreach (var entryKey in _entriesByPath[path]) | ||
| 386 | + _entriesByKey.Remove(entryKey); | ||
| 387 | + _entriesByPath.Remove(path); | ||
| 388 | + | ||
| 389 | + // Remove cache path | ||
| 375 | 390 | _pathsByKey.Remove(path); | |
| 376 | 391 | ||
| 377 | 392 | return true; | |
@@ -394,16 +409,16 @@ public void Dispose() | |||
| 394 | 409 | ||
| 395 | 410 | protected virtual void Dispose(bool disposingManagedResources) | |
| 396 | 411 | { | |
| 397 | - // The idea here is that Dispose(Boolean) knows whether it is | ||
| 398 | - // being called to do explicit cleanup (the Boolean is true) | ||
| 399 | - // versus being called due to a garbage collection (the Boolean | ||
| 400 | - // is false). This distinction is useful because, when being | ||
| 401 | - // disposed explicitly, the Dispose(Boolean) method can safely | ||
| 402 | - // execute code using reference type fields that refer to other | ||
| 403 | - // objects knowing for sure that these other objects have not been | ||
| 404 | - // finalized or disposed of yet. When the Boolean is false, | ||
| 405 | - // the Dispose(Boolean) method should not execute code that | ||
| 406 | - // refer to reference type fields because those objects may | ||
| 412 | + // The idea here is that Dispose(Boolean) knows whether it is | ||
| 413 | + // being called to do explicit cleanup (the Boolean is true) | ||
| 414 | + // versus being called due to a garbage collection (the Boolean | ||
| 415 | + // is false). This distinction is useful because, when being | ||
| 416 | + // disposed explicitly, the Dispose(Boolean) method can safely | ||
| 417 | + // execute code using reference type fields that refer to other | ||
| 418 | + // objects knowing for sure that these other objects have not been | ||
| 419 | + // finalized or disposed of yet. When the Boolean is false, | ||
| 420 | + // the Dispose(Boolean) method should not execute code that | ||
| 421 | + // refer to reference type fields because those objects may | ||
| 407 | 422 | // have already been finalized." | |
| 408 | 423 | ||
| 409 | 424 | if (!_disposed) | |
@@ -432,4 +447,44 @@ protected virtual void Dispose(bool disposingManagedResources) | |||
| 432 | 447 | ||
| 433 | 448 | #endregion | |
| 434 | 449 | } | |
| 450 | + | ||
| 451 | + /// <summary> | ||
| 452 | + /// Disposable lock class performs exit action on dispose operation. | ||
| 453 | + /// </summary> | ||
| 454 | + public class DisposableLock : IDisposable | ||
| 455 | + { | ||
| 456 | + private readonly Action _exitLock; | ||
| 457 | + | ||
| 458 | + public DisposableLock(Action exitLock) | ||
| 459 | + { | ||
| 460 | + _exitLock = exitLock; | ||
| 461 | + } | ||
| 462 | + | ||
| 463 | + public void Dispose() | ||
| 464 | + { | ||
| 465 | + _exitLock(); | ||
| 466 | + } | ||
| 467 | + } | ||
| 468 | + | ||
| 469 | + /// <summary> | ||
| 470 | + /// Read lock class enters read lock on construction and performs exit read lock on dispose. | ||
| 471 | + /// </summary> | ||
| 472 | + public class ReadLock : DisposableLock | ||
| 473 | + { | ||
| 474 | + public ReadLock(ReaderWriterLockSlim locker) : base(locker.ExitReadLock) | ||
| 475 | + { | ||
| 476 | + locker.EnterReadLock(); | ||
| 477 | + } | ||
| 478 | + } | ||
| 479 | + | ||
| 480 | + /// <summary> | ||
| 481 | + /// Write lock class enters write lock on construction and performs exit write lock on dispose. | ||
| 482 | + /// </summary> | ||
| 483 | + public class WriteLock : DisposableLock | ||
| 484 | + { | ||
| 485 | + public WriteLock(ReaderWriterLockSlim locker) : base(locker.ExitWriteLock) | ||
| 486 | + { | ||
| 487 | + locker.EnterWriteLock(); | ||
| 488 | + } | ||
| 489 | + } | ||
| 435 | 490 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 3 | <PropertyGroup> | |
| 4 | 4 | <TargetFramework>net5.0</TargetFramework> | |
| 5 | - <Version>5.0.14.0</Version> | ||
| 5 | + <Version>5.0.15.0</Version> | ||
| 6 | 6 | <Authors>Ivan Shynkarenka</Authors> | |
| 7 | 7 | <Copyright>Copyright (c) 2019-2021 Ivan Shynkarenka</Copyright> | |
| 8 | 8 | <RepositoryUrl>https://github.com/chronoxor/NetCoreServer</RepositoryUrl> | |
| Back | FazBrowse Home | New Git URL |
0 commit comments