Skip to content

Commit b14a74c

Browse files
authored
Bugfix/191.concurrency and isolation level (#197)
* Implement a unit-test to show concurrency issues in PostgreSqlWriteOnlyTransaction cause by RepeatableRead isolation level * Use ReadCommitted isolation level in PostgreSqlWriteOnlyTransaction.cs to fix concurrency issues
1 parent de7d19b commit b14a74c

File tree

3 files changed

+51
-1
lines changed

3 files changed

+51
-1
lines changed

src/Hangfire.PostgreSql/PostgreSqlWriteOnlyTransaction.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public override void Dispose() { }
5454

5555
public override void Commit()
5656
{
57-
var isolationLevel = IsolationLevel.RepeatableRead;
57+
var isolationLevel = IsolationLevel.ReadCommitted;
5858
var scopeOption = TransactionScopeOption.RequiresNew;
5959
if (_options.EnableTransactionScopeEnlistment)
6060
{

tests/Hangfire.PostgreSql.Tests/Hangfire.PostgreSql.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
</PackageReference>
2525
<PackageReference Include="xunit" Version="2.4.1" />
2626
<PackageReference Include="Moq" Version="4.15.1" />
27+
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
2728
</ItemGroup>
2829

2930
</Project>

tests/Hangfire.PostgreSql.Tests/PostgreSqlWriteOnlyTransactionFacts.cs

+49
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Data;
44
using System.Globalization;
55
using System.Linq;
6+
using System.Threading.Tasks;
67
using System.Transactions;
78
using Dapper;
89
using Hangfire.Common;
@@ -463,6 +464,54 @@ public void AddToSet_WithScore_UpdatesAScore_WhenBothKeyAndValueAreExist()
463464
});
464465
}
465466

467+
[SkippableFact, CleanDatabase]
468+
public void AddToSet_DoesNotFailWithConcurrencyError_WhenRunningMultipleThreads()
469+
{
470+
if (Environment.ProcessorCount < 2)
471+
{
472+
throw new SkipException("You need to have more than 1 CPU to run the test");
473+
}
474+
475+
void CommitTags(PostgreSqlWriteOnlyTransaction transaction, IEnumerable<string> tags, string jobId)
476+
{
477+
//Imitating concurrency issue scenario from Hangfire.Tags library.
478+
//Details: https://github.com/frankhommers/Hangfire.PostgreSql/issues/191
479+
480+
foreach (var tag in tags)
481+
{
482+
var score = DateTime.Now.Ticks;
483+
484+
transaction.AddToSet("tags", tag, score);
485+
transaction.AddToSet($"tags:{jobId}", tag, score);
486+
transaction.AddToSet($"tags:{tag}", jobId, score);
487+
}
488+
}
489+
490+
Parallel.For(1, 1_000, i =>
491+
{
492+
UseConnection(sql =>
493+
{
494+
Utils.Utils.TryExecute(() =>
495+
{
496+
Commit(sql, x =>
497+
{
498+
int jobTypeIndex = i % 10;
499+
CommitTags(x, new[] {"my-shared-tag", $"job-type-{jobTypeIndex}"}, i.ToString());
500+
});
501+
}, e =>
502+
{
503+
/* Account for 'duplicate key value violates unique constraint "set_key_value_key"' error
504+
* Details: https://github.com/frankhommers/Hangfire.PostgreSql/issues/191#issuecomment-872367869
505+
*
506+
* Once AddToSet is improved to use MERGE statement, we should be able to remove
507+
* retry-policy from this unit-test.
508+
*/
509+
return e is PostgresException postgresException && postgresException.SqlState == "23505";
510+
});
511+
});
512+
});
513+
}
514+
466515
[Fact, CleanDatabase]
467516
public void RemoveFromSet_RemovesARecord_WithGivenKeyAndValue()
468517
{

0 commit comments

Comments
 (0)