@@ -24,11 +24,9 @@ public sealed class HubLifetimeManager
2424 private readonly IRedisPubService _redisPubService ;
2525 private readonly ILoggerFactory _loggerFactory ;
2626 private readonly ILogger < HubLifetimeManager > _logger ;
27- private readonly ConcurrentDictionary < Guid , HubLifetime > _lifetimes = new ( ) ;
28- private readonly ConcurrentDictionary < Guid , SemaphoreSlim > _hubLocks = new ( ) ;
29-
30- private readonly HashSet < Guid > _pendingHubs = [ ] ;
31- private readonly SemaphoreSlim _pendingHubsLock = new ( 1 ) ;
27+ private readonly Dictionary < Guid , HubLifetime > _lifetimes = new ( ) ;
28+
29+ private readonly SemaphoreSlim _lifetimesLock = new ( 1 ) ;
3230
3331 /// <summary>
3432 /// DI constructor
@@ -62,100 +60,99 @@ ILoggerFactory loggerFactory
6260 public async Task < bool > TryAddDeviceConnection ( byte tps , IHubController hubController ,
6361 CancellationToken cancellationToken )
6462 {
65- // Try finally for _pendingHubs Add <-> Remove scope
66- try
63+ var isSwapping = false ;
64+ HubLifetime ? hubLifetime ;
65+
66+ using ( await _lifetimesLock . LockAsyncScoped ( cancellationToken ) )
6767 {
68- using ( await _pendingHubsLock . LockAsyncScoped ( cancellationToken ) )
69- {
70- if ( ! _pendingHubs . Add ( hubController . Id ) ) return false ; // Another hub is already queued here
71- }
72-
73- using ( await _hubLocks . GetOrAdd ( hubController . Id , new SemaphoreSlim ( 1 ) ) . LockAsyncScoped ( cancellationToken ) ) // Any only one thread is allowed here, per hub, and that is what we want.
68+ if ( _lifetimes . TryGetValue ( hubController . Id , out hubLifetime ) )
7469 {
75- if ( _lifetimes . TryGetValue ( hubController . Id , out var oldControllerLifetime ) )
70+ // There already is a hub lifetime, lets swap!
71+ if ( ! hubLifetime . TryMarkSwapping ( ) )
7672 {
77- _logger . LogDebug ( "Disposing old hub controller" ) ;
78- await oldControllerLifetime
79- . DisposeForNewConnection ( ) ; // Use this to not call the remove connection method from the controller
80-
81- foreach ( var websocketController in WebsocketManager . LiveControlUsers . GetConnections ( hubController
82- . Id ) )
83- await websocketController . UpdateConnectedState ( false ) ;
73+ return
74+ false ; // Tell the controller that we are busy right now TODO: Tell the connecting client why it failed
8475 }
8576
86- var hubLifetime = GetLifetime ( tps , hubController , cancellationToken ) ;
77+ isSwapping = true ;
78+ }
79+ else
80+ {
81+ // This is a fresh connection with no existing lifetime, create one!
82+ hubLifetime = CreateNewLifetime ( tps , hubController ) ;
8783 _lifetimes [ hubController . Id ] = hubLifetime ;
84+ }
85+ }
8886
89- await using var db = await _dbContextFactory . CreateDbContextAsync ( cancellationToken ) ;
90-
91- await hubLifetime . InitAsync ( db ) ;
92-
93- foreach ( var websocketController in WebsocketManager . LiveControlUsers . GetConnections ( hubController . Id ) )
94- await websocketController . UpdateConnectedState ( true ) ;
9587
96- return true ;
97- }
88+ if ( isSwapping )
89+ {
90+ await hubLifetime . Swap ( hubController ) ;
9891 }
99- finally
92+ else
10093 {
101- using ( await _pendingHubsLock . LockAsyncScoped ( cancellationToken ) )
102- {
103- _pendingHubs . Remove ( hubController . Id ) ;
104- }
94+ await hubLifetime . InitAsync ( ) ;
95+
96+ foreach ( var websocketController in WebsocketManager . LiveControlUsers . GetConnections ( hubLifetime
97+ . HubController . Id ) )
98+ await websocketController . UpdateConnectedState ( true ) ;
10599 }
100+
101+ return true ;
106102 }
107103
108- private HubLifetime GetLifetime ( byte tps , IHubController hubController , CancellationToken cancellationToken )
104+ private HubLifetime CreateNewLifetime ( byte tps , IHubController hubController )
109105 {
110- _logger . LogInformation ( "New device connected, creating lifetime [{DeviceId}]" , hubController . Id ) ;
106+ _logger . LogInformation ( "New hub connected, creating lifetime [{DeviceId}]" , hubController . Id ) ;
111107
112108 var deviceLifetime = new HubLifetime (
113109 tps ,
114110 hubController ,
115111 _dbContextFactory ,
116112 _redisConnectionProvider ,
117113 _redisPubService ,
118- _loggerFactory . CreateLogger < HubLifetime > ( ) ,
119- cancellationToken ) ;
114+ _loggerFactory . CreateLogger < HubLifetime > ( ) ) ;
120115
121116 return deviceLifetime ;
122117 }
123118
124119 /// <summary>
125- /// Remove device from Lifetime Manager, called on dispose of device controller, this is the actual end of life of the hub
120+ /// Remove device from Lifetime Manager, called on dispose of device controller,
121+ /// this is the actual end of life of the hub
126122 /// </summary>
127123 /// <param name="hubController"></param>
128124 public async Task RemoveDeviceConnection ( IHubController hubController )
129125 {
130- if ( ! _hubLocks . TryGetValue ( hubController . Id , out var hubLock ) )
131- {
132- // Our lock doesnt exist, this shouldn't happen
133- _logger . LogWarning ( "Hub lock not found for hub [{HubId}]" , hubController . Id ) ;
134- return ;
135- }
136-
137- using ( await hubLock . LockAsyncScoped ( ) )
126+ HubLifetime ? hubLifetime ;
127+
128+ using ( await _lifetimesLock . LockAsyncScoped ( ) )
138129 {
139- try
130+ if ( ! _lifetimes . TryGetValue ( hubController . Id , out hubLifetime ) )
140131 {
141- if ( ! _lifetimes . TryGetValue ( hubController . Id , out var oldControllerLifetime ) ) return ;
142-
143- if ( oldControllerLifetime . HubController != hubController ) return ;
144-
145- _lifetimes . TryRemove ( hubController . Id , out _ ) ;
132+ _logger . LogError ( "Hub lifetime not found for hub [{HubId}]" , hubController . Id ) ;
133+ return ;
134+ }
146135
147- foreach ( var websocketController in WebsocketManager . LiveControlUsers . GetConnections ( hubController . Id ) )
148- await websocketController . UpdateConnectedState ( false ) ;
136+ // Dont remove a hub lifetime that has a different hub controller,
137+ // this might happen when remove is called after a swap has been fully done
138+ if ( hubLifetime . HubController != hubController ) return ;
139+
140+ if ( hubLifetime . TryMarkRemoving ( ) )
141+ {
142+ return ;
149143 }
150- finally
144+ }
145+
146+ foreach ( var websocketController in WebsocketManager . LiveControlUsers . GetConnections ( hubController . Id ) )
147+ await websocketController . UpdateConnectedState ( false ) ;
148+
149+ await hubLifetime . DisposeAsync ( ) ;
150+
151+ using ( await _lifetimesLock . LockAsyncScoped ( ) )
152+ {
153+ if ( ! _lifetimes . Remove ( hubController . Id ) )
151154 {
152- using ( await _pendingHubsLock . LockAsyncScoped ( ) )
153- {
154- if ( ! _pendingHubs . Contains ( hubController . Id ) )
155- {
156- _hubLocks . TryRemove ( hubController . Id , out _ ) ;
157- }
158- }
155+ _logger . LogError ( "Failed to remove hub lifetime [{HubId}], this shouldnt happen WTF?!" , hubController . Id ) ;
159156 }
160157 }
161158 }
0 commit comments