Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed an issue where we would get a concurrency exception when using …

…patches
  • Loading branch information...
commit 452e14ffe5f8cd9e97578e154067319f720e123a 1 parent 3fece9e
@ayende ayende authored
View
105 Raven.Database/DocumentDatabase.cs
@@ -949,33 +949,50 @@ public RavenJArray GetIndexes(int start, int pageSize)
public PatchResult ApplyPatch(string docId, Guid? etag, PatchRequest[] patchDoc, TransactionInformation transactionInformation)
{
var result = PatchResult.Patched;
- TransactionalStorage.Batch(actions =>
+ bool shouldRetry = false;
+ int retries = 128;
+ do
{
- var doc = actions.Documents.DocumentByKey(docId, transactionInformation);
- if (doc == null)
- {
- result = PatchResult.DocumentDoesNotExists;
- }
- else if (etag != null && doc.Etag != etag.Value)
+ TransactionalStorage.Batch(actions =>
{
- Debug.Assert(doc.Etag != null);
- throw new ConcurrencyException("Could not patch document '" + docId + "' because non current etag was used")
+ var doc = actions.Documents.DocumentByKey(docId, transactionInformation);
+ if (doc == null)
{
- ActualETag = doc.Etag.Value,
- ExpectedETag = etag.Value,
- };
- }
- else
- {
- var jsonDoc = doc.ToJson();
- new JsonPatcher(jsonDoc).Apply(patchDoc);
- Put(doc.Key, doc.Etag, jsonDoc, jsonDoc.Value<RavenJObject>("@metadata"), transactionInformation);
- result = PatchResult.Patched;
- }
-
- workContext.ShouldNotifyAboutWork();
- });
+ result = PatchResult.DocumentDoesNotExists;
+ }
+ else if (etag != null && doc.Etag != etag.Value)
+ {
+ Debug.Assert(doc.Etag != null);
+ throw new ConcurrencyException("Could not patch document '" + docId + "' because non current etag was used")
+ {
+ ActualETag = doc.Etag.Value,
+ ExpectedETag = etag.Value,
+ };
+ }
+ else
+ {
+ var jsonDoc = doc.ToJson();
+ new JsonPatcher(jsonDoc).Apply(patchDoc);
+ try
+ {
+ Put(doc.Key, doc.Etag, jsonDoc, jsonDoc.Value<RavenJObject>("@metadata"), transactionInformation);
+ }
+ catch (ConcurrencyException)
+ {
+ if(retries-- > 0)
+ {
+ shouldRetry = true;
+ return;
+ }
+ throw;
+ }
+ result = PatchResult.Patched;
+ }
+ if (shouldRetry == false)
+ workContext.ShouldNotifyAboutWork();
+ });
+ } while (shouldRetry);
return result;
}
@@ -984,28 +1001,46 @@ public BatchResult[] Batch(IEnumerable<ICommandData> commands)
var results = new List<BatchResult>();
var commandDatas = commands.ToArray();
- var shouldLock = commandDatas.Any(x=>x is PutCommandData);
-
+ int retries = 128;
+ var shouldLock = commandDatas.Any(x=>x is PutCommandData || x is PatchCommandData);
+ var shouldRetryIfGotConcurrencyError = commandDatas.All(x => x is PatchCommandData);
+ bool shouldRetry = false;
if(shouldLock)
Monitor.Enter(putSerialLock);
try
{
log.Debug("Executing batched commands in a single transaction");
- TransactionalStorage.Batch(actions =>
+ do
{
- foreach (var command in commandDatas)
+ try
{
- command.Execute(this);
- results.Add(new BatchResult
+ TransactionalStorage.Batch(actions =>
{
- Method = command.Method,
- Key = command.Key,
- Etag = command.Etag,
- Metadata = command.Metadata
+ foreach (var command in commandDatas)
+ {
+ command.Execute(this);
+ results.Add(new BatchResult
+ {
+ Method = command.Method,
+ Key = command.Key,
+ Etag = command.Etag,
+ Metadata = command.Metadata
+ });
+ }
+ workContext.ShouldNotifyAboutWork();
});
}
- workContext.ShouldNotifyAboutWork();
- });
+ catch (ConcurrencyException)
+ {
+ if (shouldRetryIfGotConcurrencyError && retries-- > 128)
+ {
+ shouldRetry = true;
+ results.Clear();
+ continue;
+ }
+ throw;
+ }
+ } while (shouldRetry);
log.Debug("Successfully executed {0} commands", results.Count);
}
finally
View
56 Raven.Tests/ConcurrentPatching.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Raven.Abstractions.Commands;
+using Raven.Abstractions.Data;
+using Raven.Json.Linq;
+using Xunit;
+
+namespace Raven.Tests
+{
+ public class ConcurrentPatching : LocalClientTest
+ {
+ [Fact]
+ public void CanConcurrentlyUpdateSameDocument()
+ {
+ using (var store = NewDocumentStore("esent", false))
+ {
+ using(var s = store.OpenSession())
+ {
+ s.Store(new Post{Comments = new List<Comment>()});
+ s.SaveChanges();
+ }
+
+ int numberOfComments = 128;
+ var patches = Enumerable.Range(0, numberOfComments).Select(x =>
+ new PatchRequest
+ {
+ Name = "Comments",
+ Type = PatchCommandType.Add,
+ Value = RavenJToken.FromObject(new Comment
+ {
+ AuthorId = "ayende"
+ })
+ });
+
+ Parallel.ForEach(patches, data => store.DatabaseCommands.Patch("posts/1", new[]{data}));
+
+ using (var s = store.OpenSession())
+ {
+ Assert.Equal(numberOfComments, s.Load<Post>("posts/1").Comments.Count);
+ }
+ }
+ }
+
+ public class Post
+ {
+ public string Id { get; set; }
+ public IList<Comment> Comments { get; set; }
+ }
+
+ public class Comment
+ {
+ public string AuthorId { get; set; }
+ }
+ }
+}
View
1  Raven.Tests/Raven.Tests.csproj
@@ -436,6 +436,7 @@
<Compile Include="Bugs\WithPrivateProtectedSetter.cs" />
<Compile Include="Bugs\Zhang\UseMaxForDateTimeTypeInReduce.cs" />
<Compile Include="Bugs\Zhang\UseMaxForLongTypeInReduce.cs" />
+ <Compile Include="ConcurrentPatching.cs" />
<Compile Include="ConnectionStrings.cs" />
<Compile Include="Document\AsyncDocumentStoreServerTests.cs" />
<Compile Include="Document\CasingIssue.cs" />
Please sign in to comment.
Something went wrong with that request. Please try again.