Skip to content

Commit

Permalink
Fix CallbackManager to be thread-safe (#1428)
Browse files Browse the repository at this point in the history
  • Loading branch information
xPaw authored Sep 15, 2024
1 parent 08e2df9 commit 223468c
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 15 deletions.
22 changes: 7 additions & 15 deletions SteamKit2/SteamKit2/Steam/SteamClient/CallbackMgr/CallbackMgr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using SteamKit2.Internal;
Expand All @@ -22,7 +22,7 @@ public sealed class CallbackManager : ICallbackMgrInternals
{
SteamClient client;

List<CallbackBase> registeredCallbacks;
ImmutableList<CallbackBase> registeredCallbacks = [];



Expand All @@ -34,8 +34,6 @@ public CallbackManager( SteamClient client )
{
ArgumentNullException.ThrowIfNull( client );

registeredCallbacks = [];

this.client = client;
}

Expand Down Expand Up @@ -141,19 +139,18 @@ public IDisposable Subscribe<TCallback>( Action<TCallback> callbackFunc )
}

void ICallbackMgrInternals.Register( CallbackBase call )
{
if ( registeredCallbacks.Contains( call ) )
return;
=> ImmutableInterlocked.Update( ref registeredCallbacks, static ( list, item ) => list.Add( item ), call );

registeredCallbacks.Add( call );
}
void ICallbackMgrInternals.Unregister( CallbackBase call )
=> ImmutableInterlocked.Update( ref registeredCallbacks, static ( list, item ) => list.Remove( item ), call );

void Handle( ICallbackMsg call )
{
var callbacks = registeredCallbacks;
var type = call.GetType();

// find handlers interested in this callback
foreach ( var callback in registeredCallbacks )
foreach ( var callback in callbacks )
{
if ( callback.CallbackType.IsAssignableFrom( type ) )
{
Expand All @@ -162,11 +159,6 @@ void Handle( ICallbackMsg call )
}
}

void ICallbackMgrInternals.Unregister( CallbackBase call )
{
registeredCallbacks.Remove( call );
}

sealed class Subscription : IDisposable
{
public Subscription( CallbackBase call, ICallbackMgrInternals manager )
Expand Down
43 changes: 43 additions & 0 deletions SteamKit2/Tests/CallbackManagerFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,49 @@ void action( CallbackForTest cb )
}
}

[Fact]
public void CorrectlyUnsubscribesFromInsideOfCallback()
{
static void nothing( CallbackForTest cb )
{
//
}

using var s1 = mgr.Subscribe<CallbackForTest>( nothing );

IDisposable subscription = null;

void unsubscribe( CallbackForTest cb )
{
Assert.NotNull( subscription );
subscription.Dispose();
subscription = null;
}

subscription = mgr.Subscribe<CallbackForTest>( unsubscribe );

PostAndRunCallback( new CallbackForTest { UniqueID = Guid.NewGuid() } );
}

[Fact]
public void CorrectlysubscribesFromInsideOfCallback()
{
static void nothing( CallbackForTest cb )
{
//
}

void subscribe( CallbackForTest cb )
{
using var s2 = mgr.Subscribe<CallbackForTest>( nothing );
}

using var s1 = mgr.Subscribe<CallbackForTest>( nothing );
using var se = mgr.Subscribe<CallbackForTest>( subscribe );

PostAndRunCallback( new CallbackForTest { UniqueID = Guid.NewGuid() } );
}

void PostAndRunCallback(CallbackMsg callback)
{
client.PostCallback(callback);
Expand Down

0 comments on commit 223468c

Please sign in to comment.