From 34e8a790c75e4d2da8c020d187022e5f3266b3a0 Mon Sep 17 00:00:00 2001 From: mbdavid Date: Sun, 2 Jul 2017 20:16:49 -0300 Subject: [PATCH] Support exclusive lock under shared lock --- LiteDB/Engine/Disks/FileDiskService.cs | 31 +++++++++++++++++++-- LiteDB/Engine/Engine/Find.cs | 13 ++------- LiteDB/Engine/Services/LockService.cs | 25 ++++++++++++++--- LiteDB/Utils/Extensions/StreamExtensions.cs | 4 +++ README.md | 11 ++++++++ 5 files changed, 67 insertions(+), 17 deletions(-) diff --git a/LiteDB/Engine/Disks/FileDiskService.cs b/LiteDB/Engine/Disks/FileDiskService.cs index a1afbe672..8632b91d1 100644 --- a/LiteDB/Engine/Disks/FileDiskService.cs +++ b/LiteDB/Engine/Disks/FileDiskService.cs @@ -251,8 +251,19 @@ public void Lock(LockState state, TimeSpan timeout) var length = state == LockState.Shared ? 1 : LOCK_SHARED_LENGTH; _log.Write(Logger.LOCK, "locking file in {0} mode (position: {1}, length: {2})", state.ToString().ToLower(), position, length); - - _stream.TryLock(position, length, timeout); + + if (state == LockState.Exclusive && _lockSharedPosition > 0) + { + var beforeLength = _lockSharedPosition - LOCK_POSITION; + var afterLength = LOCK_SHARED_LENGTH - beforeLength - 1; + + _stream.TryLock(LOCK_POSITION, beforeLength, timeout); + _stream.TryLock(_lockSharedPosition + 1, afterLength, timeout); + } + else + { + _stream.TryLock(position, length, timeout); + } #endif } @@ -270,7 +281,21 @@ public void Unlock(LockState state) _log.Write(Logger.LOCK, "unlocking file in {0} mode (position: {1}, length: {2})", state.ToString().ToLower(), position, length); - _stream.TryUnlock(position, length); + // if unlock exclusive but contains position of shared lock, keep shared + if (state == LockState.Exclusive && _lockSharedPosition != 0) + { + var beforeLength = _lockSharedPosition - LOCK_POSITION; + var afterLength = LOCK_SHARED_LENGTH - beforeLength - 1; + + _stream.TryUnlock(LOCK_POSITION, beforeLength); + _stream.TryUnlock(_lockSharedPosition + 1, afterLength); + } + else + { + _stream.TryUnlock(position, length); + + _lockSharedPosition = 0; + } #endif } diff --git a/LiteDB/Engine/Engine/Find.cs b/LiteDB/Engine/Engine/Find.cs index 6498ba6d3..cc0508d5c 100644 --- a/LiteDB/Engine/Engine/Find.cs +++ b/LiteDB/Engine/Engine/Find.cs @@ -14,8 +14,6 @@ public IEnumerable Find(string collection, Query query, int skip = if (collection.IsNullOrWhiteSpace()) throw new ArgumentNullException("collection"); if (query == null) throw new ArgumentNullException("query"); - var docs = new List(bufferSize); - using (_locker.Shared()) { // get my collection page @@ -48,15 +46,10 @@ public IEnumerable Find(string collection, Query query, int skip = buffer = _data.Read(node.DataBlock); doc = BsonSerializer.Deserialize(buffer).AsDocument; - docs.Add(doc); - } - } + yield return doc; - foreach(var doc in docs) - { - yield return doc; - - _trans.CheckPoint(); + _trans.CheckPoint(); + } } } diff --git a/LiteDB/Engine/Services/LockService.cs b/LiteDB/Engine/Services/LockService.cs index 0f6701dc1..0595068f8 100644 --- a/LiteDB/Engine/Services/LockService.cs +++ b/LiteDB/Engine/Services/LockService.cs @@ -97,8 +97,11 @@ public LockControl Exclusive() return new LockControl(() => { }); } - // if are locked in shared, exit read to enter in write - if (_state == LockState.Shared) throw new NotSupportedException("Lock are in exclusive mode"); + // test if came from a shared lock (to restore after unlock) + var shared = _state == LockState.Shared; + + // if came, need exit read lock + if (shared) _thread.ExitReadLock(); // try enter in write mode (thread) if (!_thread.TryEnterWriteLock(_timeout)) @@ -111,7 +114,11 @@ public LockControl Exclusive() _log.Write(Logger.LOCK, "entered in exclusive lock mode"); - this.AvoidDirtyRead(); + // call avoid dirty only if not came from a shared mode + if (!shared) + { + this.AvoidDirtyRead(); + } _state = LockState.Exclusive; @@ -120,10 +127,20 @@ public LockControl Exclusive() // release thread write _thread.ExitWriteLock(); + // + if (shared) + { + _thread.TryEnterReadLock(_timeout); + _state = LockState.Shared; + } + // release disk exclusive _disk.Unlock(LockState.Exclusive); - _state = LockState.Unlocked; + if (!shared) + { + _state = LockState.Unlocked; + } }); } } diff --git a/LiteDB/Utils/Extensions/StreamExtensions.cs b/LiteDB/Utils/Extensions/StreamExtensions.cs index b8f69f7b3..07cd1d3dd 100644 --- a/LiteDB/Utils/Extensions/StreamExtensions.cs +++ b/LiteDB/Utils/Extensions/StreamExtensions.cs @@ -35,6 +35,8 @@ public static void CopyTo(this Stream input, Stream output) /// public static bool TryUnlock(this FileStream stream, long position, long length) { + if (length == 0) return true; + try { #if NET35 @@ -53,6 +55,8 @@ public static bool TryUnlock(this FileStream stream, long position, long length) /// public static void TryLock(this FileStream stream, long position, long length, TimeSpan timeout) { + if (length == 0) return; + FileHelper.TryExec(() => { #if NET35 diff --git a/README.md b/README.md index 25fbf4e6d..98fde8939 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ +# TODO - next major version + +- Remove transactions +- Remove auto-create index +- Add full scan when no index +- think about how/when create indexes defined by [Attributes] of FluentApi +- cache results in query +- findAndModify + + + # LiteDB - A .NET NoSQL Document Store in a single data file [![Join the chat at https://gitter.im/mbdavid/LiteDB](https://badges.gitter.im/mbdavid/LiteDB.svg)](https://gitter.im/mbdavid/LiteDB?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build status](https://ci.appveyor.com/api/projects/status/sfe8he0vik18m033?svg=true)](https://ci.appveyor.com/project/mbdavid/litedb) [![Build Status](https://travis-ci.org/mbdavid/LiteDB.svg?branch=master)](https://travis-ci.org/mbdavid/LiteDB)