aboutsummaryrefslogtreecommitdiff
path: root/tests/Jellyfin.Server.Implementations.Tests/Users
diff options
context:
space:
mode:
authorJPVenson <github@jpb.email>2026-06-07 14:29:36 -0400
committerBond_009 <bond.009@outlook.com>2026-06-07 14:29:36 -0400
commit6ccdaad0a47351d30cfde39c7470ae26bb6f7567 (patch)
tree70fc279ec70be18c4214b8548af17edaf8bb8336 /tests/Jellyfin.Server.Implementations.Tests/Users
parentec43ea156e11705227eccbe760e2510a28d774a4 (diff)
Backport pull request #16944 from jellyfin/release-10.11.z
Add lockhelper for UserManager Original-merge: 39958ad9e51f83f520803aa9bc5e70e58f3a0836 Merged-by: Bond-009 <bond.009@outlook.com> Backported-by: Bond_009 <bond.009@outlook.com>
Diffstat (limited to 'tests/Jellyfin.Server.Implementations.Tests/Users')
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerLockHelperTests.cs89
1 files changed, 89 insertions, 0 deletions
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerLockHelperTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerLockHelperTests.cs
new file mode 100644
index 0000000000..ab6f0fd32e
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerLockHelperTests.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Threading.Tasks;
+using Jellyfin.Server.Implementations.Users;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Users
+{
+ public class UserManagerLockHelperTests
+ {
+ [Fact]
+ public async Task LockAsync_WhenNested_DoesNotAcquireSecondLockAndRestoresStateOnDispose()
+ {
+ UserManager.LockHelper.IsNestedLock.Value = 0;
+ using var helper = new UserManager.LockHelper();
+ var key = Guid.NewGuid();
+
+ Assert.True(helper.ShouldLock());
+
+ var outerHandle = await helper.LockAsync(key);
+ Assert.False(helper.ShouldLock());
+
+ var innerHandle = await helper.LockAsync(key);
+ Assert.False(helper.ShouldLock());
+
+ innerHandle.Dispose();
+ Assert.False(helper.ShouldLock());
+
+ outerHandle.Dispose();
+ Assert.True(helper.ShouldLock());
+ }
+
+ [Fact]
+ public async Task LockAsync_WithSameKey_BlocksSecondLockUntilFirstIsReleased()
+ {
+ UserManager.LockHelper.IsNestedLock.Value = 0;
+ using var helper = new UserManager.LockHelper();
+ var key = Guid.NewGuid();
+
+ var firstAcquired = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var releaseFirst = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var secondEntered = false;
+
+ var firstTask = Task.Run(async () =>
+ {
+ using var firstHandle = await helper.LockAsync(key);
+ firstAcquired.SetResult(true);
+ await releaseFirst.Task;
+ });
+
+ await firstAcquired.Task;
+
+ var secondTask = Task.Run(async () =>
+ {
+ using var secondHandle = await helper.LockAsync(key);
+ secondEntered = true;
+ });
+
+ await Task.Delay(100);
+ Assert.False(secondEntered);
+
+ releaseFirst.SetResult(true);
+
+ await Task.WhenAll(firstTask, secondTask);
+ Assert.True(secondEntered);
+ }
+
+ [Fact]
+ public async Task LockAsync_WhenDisposed_ThrowsObjectDisposedException()
+ {
+ UserManager.LockHelper.IsNestedLock.Value = 0;
+ using var helper = new UserManager.LockHelper();
+ helper.Dispose();
+
+ await Assert.ThrowsAsync<ObjectDisposedException>(async () => await helper.LockAsync(Guid.NewGuid()));
+ }
+
+ [Fact]
+ public void Dispose_WhenCalledMultipleTimes_DoesNotThrow()
+ {
+ UserManager.LockHelper.IsNestedLock.Value = 0;
+ using var helper = new UserManager.LockHelper();
+
+ helper.Dispose();
+ var ex = Record.Exception(() => helper.Dispose());
+
+ Assert.Null(ex);
+ }
+ }
+}