LiteDB
LiteDB copied to clipboard
[BUG] The read lock is being released without being held
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()
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
Any updates? Facing the same bug when using Tasks
Im also getting these on .NET 6.0
Me too
Also getting on .NET 6 - using singleton and no transactions whatsoever. Are there implicit transactions?
Sadly still getting the same issue in .NET 6
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();
}
}
}