wenxiangchao
4 天以前 559890c482f5973f06aafdfec9550fee5ba3c7f2
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Aging.Infrastructure.Core;
using Aging.ResourceContracts;
using Xunit;
 
namespace Aging.MvpTests
{
    public class DbLeaseStoreTests : IDisposable
    {
        private readonly string _dbPath;
        private readonly DbLeaseStore _store;
 
        public DbLeaseStoreTests()
        {
            _dbPath = Path.Combine(Path.GetTempPath(),
                "arb_test_" + Guid.NewGuid().ToString("N") + ".db");
            _store = new DbLeaseStore(_dbPath, "local-main");
        }
        public void Dispose() { try { File.Delete(_dbPath); File.Delete(_dbPath + "-wal"); File.Delete(_dbPath + "-shm"); } catch { } }
 
        private AcquireRequest Req(string key, OwnerTypeV3 type = OwnerTypeV3.Maintenance,
            int ttl = 30) => new AcquireRequest
        {
            OwnerId = "test-owner", OwnerType = type,
            ProcessInstanceId = Guid.NewGuid().ToString("N"),
            ResourceKeys = new[] { key }, TtlSeconds = ttl
        };
 
        [Fact]
        public void Acquire_FirstOwner_Succeeds()
        {
            var r = _store.Acquire(Req("can/can02"));
            Assert.Equal(AcquireResultCode.Acquired, r.Code);
            Assert.Equal(1, r.Grants[0].Generation);
        }
 
        [Fact]
        public void Acquire_WhileActiveByOther_ReturnsConflictWithHolder()
        {
            _store.Acquire(Req("can/can02"));
            var second = new AcquireRequest
            {
                OwnerId = "other-owner", OwnerType = OwnerTypeV3.Production,
                ProcessInstanceId = "x", ResourceKeys = new[] { "can/can02" }
            };
            var r = _store.Acquire(second);
            Assert.Equal(AcquireResultCode.ConflictHeld, r.Code);
            Assert.Equal("test-owner", r.Holder.OwnerId);
        }
 
        [Fact]
        public void TwoIndependentStores_SameFile_SimulatesTwoProcesses()
        {
            var storeB = new DbLeaseStore(_dbPath, "local-main");
            var a = _store.Acquire(Req("psu/psu01/ch/5"));
            var b = storeB.Acquire(new AcquireRequest
            {
                OwnerId = "W02", OwnerType = OwnerTypeV3.Production,
                ProcessInstanceId = "p2", ResourceKeys = new[] { "psu/psu01/ch/5" }
            });
            Assert.Equal(AcquireResultCode.Acquired, a.Code);
            Assert.Equal(AcquireResultCode.ConflictHeld, b.Code);
        }
 
        [Fact]
        public void Concurrent_50Threads_OnlyOneWins()
        {
            int winners = 0;
            Parallel.For(0, 50, _ =>
            {
                var s = new DbLeaseStore(_dbPath, "local-main");
                var r = s.Acquire(new AcquireRequest
                {
                    OwnerId = Guid.NewGuid().ToString("N"), OwnerType = OwnerTypeV3.Production,
                    ProcessInstanceId = "p", ResourceKeys = new[] { "can/bus" }, WaitTimeoutMs = 3000
                });
                if (r.Code == AcquireResultCode.Acquired) Interlocked.Increment(ref winners);
            });
            Assert.Equal(1, winners);
        }
 
        [Fact]
        public void ExpiredLease_EntersRecoveryRequired_NotAutoReclaim()
        {
            _store.Acquire(Req("can/can02", ttl: -1));
            _store.SweepExpired();
            var r = _store.Acquire(Req("can/can02", OwnerTypeV3.Production));
            Assert.Equal(AcquireResultCode.RecoveryBlocked, r.Code);
 
            Assert.True(_store.ApproveRecovery("can/can02", "{\"output\":\"off\"}", "admin"));
            var again = _store.Acquire(Req("can/can02", OwnerTypeV3.Production));
            Assert.Equal(AcquireResultCode.Acquired, again.Code);
            Assert.Equal(2, again.Grants[0].Generation);
        }
 
        [Fact]
        public void Renew_WithStaleGeneration_Fails()
        {
            var g = _store.Acquire(Req("can/can02")).Grants[0];
            var stale = new LeaseGrant(g.AuthorityId, g.ResourceKey, g.LeaseId,
                g.OwnerId, g.OwnerType, g.Epoch, 999, g.ExpiresUtcMs);
            Assert.False(_store.Renew(stale, 30));
            Assert.True(_store.Renew(g, 30));
        }
 
        [Fact]
        public void BumpEpoch_InvalidatesAllActiveGrants()
        {
            var g = _store.Acquire(Req("can/can02")).Grants[0];
            _store.BumpEpochForTest("arb.db 重启模拟");
            Assert.False(_store.Renew(g, 30));
        }
 
        [Fact]
        public void GroupAcquire_PartialConflict_RollsBackAll()
        {
            _store.Acquire(Req("psu/b"));
            var r = _store.Acquire(new AcquireRequest
            {
                OwnerId = "W09", OwnerType = OwnerTypeV3.Production, ProcessInstanceId = "p",
                ResourceKeys = new[] { "psu/a", "psu/b", "psu/c" }
            });
            Assert.Equal(AcquireResultCode.ConflictHeld, r.Code);
            Assert.Null(_store.ProbeConflict("psu/a"));
        }
 
        [Fact]
        public void ExpiredLease_ReconcileAfterBoot_FreesResource_ForNewAcquire()
        {
            // 模拟旧 owner 进程未释放就异常退出:短 TTL 租约泄漏后过期
            var leaked = _store.Acquire(Req("can/can99", OwnerTypeV3.Maintenance, ttl: 1));
            Assert.Equal(AcquireResultCode.Acquired, leaked.Code);
            Thread.Sleep(1600);
 
            // 未对账前:过期租约进入 RecoveryBlocked(永久阻塞现状)
            var blocked = _store.Acquire(new AcquireRequest
            {
                OwnerId = "new-owner", OwnerType = OwnerTypeV3.Maintenance,
                ProcessInstanceId = "y", ResourceKeys = new[] { "can/can99" }
            });
            Assert.Equal(AcquireResultCode.RecoveryBlocked, blocked.Code);
 
            // 启动对账自愈(INV-03 自动化形式,证据落 recovery_checkpoint)
            Assert.True(_store.ReconcileAfterBoot("HOST/999") >= 1);
            Assert.Equal(LeasePhase.Free, _store.PhaseOf("can/can99"));
 
            // 对账后可重新获取,且 Generation 只前进不回退
            var reacquired = _store.Acquire(new AcquireRequest
            {
                OwnerId = "new-owner", OwnerType = OwnerTypeV3.Maintenance,
                ProcessInstanceId = "y", ResourceKeys = new[] { "can/can99" }
            });
            Assert.Equal(AcquireResultCode.Acquired, reacquired.Code);
            Assert.True(reacquired.Grants[0].Generation > leaked.Grants[0].Generation);
        }
    }
}