PageRenderTime 47ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Raven.Database/Server/RavenFS/Infrastructure/StorageOperationsTask.cs

https://github.com/nwendel/ravendb
C# | 366 lines | 292 code | 69 blank | 5 comment | 37 complexity | 3fa4b6a6574c00bf81f480d4dcce952f MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.Diagnostics;
  6. using System.Linq;
  7. using System.Reactive.Linq;
  8. using System.Threading.Tasks;
  9. using Microsoft.Isam.Esent.Interop;
  10. using NLog;
  11. using Raven.Abstractions.Extensions;
  12. using Raven.Database.Server.RavenFS.Extensions;
  13. using Raven.Database.Server.RavenFS.Notifications;
  14. using Raven.Database.Server.RavenFS.Search;
  15. using Raven.Database.Server.RavenFS.Storage;
  16. using Raven.Database.Server.RavenFS.Storage.Esent;
  17. using Raven.Database.Server.RavenFS.Storage.Exceptions;
  18. using Raven.Database.Server.RavenFS.Synchronization;
  19. using Raven.Database.Server.RavenFS.Util;
  20. using Raven.Json.Linq;
  21. using Raven.Abstractions.FileSystem.Notifications;
  22. using Raven.Abstractions.FileSystem;
  23. namespace Raven.Database.Server.RavenFS.Infrastructure
  24. {
  25. public class StorageOperationsTask
  26. {
  27. private static readonly Logger Log = LogManager.GetCurrentClassLogger();
  28. private readonly ConcurrentDictionary<string, Task> deleteFileTasks = new ConcurrentDictionary<string, Task>();
  29. private readonly FileLockManager fileLockManager = new FileLockManager();
  30. private readonly INotificationPublisher notificationPublisher;
  31. private readonly ConcurrentDictionary<string, Task> renameFileTasks = new ConcurrentDictionary<string, Task>();
  32. private readonly IndexStorage search;
  33. private readonly ITransactionalStorage storage;
  34. private readonly IObservable<long> timer = Observable.Interval(TimeSpan.FromMinutes(15));
  35. private readonly ConcurrentDictionary<string, FileHeader> uploadingFiles = new ConcurrentDictionary<string, FileHeader>();
  36. public StorageOperationsTask(ITransactionalStorage storage, IndexStorage search, INotificationPublisher notificationPublisher)
  37. {
  38. this.storage = storage;
  39. this.search = search;
  40. this.notificationPublisher = notificationPublisher;
  41. InitializeTimer();
  42. }
  43. private void InitializeTimer()
  44. {
  45. timer.Subscribe(tick =>
  46. {
  47. ResumeFileRenamingAsync();
  48. CleanupDeletedFilesAsync();
  49. });
  50. }
  51. public void RenameFile(RenameFileOperation operation)
  52. {
  53. var configName = RavenFileNameHelper.RenameOperationConfigNameForFile(operation.Name);
  54. notificationPublisher.Publish(new FileChangeNotification
  55. {
  56. File = FilePathTools.Cannoicalise(operation.Name),
  57. Action = FileChangeAction.Renaming
  58. });
  59. storage.Batch(accessor =>
  60. {
  61. var previousRenameTombstone = accessor.ReadFile(operation.Rename);
  62. if (previousRenameTombstone != null &&
  63. previousRenameTombstone.Metadata[SynchronizationConstants.RavenDeleteMarker] != null)
  64. {
  65. // if there is a tombstone delete it
  66. accessor.Delete(previousRenameTombstone.Name);
  67. }
  68. accessor.RenameFile(operation.Name, operation.Rename, true);
  69. accessor.UpdateFileMetadata(operation.Rename, operation.MetadataAfterOperation);
  70. // copy renaming file metadata and set special markers
  71. var tombstoneMetadata = new RavenJObject(operation.MetadataAfterOperation).WithRenameMarkers(operation.Rename);
  72. accessor.PutFile(operation.Name, 0, tombstoneMetadata, true); // put rename tombstone
  73. accessor.DeleteConfig(configName);
  74. search.Delete(operation.Name);
  75. search.Index(operation.Rename, operation.MetadataAfterOperation);
  76. });
  77. notificationPublisher.Publish(new ConfigurationChangeNotification { Name = configName, Action = ConfigurationChangeAction.Set });
  78. notificationPublisher.Publish(new FileChangeNotification
  79. {
  80. File = FilePathTools.Cannoicalise(operation.Rename),
  81. Action = FileChangeAction.Renamed
  82. });
  83. }
  84. public void IndicateFileToDelete(string fileName)
  85. {
  86. var deletingFileName = RavenFileNameHelper.DeletingFileName(fileName);
  87. var fileExists = true;
  88. storage.Batch(accessor =>
  89. {
  90. var existingFileHeader = accessor.ReadFile(fileName);
  91. if (existingFileHeader == null)
  92. {
  93. // do nothing if file does not exist
  94. fileExists = false;
  95. return;
  96. }
  97. if (existingFileHeader.Metadata[SynchronizationConstants.RavenDeleteMarker] != null)
  98. {
  99. // if it is a tombstone drop it
  100. accessor.Delete(fileName);
  101. fileExists = false;
  102. return;
  103. }
  104. var metadata = new RavenJObject(existingFileHeader.Metadata).WithDeleteMarker();
  105. var renameSucceeded = false;
  106. int deleteVersion = 0;
  107. do
  108. {
  109. try
  110. {
  111. accessor.RenameFile(fileName, deletingFileName);
  112. renameSucceeded = true;
  113. }
  114. catch (FileExistsException) // it means that .deleting file was already existed
  115. {
  116. var deletingFileHeader = accessor.ReadFile(deletingFileName);
  117. if (deletingFileHeader != null && deletingFileHeader.Equals(existingFileHeader))
  118. {
  119. fileExists = false; // the same file already marked as deleted no need to do it again
  120. return;
  121. }
  122. // we need to use different name to do a file rename
  123. deleteVersion++;
  124. deletingFileName = RavenFileNameHelper.DeletingFileName(fileName, deleteVersion);
  125. }
  126. } while (!renameSucceeded && deleteVersion < 128);
  127. if (renameSucceeded)
  128. {
  129. accessor.UpdateFileMetadata(deletingFileName, metadata);
  130. accessor.DecrementFileCount(deletingFileName);
  131. Log.Debug(string.Format("File '{0}' was renamed to '{1}' and marked as deleted", fileName, deletingFileName));
  132. var configName = RavenFileNameHelper.DeleteOperationConfigNameForFile(deletingFileName);
  133. var operation = new DeleteFileOperation { OriginalFileName = fileName, CurrentFileName = deletingFileName };
  134. accessor.SetConfig(configName, JsonExtensions.ToJObject(operation));
  135. notificationPublisher.Publish(new ConfigurationChangeNotification { Name = configName, Action = ConfigurationChangeAction.Set });
  136. }
  137. else
  138. {
  139. Log.Warn("Could not rename a file '{0}' when a delete operation was performed", fileName);
  140. }
  141. });
  142. if (fileExists)
  143. {
  144. search.Delete(fileName);
  145. search.Delete(deletingFileName);
  146. }
  147. }
  148. public Task CleanupDeletedFilesAsync()
  149. {
  150. var filesToDelete = new List<DeleteFileOperation>();
  151. storage.Batch(accessor => filesToDelete = accessor.GetConfigsStartWithPrefix(RavenFileNameHelper.DeleteOperationConfigPrefix, 0, 10)
  152. .Select(config => config.JsonDeserialization<DeleteFileOperation>())
  153. .ToList());
  154. if(filesToDelete.Count == 0)
  155. return Task.FromResult<object>(null);
  156. var tasks = new List<Task>();
  157. foreach (var fileToDelete in filesToDelete)
  158. {
  159. var deletingFileName = fileToDelete.CurrentFileName;
  160. if (IsDeleteInProgress(deletingFileName))
  161. continue;
  162. if (IsUploadInProgress(fileToDelete.OriginalFileName))
  163. continue;
  164. if (IsSynchronizationInProgress(fileToDelete.OriginalFileName))
  165. continue;
  166. if (fileToDelete.OriginalFileName.EndsWith(RavenFileNameHelper.DownloadingFileSuffix)) // if it's .downloading file
  167. {
  168. if (IsSynchronizationInProgress(SynchronizedFileName(fileToDelete.OriginalFileName))) // and file is being synced
  169. continue;
  170. }
  171. Log.Debug("Starting to delete file '{0}' from storage", deletingFileName);
  172. var deleteTask = Task.Run(() =>
  173. {
  174. try
  175. {
  176. ConcurrencyAwareExecutor.Execute(() => storage.Batch(accessor => accessor.Delete(deletingFileName)), retries: 1);
  177. }
  178. catch (Exception e)
  179. {
  180. Log.WarnException(string.Format("Could not delete file '{0}' from storage", deletingFileName), e);
  181. return;
  182. }
  183. var configName = RavenFileNameHelper.DeleteOperationConfigNameForFile(deletingFileName);
  184. storage.Batch(accessor => accessor.DeleteConfig(configName));
  185. notificationPublisher.Publish(new ConfigurationChangeNotification
  186. {
  187. Name = configName,
  188. Action = ConfigurationChangeAction.Delete
  189. });
  190. Log.Debug("File '{0}' was deleted from storage", deletingFileName);
  191. });
  192. deleteFileTasks.AddOrUpdate(deletingFileName, deleteTask, (file, oldTask) => deleteTask);
  193. tasks.Add(deleteTask);
  194. }
  195. return Task.WhenAll(tasks);
  196. }
  197. public Task ResumeFileRenamingAsync()
  198. {
  199. var filesToRename = new List<RenameFileOperation>();
  200. storage.Batch(accessor =>
  201. {
  202. var renameOpConfigs = accessor.GetConfigsStartWithPrefix(RavenFileNameHelper.RenameOperationConfigPrefix, 0, 10);
  203. filesToRename = renameOpConfigs.Select(config => config.JsonDeserialization<RenameFileOperation>()).ToList();
  204. });
  205. if (filesToRename.Count == 0)
  206. return Task.FromResult<object>(null);
  207. var tasks = new List<Task>();
  208. foreach (var item in filesToRename)
  209. {
  210. var renameOperation = item;
  211. if (IsRenameInProgress(renameOperation.Name))
  212. continue;
  213. Log.Debug("Starting to resume a rename operation of a file '{0}' to '{1}'", renameOperation.Name,
  214. renameOperation.Rename);
  215. var renameTask = Task.Run(() =>
  216. {
  217. try
  218. {
  219. ConcurrencyAwareExecutor.Execute(() => RenameFile(renameOperation), retries: 1);
  220. Log.Debug("File '{0}' was renamed to '{1}'", renameOperation.Name, renameOperation.Rename);
  221. }
  222. catch (Exception e)
  223. {
  224. Log.WarnException(
  225. string.Format("Could not rename file '{0}' to '{1}'", renameOperation.Name, renameOperation.Rename), e);
  226. throw;
  227. }
  228. });
  229. renameFileTasks.AddOrUpdate(renameOperation.Name, renameTask, (file, oldTask) => renameTask);
  230. tasks.Add(renameTask);
  231. }
  232. return Task.WhenAll(tasks);
  233. }
  234. private static string SynchronizedFileName(string originalFileName)
  235. {
  236. return originalFileName.Substring(0,
  237. originalFileName.IndexOf(RavenFileNameHelper.DownloadingFileSuffix,
  238. StringComparison.InvariantCulture));
  239. }
  240. private bool IsSynchronizationInProgress(string originalFileName)
  241. {
  242. if (!fileLockManager.TimeoutExceeded(originalFileName, storage))
  243. return true;
  244. return false;
  245. }
  246. private bool IsUploadInProgress(string originalFileName)
  247. {
  248. FileHeader deletedFile = null;
  249. storage.Batch(accessor => deletedFile = accessor.ReadFile(originalFileName));
  250. if (deletedFile != null) // if there exists a file already marked as deleted
  251. {
  252. if (deletedFile.IsFileBeingUploadedOrUploadHasBeenBroken()) // and might be uploading at the moment
  253. {
  254. if (!uploadingFiles.ContainsKey(deletedFile.Name))
  255. {
  256. uploadingFiles.TryAdd(deletedFile.Name, deletedFile);
  257. return true; // first attempt to delete a file, prevent this time
  258. }
  259. var uploadingFile = uploadingFiles[deletedFile.Name];
  260. if (uploadingFile != null && uploadingFile.UploadedSize != deletedFile.UploadedSize)
  261. {
  262. return true; // if uploaded size changed it means that file is being uploading
  263. }
  264. FileHeader header;
  265. uploadingFiles.TryRemove(deletedFile.Name, out header);
  266. }
  267. }
  268. return false;
  269. }
  270. private bool IsDeleteInProgress(string deletingFileName)
  271. {
  272. Task existingTask;
  273. if (deleteFileTasks.TryGetValue(deletingFileName, out existingTask))
  274. {
  275. if (!existingTask.IsCompleted)
  276. {
  277. return true;
  278. }
  279. deleteFileTasks.TryRemove(deletingFileName, out existingTask);
  280. }
  281. return false;
  282. }
  283. private bool IsRenameInProgress(string fileName)
  284. {
  285. Task existingTask;
  286. if (renameFileTasks.TryGetValue(fileName, out existingTask))
  287. {
  288. if (!existingTask.IsCompleted)
  289. {
  290. return true;
  291. }
  292. renameFileTasks.TryRemove(fileName, out existingTask);
  293. }
  294. return false;
  295. }
  296. }
  297. }