public async Task LogAndApplyAsync(VersionEdit edit, AsyncLock.LockScope locker) { string newManifestFile = null; try { Version version; using (await locker.LockAsync()) { if (!edit.LogNumber.HasValue) edit.SetLogNumber(VersionSet.LogNumber); else if (edit.LogNumber < VersionSet.LogNumber || edit.LogNumber >= VersionSet.NextFileNumber) throw new InvalidOperationException("LogNumber"); if (!edit.PrevLogNumber.HasValue) edit.SetPrevLogNumber(VersionSet.PrevLogNumber); edit.SetNextFile(VersionSet.NextFileNumber); edit.SetLastSequence(VersionSet.LastSequence); version = new Version(this, VersionSet); var builder = new Builder(this, VersionSet, VersionSet.Current); builder.Apply(edit); builder.SaveTo(version); Version.Finalize(version); // Initialize new descriptor log file if necessary by creating // a temporary file that contains a snapshot of the current version. if (DescriptorLogWriter == null) { // No reason to unlock *mu here since we only hit this path in the // first call to LogAndApply (when opening the database). newManifestFile = FileSystem.DescriptorFileName(VersionSet.ManifestFileNumber); edit.SetNextFile(VersionSet.NextFileNumber); var descriptorFile = FileSystem.NewWritable(newManifestFile); DescriptorLogWriter = new LogWriter(FileSystem, descriptorFile, Options.BufferPool); Snapshooter.WriteSnapshot(DescriptorLogWriter, VersionSet); } } // Write new record to MANIFEST log edit.EncodeTo(DescriptorLogWriter); // If we just created a new descriptor file, install it by writing a // new CURRENT file that points to it. if (!string.IsNullOrEmpty(newManifestFile)) { SetCurrentFile(VersionSet.ManifestFileNumber); // No need to double-check MANIFEST in case of error since it // will be discarded below. } using (await locker.LockAsync()) { // Install the new version VersionSet.AppendVersion(version); VersionSet.SetLogNumber(edit.LogNumber.Value); VersionSet.SetPrevLogNumber(edit.PrevLogNumber.Value); } } catch (Exception) { if (!string.IsNullOrEmpty(newManifestFile)) { if (DescriptorLogWriter != null) { DescriptorLogWriter.Dispose(); DescriptorLogWriter = null; } FileSystem.DeleteFile(newManifestFile); } throw; } }
internal async Task MakeRoomForWriteAsync(bool force, AsyncLock.LockScope lockScope) { bool allowDelay = force == false; while (true) { using (await lockScope.LockAsync()) { if (BackgroundTask.IsCanceled || BackgroundTask.IsFaulted) { BackgroundTask.Wait(); // throws } else if (allowDelay && VersionSet.GetNumberOfFilesAtLevel(0) >= Config.SlowdownWritesTrigger) { // We are getting close to hitting a hard limit on the number of // L0 files. Rather than delaying a single write by several // seconds when we hit the hard limit, start delaying each // individual write by 1ms to reduce latency variance. Also, // this delay hands over some CPU to the compaction thread in // case it is sharing the same core as the writer. lockScope.Exit(); { await Task.Delay(TimeSpan.FromMilliseconds(1)).ConfigureAwait(false); } await lockScope.LockAsync().ConfigureAwait(false); allowDelay = false; // Do not delay a single write more than once } else if (force == false && MemTable.ApproximateMemoryUsage <= Options.WriteBatchSize) { // There is room in current memtable break; } else if (ImmutableMemTable != null) { Compactor.MaybeScheduleCompaction(lockScope); lockScope.Exit(); // We have filled up the current memtable, but the previous // one is still being compacted, so we wait. await BackgroundTask.ConfigureAwait(false); } else if (VersionSet.GetNumberOfFilesAtLevel(0) >= Config.StopWritesTrigger) { Compactor.MaybeScheduleCompaction(lockScope); lockScope.Exit(); // There are too many level-0 files. await BackgroundTask.ConfigureAwait(false); } else { // Attempt to switch to a new memtable and trigger compaction of old Debug.Assert(VersionSet.PrevLogNumber == 0); LogWriter.Dispose(); CreateNewLog(); ImmutableMemTable = MemTable; MemTable = new MemTable(this); force = false; Compactor.MaybeScheduleCompaction(lockScope); } } } }