-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathConcurrentSessionCounter.cs
More file actions
89 lines (77 loc) · 2.83 KB
/
Copy pathConcurrentSessionCounter.cs
File metadata and controls
89 lines (77 loc) · 2.83 KB
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
using EnterpriseChat.Licensing.Abstractions;
namespace EnterpriseChat.Server.Licensing;
/// <summary>
/// In-memory counter that combines per-user connection count with the active
/// <see cref="ILicenseValidator"/> to gate new admissions. The licence cap
/// applies to <b>distinct users with at least one active connection</b>, not
/// to raw connection count — a user opening a second window should not
/// consume a second licence slot.
/// </summary>
public sealed class ConcurrentSessionCounter(ILicenseValidator validator)
{
private readonly Dictionary<int, int> _connectionsPerUser = [];
private readonly object _gate = new();
public SessionAdmission TryAdmit(int userId)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(userId);
lock (_gate)
{
if (_connectionsPerUser.TryGetValue(userId, out var existing) && existing > 0)
{
_connectionsPerUser[userId] = existing + 1;
return new SessionAdmission(Admitted: true, DeniedReason: null, IsFirstConnection: false);
}
var distinctActive = _connectionsPerUser.Count(kv => kv.Value > 0);
var verdict = validator.TryAdmitSession(distinctActive);
if (verdict.Admitted)
{
_connectionsPerUser[userId] = 1;
return new SessionAdmission(Admitted: true, DeniedReason: null, IsFirstConnection: true);
}
return new SessionAdmission(Admitted: false, DeniedReason: verdict.DeniedReason, IsFirstConnection: false);
}
}
public SessionRelease Release(int userId)
{
lock (_gate)
{
if (!_connectionsPerUser.TryGetValue(userId, out var existing))
{
return new SessionRelease(WasLastConnection: false);
}
if (existing <= 1)
{
_connectionsPerUser.Remove(userId);
return new SessionRelease(WasLastConnection: true);
}
_connectionsPerUser[userId] = existing - 1;
return new SessionRelease(WasLastConnection: false);
}
}
public int DistinctActiveUsers
{
get
{
lock (_gate)
{
return _connectionsPerUser.Count(kv => kv.Value > 0);
}
}
}
public bool IsOnline(int userId)
{
lock (_gate)
{
return _connectionsPerUser.TryGetValue(userId, out var n) && n > 0;
}
}
public IReadOnlyCollection<int> Snapshot()
{
lock (_gate)
{
return _connectionsPerUser.Where(kv => kv.Value > 0).Select(kv => kv.Key).ToArray();
}
}
}
public sealed record SessionAdmission(bool Admitted, string? DeniedReason, bool IsFirstConnection);
public sealed record SessionRelease(bool WasLastConnection);