aboutsummaryrefslogtreecommitdiff
path: root/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerLockHelperTests.cs
blob: 8149938b4d37e7e7f9f642742afd146252c80136 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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;
                },
                TestContext.Current.CancellationToken);

            await firstAcquired.Task;

            var secondTask = Task.Run(
                async () =>
                {
                    using var secondHandle = await helper.LockAsync(key);
                    secondEntered = true;
                },
                TestContext.Current.CancellationToken);

            await Task.Delay(100, TestContext.Current.CancellationToken);
            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);
        }
    }
}