LiteDB icon indicating copy to clipboard operation
LiteDB copied to clipboard

[BUG] The read lock is being released without being held

Open laurentiucocanu opened this issue 4 years ago • 5 comments
trafficstars

Version 5.0.10/Windows 10/NET 5

Describe the bug Trying to read and update records in a parallel foreach throws an exception with the following message: "The read lock is being released without being held." It seems to run fine in 5.0.3, but not in 5.0.10.

Code to Reproduce

    public class Target
    {
        [BsonId]
        public ObjectId Id { get; set; }

        public string Name { get; set; }

        public DateTime LastUpdateCheck { get; set; }
    }

    [Fact]
    public void InsertReadTest()
    {
        var dbFullPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
        try
        {
            using (var db = new LiteDatabase(dbFullPath))
            {
                var table = db.GetCollection<Target>("targets");
                for (int i = 0; i < 10000; i++)
                {
                    table.Insert(new Target
                    {
                        Name = $"Name_{i}",
                        LastUpdateCheck = DateTime.UtcNow
                    });
                }
                var targetIds = table.FindAll().Select(t => t.Id);
                Parallel.ForEach(targetIds, targetId =>
                {
                    var target = table.FindOne(ur => ur.Id == targetId);
                    target.LastUpdateCheck = DateTime.UtcNow;
                    table.Update(target);
                });
            }
        }
        finally
        {
            File.Delete(dbFullPath);
        }
    }

Expected behavior No exception thrown.

Screenshots/Stacktrace The read lock is being released without being held.

at System.Threading.Tasks.TaskReplicator.Run[TState](ReplicatableUserAction1 action, ParallelOptions options, Boolean stopOnFirstFailure) at System.Threading.Tasks.Parallel.PartitionerForEachWorker[TSource,TLocal](Partitioner1 source, ParallelOptions parallelOptions, Action1 simpleBody, Action2 bodyWithState, Action3 bodyWithStateAndIndex, Func4 bodyWithStateAndLocal, Func5 bodyWithEverything, Func1 localInit, Action1 localFinally) --- End of stack trace from previous location --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source) at System.Threading.Tasks.Parallel.ThrowSingleCancellationExceptionOrOtherException(ICollection exceptions, CancellationToken cancelToken, Exception otherException) at System.Threading.Tasks.Parallel.PartitionerForEachWorker[TSource,TLocal](Partitioner1 source, ParallelOptions parallelOptions, Action1 simpleBody, Action2 bodyWithState, Action3 bodyWithStateAndIndex, Func4 bodyWithStateAndLocal, Func5 bodyWithEverything, Func1 localInit, Action1 localFinally) at System.Threading.Tasks.Parallel.ForEachWorker[TSource,TLocal](IEnumerable1 source, ParallelOptions parallelOptions, Action1 body, Action2 bodyWithState, Action3 bodyWithStateAndIndex, Func4 bodyWithStateAndLocal, Func5 bodyWithEverything, Func1 localInit, Action1 localFinally) at System.Threading.Tasks.Parallel.ForEach[TSource](IEnumerable1 source, Action`1 body)

Inner exception: at System.Threading.ReaderWriterLockSlim.ExitReadLock() at LiteDB.Engine.TransactionMonitor.ReleaseTransaction(TransactionService transaction) at LiteDB.Engine.QueryExecutor.<>c__DisplayClass10_0.<<ExecuteQuery>g__RunQuery|0>d.MoveNext() at LiteDB.BsonDataReader.Read() at LiteDB.LiteQueryable1.<ToDocuments>d__26.MoveNext() at System.Linq.Enumerable.SelectEnumerableIterator2.MoveNext() at System.Collections.Concurrent.Partitioner.DynamicPartitionerForIEnumerable1.InternalPartitionEnumerable.GrabChunk_Buffered(KeyValuePair2[] destArray, Int32 requestedChunkSize, Int32& actualNumElementsGrabbed) at System.Collections.Concurrent.Partitioner.DynamicPartitionEnumerator_Abstract2.MoveNext() at System.Threading.Tasks.Parallel.<>c__DisplayClass44_02.<PartitionerForEachWorker>b__1(IEnumerator& partitionState, Int32 timeout, Boolean& replicationDelegateYieldedBeforeCompletion) --- End of stack trace from previous location --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source) at System.Threading.Tasks.Parallel.<>c__DisplayClass44_02.<PartitionerForEachWorker>b__1(IEnumerator& partitionState, Int32 timeout, Boolean& replicationDelegateYieldedBeforeCompletion) at System.Threading.Tasks.TaskReplicator.Replica1.ExecuteAction(Boolean& yieldedBeforeCompletion) at System.Threading.Tasks.TaskReplicator.Replica.Execute()

laurentiucocanu avatar Jun 14 '21 09:06 laurentiucocanu

Also getting this, but not getting a full stack trace in the error reporting I use of where it's originating from. .NET 4.8, latest LiteDB

System.Threading.ReaderWriterLockSlim.ExitReadLock():122
LiteDB.Engine.TransactionMonitor.ReleaseTransaction(TransactionService transaction):123
LiteDB.Engine.TransactionService.Dispose(Boolean dispose):439
LiteDB.Engine.TransactionService.Finalize():16

nabeelio avatar Oct 13 '21 12:10 nabeelio

Any updates? Facing the same bug when using Tasks

Rickedb avatar Jan 24 '22 03:01 Rickedb

Im also getting these on .NET 6.0

gorillapower avatar Mar 23 '22 18:03 gorillapower

Me too

ysinsane avatar Mar 30 '22 03:03 ysinsane

Also getting on .NET 6 - using singleton and no transactions whatsoever. Are there implicit transactions?

tdsalty avatar Jun 20 '22 13:06 tdsalty

Sadly still getting the same issue in .NET 6

ultravelocity avatar Nov 16 '22 00:11 ultravelocity

I getting the same error today at 13-07-2023 My code:

 public void AddFailedRequest(FailedRequest failedRequest)
        {
            using (var db = new LiteDatabase(Constants.FailedRequestsDb))
            {
                var failedRequests = db.GetCollection<FailedRequest>(Constants.FailedRequestsCollection);
                failedRequest.Created = DateTime.Now;
                failedRequests.Insert(failedRequest);
                //db.Commit();
            }
        }
public void HandleFailedRequestes()
        {
            _ = CallFailedRequests();
            async Task CallFailedRequests()
            {
                try
                {
                    _logger.LogInformation("Start handling failed requests.");
                    using (var db = new LiteDatabase(Constants.FailedRequestsDb))
                    {
                        var failedRequests = db.GetCollection<FailedRequest>(Constants.FailedRequestsCollection);
                        foreach (var request in failedRequests.FindAll())
                        {
                            _logger.LogInformation($"Found `{failedRequests.Count()}` failed request.");
                            try
                            {
                                var client = _httpClientFactory.CreateClient();
                                var response = await client.PostAsync(
                                    request.ActionUrl,
                                    new ByteArrayContent(request.Body));
                                response.EnsureSuccessStatusCode();
                                var result = await response.Content.ReadAsStringAsync();
                                if (!(result == "1" || result == "-1"))
                                    throw new Exception("Expected result must be 1 or -1");
                                failedRequests.Delete(request.Id);
                            }
                            catch (Exception)
                            {
                                request.AttemptsCount++;
                                _logger.LogInformation(
                                    $"Request `{request.Id}` failed againg for `{request.AttemptsCount}` times");
                                request.LastAttemptDate = DateTime.Now;
                                failedRequests.Update(request);
                                throw;
                            }
                        }
                    }
                    _logger.LogInformation("End handling failed requests.");
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Error when handling failed requests.");
                }
                finally
                {
                    await Task.Delay(_settings.CheckFailedRequestEvery);
                    await CallFailedRequests();
                }
            }
        }

IbrahimElshafey avatar Jul 13 '23 05:07 IbrahimElshafey