PageRenderTime 101ms CodeModel.GetById 79ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 1ms

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