Skip to content

Commit

Permalink
Merge pull request #116 from WalletConnect/feat/dispose-modules
Browse files Browse the repository at this point in the history
Allow modules to be disposed
  • Loading branch information
gigajuwels authored Sep 12, 2023
2 parents 96ecf65 + 5866dd4 commit d9404ac
Show file tree
Hide file tree
Showing 30 changed files with 443 additions and 78 deletions.
4 changes: 2 additions & 2 deletions Core Modules/WalletConnectSharp.Common/IModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace WalletConnectSharp.Common
/// <summary>
/// An interface that represents a module
/// </summary>
public interface IModule /* : IDisposable */
public interface IModule : IDisposable
{
/// <summary>
/// The name of this module
Expand All @@ -18,4 +18,4 @@ public interface IModule /* : IDisposable */
/// </summary>
string Context { get; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@ public IsolatedModule()

activeModules.Add(_guid);
}

public void Dispose() { }
}
}
}
5 changes: 5 additions & 0 deletions Core Modules/WalletConnectSharp.Crypto/Crypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -684,5 +684,10 @@ private async Task<byte[]> GetClientSeed()

return seed.HexToByteArray();
}

public void Dispose()
{
KeyChain?.Dispose();
}
}
}
10 changes: 6 additions & 4 deletions Core Modules/WalletConnectSharp.Crypto/KeyChain.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using WalletConnectSharp.Common;
using WalletConnectSharp.Common.Model.Errors;
using WalletConnectSharp.Common.Utils;
using WalletConnectSharp.Crypto.Interfaces;
Expand Down Expand Up @@ -199,5 +195,11 @@ private async Task SaveKeyChain()
{
await Storage.SetItem(StorageKey, this._keyChain);
}

public void Dispose()
{
_keyChain?.Clear();
Storage?.Dispose();
}
}
}
56 changes: 51 additions & 5 deletions Core Modules/WalletConnectSharp.Events/EventDelegator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace WalletConnectSharp.Events
/// </summary>
public class EventDelegator : IModule
{
private static readonly object _contextLock = new object();
private static HashSet<string> contextInstances = new HashSet<string>();

private readonly object _cacheLock = new object();
Expand Down Expand Up @@ -47,11 +48,14 @@ public EventDelegator(IModule parent)
this.Name = parent + ":event-delegator";
this.Context = parent.Context;

if (contextInstances.Contains(Context))
throw new ArgumentException(
$"The module {parent.Name} with context {Context} is attempting to create a new EventDelegator that overlaps with an existing EventDelegator");

contextInstances.Add(Context);
lock (_contextLock)
{
if (contextInstances.Contains(Context))
throw new ArgumentException(
$"The module {parent.Name} with context {Context} is attempting to create a new EventDelegator that overlaps with an existing EventDelegator");

contextInstances.Add(Context);
}
}

/// <summary>
Expand Down Expand Up @@ -243,5 +247,47 @@ from type in assembly.GetTypes()

return wasTriggered;
}

public void Dispose()
{
lock (_cacheLock)
{
// loop through all cached types and remove all listeners
foreach (var eventType in _typeToTriggerTypes.Keys)
{
var allPossibleTypes = _typeToTriggerTypes[eventType];

// loop through all possible types of an event type, since
// event listeners can be anywhere
foreach (var type in allPossibleTypes)
{
var genericFactoryType = typeof(EventFactory<>).MakeGenericType(type);

var instanceProperty = genericFactoryType.GetMethod("InstanceOf");
if (instanceProperty == null) continue;

var genericFactory = instanceProperty.Invoke(null, new object[] { Context });

var genericProviderProperty = genericFactoryType.GetProperty("Provider");
if (genericProviderProperty == null) continue;

var genericProvider = genericProviderProperty.GetValue(genericFactory);
if (genericProvider == null) continue;

// type of IEventProvider is IDisposable
IDisposable disposable = (IDisposable)genericProvider;

// dispose of this provider
disposable.Dispose();
}
}
}

lock (_contextLock)
{
if (contextInstances.Contains(Context))
contextInstances.Remove(Context);
}
}
}
}
10 changes: 9 additions & 1 deletion Core Modules/WalletConnectSharp.Events/EventHandlerMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace WalletConnectSharp.Events
/// A mapping of eventIds to EventHandler objects. This using a Dictionary as the backing datastore
/// </summary>
/// <typeparam name="TEventArgs">The type of EventHandler's argument to store</typeparam>
public class EventHandlerMap<TEventArgs>
public class EventHandlerMap<TEventArgs> : IDisposable
{
private Dictionary<string, EventHandler<TEventArgs>> mapping =
new Dictionary<string, EventHandler<TEventArgs>>();
Expand Down Expand Up @@ -94,5 +94,13 @@ public void Clear(string eventId)
}
}
}

public void Dispose()
{
lock (_mappingLock)
{
mapping.Clear();
}
}
}
}
10 changes: 10 additions & 0 deletions Core Modules/WalletConnectSharp.Events/EventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,15 @@ public void PropagateEvent(string eventId, T eventData)
}
}
}

public void Dispose()
{
EventTriggers.Dispose();
lock (_managerLock)
{
if (_instances.ContainsKey(Context))
_instances.Remove(Context);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace WalletConnectSharp.Events
/// provider can trigger any event for a given event data type.
/// </summary>
/// <typeparam name="T">The event data type this class triggers events with</typeparam>
public interface IEventProvider<in T>
public interface IEventProvider<in T> : IDisposable
{
/// <summary>
/// Trigger the given event (using the event id) with the given event data.
Expand All @@ -14,4 +14,4 @@ public interface IEventProvider<in T>
/// <param name="eventData">The event data to trigger the event with</param>
void PropagateEvent(string eventId, T eventData);
}
}
}
6 changes: 6 additions & 0 deletions Core Modules/WalletConnectSharp.Network/JsonRpcProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,5 +304,11 @@ private void OnPayload(object sender, GenericEvent<string> e)
}
}
}

public void Dispose()
{
_connection?.Dispose();
_delegator?.Dispose();
}
}
}
7 changes: 6 additions & 1 deletion Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,10 @@ protected void IsInitialized()
throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, "Storage");
}
}

public void Dispose()
{
Entries?.Clear();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace WalletConnectSharp.Storage.Interfaces
/// <summary>
/// The storage module handles the storing by key value pairing. All of the functions are asynchronous
/// </summary>
public interface IKeyValueStorage
public interface IKeyValueStorage : IDisposable
{
/// <summary>
/// Initialize the storage. This should load any data or prepare any connection required by the
Expand Down Expand Up @@ -65,4 +65,4 @@ public interface IKeyValueStorage
/// <returns></returns>
Task Clear();
}
}
}
85 changes: 85 additions & 0 deletions Tests/WalletConnectSharp.Events.Tests/EventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using WalletConnectSharp.Common;
using WalletConnectSharp.Common.Model;
using WalletConnectSharp.Events.Model;
using Xunit;
Expand Down Expand Up @@ -173,5 +174,89 @@ public void ListenAndDeserializeJson()
Assert.Equal(result1.test1, testData1.test1);
Assert.Equal(result1.test2, testData1.test2);
}

public class TestModule : IModule
{
public void Dispose()
{
// TODO release managed resources here
}

public string Name
{
get
{
return "test";
}
}

public string Context
{
get
{
return $"{Name}-context";
}
}
}

[Fact, Trait("Category", "unit")]
public void TestContextSharingWithoutDisposingFails()
{
var module = new TestModule();
var events = new EventDelegator(module);

Assert.Equal(module.Context, events.Context);

Assert.Throws<ArgumentException>(() => new EventDelegator(module));

events.Dispose();

events = new EventDelegator(module);

Assert.Equal(module.Context, events.Context);

events.Dispose();
}

[Fact, Trait("Category", "unit")]
public void TestEventsDontLeakWhenDisposed()
{
var module = new TestModule();
var events = new EventDelegator(module);

Assert.Equal(module.Context, events.Context);

Assert.Throws<ArgumentException>(() => new EventDelegator(module));

TestEventData result1 = null;

events.ListenForAndDeserialize<TestEventData>("abc", delegate(object sender, GenericEvent<TestEventData> @event)
{
result1 = @event.EventData;
});

var testData1 = new TestEventData()
{
test1 = 11,
test2 = "abccc"
};

var json = JsonConvert.SerializeObject(testData1);

events.Trigger("abc", json);

Assert.Equal(result1.test1, testData1.test1);
Assert.Equal(result1.test2, testData1.test2);

events.Dispose();
result1 = null;

events = new EventDelegator(module);
events.Trigger("abc", json);

Assert.Null(result1);

events.Dispose();
}
}
}
Loading

0 comments on commit d9404ac

Please sign in to comment.