PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/Raven.Database/Storage/Voron/TransactionalStorage.cs

https://github.com/nwendel/ravendb
C# | 393 lines | 318 code | 74 blank | 1 comment | 38 complexity | f38fd5ccab900862302e3f3d2b292444 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.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Threading;
  7. using Raven.Abstractions.Data;
  8. using Raven.Abstractions.Exceptions;
  9. using Raven.Abstractions.Extensions;
  10. using Raven.Abstractions.Logging;
  11. using Raven.Abstractions.MEF;
  12. using Raven.Abstractions.Util.Streams;
  13. using Raven.Database;
  14. using Raven.Database.Config;
  15. using Raven.Database.Impl;
  16. using Raven.Database.Impl.DTC;
  17. using Raven.Database.Plugins;
  18. using Raven.Database.Storage;
  19. using Raven.Database.Storage.Voron;
  20. using Raven.Database.Storage.Voron.Backup;
  21. using Raven.Database.Storage.Voron.Impl;
  22. using Raven.Database.Storage.Voron.Schema;
  23. using Raven.Json.Linq;
  24. using Voron;
  25. using Voron.Impl;
  26. using VoronExceptions = Voron.Exceptions;
  27. using Task = System.Threading.Tasks.Task;
  28. namespace Raven.Storage.Voron
  29. {
  30. public class TransactionalStorage : ITransactionalStorage
  31. {
  32. private static readonly ILog Log = LogManager.GetCurrentClassLogger();
  33. private readonly ThreadLocal<IStorageActionsAccessor> current = new ThreadLocal<IStorageActionsAccessor>();
  34. private readonly ThreadLocal<object> disableBatchNesting = new ThreadLocal<object>();
  35. private volatile bool disposed;
  36. private readonly DisposableAction exitLockDisposable;
  37. private readonly ReaderWriterLockSlim disposerLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
  38. private OrderedPartCollection<AbstractDocumentCodec> _documentCodecs;
  39. private IDocumentCacher documentCacher;
  40. private IUuidGenerator uuidGenerator;
  41. private readonly InMemoryRavenConfiguration configuration;
  42. private readonly Action onCommit;
  43. private TableStorage tableStorage;
  44. private readonly IBufferPool bufferPool;
  45. public TransactionalStorage(InMemoryRavenConfiguration configuration, Action onCommit)
  46. {
  47. this.configuration = configuration;
  48. this.onCommit = onCommit;
  49. documentCacher = new DocumentCacher(configuration);
  50. exitLockDisposable = new DisposableAction(() => Monitor.Exit(this));
  51. bufferPool = new BufferPool(configuration.Storage.Voron.MaxBufferPoolSize * 1024 * 1024 * 1024, int.MaxValue); // 2GB max buffer size (voron limit)
  52. }
  53. public void Dispose()
  54. {
  55. disposerLock.EnterWriteLock();
  56. try
  57. {
  58. if (disposed)
  59. return;
  60. disposed = true;
  61. var exceptionAggregator = new ExceptionAggregator("Could not properly dispose TransactionalStorage");
  62. exceptionAggregator.Execute(() => current.Dispose());
  63. if (tableStorage != null)
  64. exceptionAggregator.Execute(() => tableStorage.Dispose());
  65. if (bufferPool != null)
  66. exceptionAggregator.Execute(() => bufferPool.Dispose());
  67. exceptionAggregator.ThrowIfNeeded();
  68. }
  69. finally
  70. {
  71. disposerLock.ExitWriteLock();
  72. }
  73. }
  74. public Guid Id { get; private set; }
  75. public IDisposable WriteLock()
  76. {
  77. Monitor.Enter(this);
  78. return exitLockDisposable;
  79. }
  80. public IDisposable DisableBatchNesting()
  81. {
  82. disableBatchNesting.Value = new object();
  83. return new DisposableAction(() => disableBatchNesting.Value = null);
  84. }
  85. public IStorageActionsAccessor CreateAccessor()
  86. {
  87. var snapshotReference = new Reference<SnapshotReader> { Value = tableStorage.CreateSnapshot() };
  88. var writeBatchReference = new Reference<WriteBatch> { Value = new WriteBatch() };
  89. var accessor = new StorageActionsAccessor(uuidGenerator, _documentCodecs,
  90. documentCacher, writeBatchReference, snapshotReference, tableStorage, this, bufferPool);
  91. accessor.OnDispose += () =>
  92. {
  93. var exceptionAggregator = new ExceptionAggregator("Could not properly dispose StorageActionsAccessor");
  94. exceptionAggregator.Execute(() => snapshotReference.Value.Dispose());
  95. exceptionAggregator.Execute(() => writeBatchReference.Value.Dispose());
  96. exceptionAggregator.ThrowIfNeeded();
  97. };
  98. return accessor;
  99. }
  100. public void Batch(Action<IStorageActionsAccessor> action)
  101. {
  102. if (disposerLock.IsReadLockHeld && disableBatchNesting.Value == null) // we are currently in a nested Batch call and allow to nest batches
  103. {
  104. if (current.Value != null) // check again, just to be sure
  105. {
  106. current.Value.IsNested = true;
  107. action(current.Value);
  108. current.Value.IsNested = false;
  109. return;
  110. }
  111. }
  112. disposerLock.EnterReadLock();
  113. try
  114. {
  115. if (disposed)
  116. {
  117. Trace.WriteLine("TransactionalStorage.Batch was called after it was disposed, call was ignored.");
  118. return; // this may happen if someone is calling us from the finalizer thread, so we can't even throw on that
  119. }
  120. ExecuteBatch(action);
  121. }
  122. catch (Exception e)
  123. {
  124. if (disposed)
  125. {
  126. Trace.WriteLine("TransactionalStorage.Batch was called after it was disposed, call was ignored.");
  127. return; // this may happen if someone is calling us from the finalizer thread, so we can't even throw on that
  128. }
  129. if (e.InnerException is VoronExceptions.ConcurrencyException)
  130. throw new ConcurrencyException("Concurrent modification to the same document are not allowed", e.InnerException);
  131. throw;
  132. }
  133. finally
  134. {
  135. disposerLock.ExitReadLock();
  136. if (disposed == false && disableBatchNesting.Value == null)
  137. current.Value = null;
  138. }
  139. onCommit(); // call user code after we exit the lock
  140. }
  141. private IStorageActionsAccessor ExecuteBatch(Action<IStorageActionsAccessor> action)
  142. {
  143. var snapshotRef = new Reference<SnapshotReader>();
  144. var writeBatchRef = new Reference<WriteBatch>();
  145. try
  146. {
  147. snapshotRef.Value = tableStorage.CreateSnapshot();
  148. writeBatchRef.Value = new WriteBatch { DisposeAfterWrite = false }; // prevent from disposing after write to allow read from batch OnStorageCommit
  149. var storageActionsAccessor = new StorageActionsAccessor(uuidGenerator, _documentCodecs,
  150. documentCacher, writeBatchRef, snapshotRef,
  151. tableStorage, this, bufferPool);
  152. if (disableBatchNesting.Value == null)
  153. current.Value = storageActionsAccessor;
  154. action(storageActionsAccessor);
  155. storageActionsAccessor.SaveAllTasks();
  156. tableStorage.Write(writeBatchRef.Value);
  157. storageActionsAccessor.ExecuteOnStorageCommit();
  158. return storageActionsAccessor;
  159. }
  160. finally
  161. {
  162. if (snapshotRef.Value != null)
  163. snapshotRef.Value.Dispose();
  164. if (writeBatchRef.Value != null)
  165. writeBatchRef.Value.Dispose();
  166. }
  167. }
  168. public void ExecuteImmediatelyOrRegisterForSynchronization(Action action)
  169. {
  170. if (current.Value == null)
  171. {
  172. action();
  173. return;
  174. }
  175. current.Value.OnStorageCommit += action;
  176. }
  177. public void Initialize(IUuidGenerator generator, OrderedPartCollection<AbstractDocumentCodec> documentCodecs)
  178. {
  179. if (generator == null) throw new ArgumentNullException("generator");
  180. if (documentCodecs == null) throw new ArgumentNullException("documentCodecs");
  181. uuidGenerator = generator;
  182. _documentCodecs = documentCodecs;
  183. StorageEnvironmentOptions options = configuration.RunInMemory ?
  184. CreateMemoryStorageOptionsFromConfiguration(configuration) :
  185. CreateStorageOptionsFromConfiguration(configuration);
  186. tableStorage = new TableStorage(options, bufferPool);
  187. var schemaCreator = new SchemaCreator(configuration, tableStorage, Output, Log);
  188. schemaCreator.CreateSchema();
  189. schemaCreator.SetupDatabaseIdAndSchemaVersion();
  190. schemaCreator.UpdateSchemaIfNecessary();
  191. SetupDatabaseId();
  192. }
  193. private void SetupDatabaseId()
  194. {
  195. Id = tableStorage.Id;
  196. }
  197. private static StorageEnvironmentOptions CreateMemoryStorageOptionsFromConfiguration(InMemoryRavenConfiguration configuration)
  198. {
  199. var options = StorageEnvironmentOptions.CreateMemoryOnly();
  200. options.InitialFileSize = configuration.Storage.Voron.InitialFileSize;
  201. return options;
  202. }
  203. private static StorageEnvironmentOptions CreateStorageOptionsFromConfiguration(InMemoryRavenConfiguration configuration)
  204. {
  205. bool allowIncrementalBackupsSetting;
  206. if (bool.TryParse(configuration.Settings["Raven/Voron/AllowIncrementalBackups"] ?? "false", out allowIncrementalBackupsSetting) == false)
  207. throw new ArgumentException("Raven/Voron/AllowIncrementalBackups settings key contains invalid value");
  208. var directoryPath = configuration.DataDirectory ?? AppDomain.CurrentDomain.BaseDirectory;
  209. var filePathFolder = new DirectoryInfo(directoryPath);
  210. if (filePathFolder.Exists == false)
  211. filePathFolder.Create();
  212. var tempPath = configuration.Settings["Raven/Voron/TempPath"];
  213. var journalPath = configuration.Settings[Abstractions.Data.Constants.RavenTxJournalPath] ?? configuration.JournalsStoragePath;
  214. var options = StorageEnvironmentOptions.ForPath(directoryPath, tempPath, journalPath);
  215. options.IncrementalBackupEnabled = allowIncrementalBackupsSetting;
  216. options.InitialFileSize = configuration.Storage.Voron.InitialFileSize;
  217. return options;
  218. }
  219. public void StartBackupOperation(DocumentDatabase database, string backupDestinationDirectory, bool incrementalBackup,
  220. DatabaseDocument documentDatabase)
  221. {
  222. if (tableStorage == null)
  223. throw new InvalidOperationException("Cannot begin database backup - table store is not initialized");
  224. var backupOperation = new BackupOperation(database, database.Configuration.DataDirectory,
  225. backupDestinationDirectory, tableStorage.Environment, incrementalBackup,documentDatabase);
  226. Task.Factory.StartNew(() =>
  227. {
  228. using(backupOperation)
  229. backupOperation.Execute();
  230. });
  231. }
  232. public void Restore(RestoreRequest restoreRequest, Action<string> output)
  233. {
  234. new RestoreOperation(restoreRequest, configuration, output).Execute();
  235. }
  236. public DatabaseSizeInformation GetDatabaseSize()
  237. {
  238. var stats = tableStorage.Environment.Stats();
  239. return new DatabaseSizeInformation
  240. {
  241. AllocatedSizeInBytes = stats.AllocatedDataFileSizeInBytes,
  242. UsedSizeInBytes = stats.UsedDataFileSizeInBytes
  243. };
  244. }
  245. public long GetDatabaseCacheSizeInBytes()
  246. {
  247. return -1;
  248. }
  249. public long GetDatabaseTransactionVersionSizeInBytes()
  250. {
  251. return -1;
  252. }
  253. public string FriendlyName
  254. {
  255. get { return "Voron"; }
  256. }
  257. public bool HandleException(Exception exception)
  258. {
  259. return false; //false returned --> all exceptions (if any) are properly rethrown in DocumentDatabase
  260. }
  261. public bool IsAlreadyInBatch
  262. {
  263. get
  264. {
  265. return current.Value != null;
  266. }
  267. }
  268. public bool SupportsDtc { get { return false; } }
  269. public void Compact(InMemoryRavenConfiguration configuration)
  270. {
  271. //Voron storage does not support compaction
  272. }
  273. public Guid ChangeId()
  274. {
  275. var newId = Guid.NewGuid();
  276. using (var changeIdWriteBatch = new WriteBatch())
  277. {
  278. tableStorage.Details.Delete(changeIdWriteBatch, "id");
  279. tableStorage.Details.Add(changeIdWriteBatch, "id", newId.ToByteArray());
  280. tableStorage.Write(changeIdWriteBatch);
  281. }
  282. Id = newId;
  283. return newId;
  284. }
  285. public void ClearCaches()
  286. {
  287. var oldDocumentCacher = documentCacher;
  288. documentCacher = new DocumentCacher(configuration);
  289. oldDocumentCacher.Dispose();
  290. }
  291. public void DumpAllStorageTables()
  292. {
  293. throw new NotSupportedException("Not valid for Voron storage");
  294. }
  295. public InFlightTransactionalState GetInFlightTransactionalState(DocumentDatabase self, Func<string, Etag, RavenJObject, RavenJObject, TransactionInformation, PutResult> put, Func<string, Etag, TransactionInformation, bool> delete)
  296. {
  297. return new DtcNotSupportedTransactionalState(FriendlyName, put, delete);
  298. }
  299. public IList<string> ComputeDetailedStorageInformation()
  300. {
  301. return tableStorage.GenerateReportOnStorage()
  302. .Select(kvp => String.Format("{0} -> {1}", kvp.Key, kvp.Value))
  303. .ToList();
  304. }
  305. internal IStorageActionsAccessor GetCurrentBatch()
  306. {
  307. var batch = current.Value;
  308. if (batch == null)
  309. throw new InvalidOperationException("Batch was not started, you are not supposed to call this method");
  310. return batch;
  311. }
  312. private void Output(string message)
  313. {
  314. Log.Info(message);
  315. Console.Write(message);
  316. Console.WriteLine();
  317. }
  318. }
  319. }