Skip to content

Commit 11d62ae

Browse files
authored
Merge pull request #1353 from PepperDash/mc-subscription-concurrency
fix: make subscriber functionality thread-safe
2 parents d3ceb4d + edc10a9 commit 11d62ae

File tree

1 file changed

+31
-10
lines changed

1 file changed

+31
-10
lines changed

src/PepperDash.Essentials.MobileControl.Messengers/Messengers/MessengerBase.cs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ public abstract class MessengerBase : EssentialsDevice, IMobileControlMessengerW
3131
/// <remarks>
3232
/// Unsoliciited feedback from a device in a messenger will ONLY be sent to devices in this subscription list. When a client disconnects, it's ID will be removed from the collection.
3333
/// </remarks>
34-
protected HashSet<string> SubscriberIds = new HashSet<string>();
34+
private readonly HashSet<string> subscriberIds = new HashSet<string>();
35+
36+
/// <summary>
37+
/// Lock object for thread-safe access to SubscriberIds
38+
/// </summary>
39+
private readonly object _subscriberLock = new object();
3540

3641
private readonly List<string> _deviceInterfaces;
3742

@@ -193,14 +198,15 @@ protected void SubscribeClient(string clientId)
193198
return;
194199
}
195200

196-
if (SubscriberIds.Any(id => id == clientId))
201+
lock (_subscriberLock)
197202
{
198-
this.LogVerbose("Client {clientId} already subscribed", clientId);
199-
return;
203+
if (!subscriberIds.Add(clientId))
204+
{
205+
this.LogVerbose("Client {clientId} already subscribed", clientId);
206+
return;
207+
}
200208
}
201209

202-
SubscriberIds.Add(clientId);
203-
204210
this.LogDebug("Client {clientId} subscribed", clientId);
205211
}
206212

@@ -216,14 +222,22 @@ public void UnsubscribeClient(string clientId)
216222
return;
217223
}
218224

219-
if (!SubscriberIds.Any(i => i == clientId))
225+
bool wasSubscribed;
226+
lock (_subscriberLock)
227+
{
228+
wasSubscribed = subscriberIds.Contains(clientId);
229+
if (wasSubscribed)
230+
{
231+
subscriberIds.Remove(clientId);
232+
}
233+
}
234+
235+
if (!wasSubscribed)
220236
{
221237
this.LogVerbose("Client with ID {clientId} is not subscribed", clientId);
222238
return;
223239
}
224240

225-
SubscriberIds.RemoveWhere((i) => i == clientId);
226-
227241
this.LogInformation("Client with ID {clientId} unsubscribed", clientId);
228242
}
229243

@@ -312,7 +326,14 @@ protected void PostStatusMessage(JToken content, string type = "", string client
312326
// If client is null or empty, this message is unsolicited feedback. Iterate through the subscriber list and send to all interested parties
313327
if (string.IsNullOrEmpty(clientId))
314328
{
315-
foreach (var client in SubscriberIds)
329+
// Create a snapshot of subscribers to avoid collection modification during iteration
330+
List<string> subscriberSnapshot;
331+
lock (_subscriberLock)
332+
{
333+
subscriberSnapshot = new List<string>(subscriberIds);
334+
}
335+
336+
foreach (var client in subscriberSnapshot)
316337
{
317338
AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = client, Content = content });
318339
}

0 commit comments

Comments
 (0)