Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve plugins #1312

Merged
merged 6 commits into from
Dec 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/neo/Consensus/ConsensusService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ private void InitializeConsensus(byte viewNumber)

private void Log(string message, LogLevel level = LogLevel.Info)
{
Plugin.Log(nameof(ConsensusService), level, message);
Utility.Log(nameof(ConsensusService), level, message);
}

private void OnChangeViewReceived(ConsensusPayload payload, ChangeView message)
Expand Down
2 changes: 1 addition & 1 deletion src/neo/Plugins/LogLevel.cs → src/neo/LogLevel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Neo.Plugins
namespace Neo
{
public enum LogLevel : byte
{
Expand Down
3 changes: 2 additions & 1 deletion src/neo/NeoSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public NeoSystem(string storageEngine = null)
this.Blockchain = ActorSystem.ActorOf(Ledger.Blockchain.Props(this, store));
this.LocalNode = ActorSystem.ActorOf(Network.P2P.LocalNode.Props(this));
this.TaskManager = ActorSystem.ActorOf(Network.P2P.TaskManager.Props(this));
Plugin.NotifyPluginsLoadedAfterSystemConstructed();
foreach (var plugin in Plugin.Plugins)
plugin.OnPluginsLoaded();
}

public void Dispose()
Expand Down
4 changes: 2 additions & 2 deletions src/neo/Plugins/IP2PPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Neo.Plugins
{
public interface IP2PPlugin
{
bool OnP2PMessage(Message message);
bool OnConsensusMessage(ConsensusPayload payload);
bool OnP2PMessage(Message message) => true;
bool OnConsensusMessage(ConsensusPayload payload) => true;
}
}
6 changes: 3 additions & 3 deletions src/neo/Plugins/IPersistencePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ namespace Neo.Plugins
{
public interface IPersistencePlugin
{
void OnPersist(StoreView snapshot, IReadOnlyList<ApplicationExecuted> applicationExecutedList);
void OnCommit(StoreView snapshot);
bool ShouldThrowExceptionFromCommit(Exception ex);
void OnPersist(StoreView snapshot, IReadOnlyList<ApplicationExecuted> applicationExecutedList) { }
void OnCommit(StoreView snapshot) { }
bool ShouldThrowExceptionFromCommit(Exception ex) => false;
}
}
152 changes: 82 additions & 70 deletions src/neo/Plugins/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,35 @@
using System.Linq;
using System.Reflection;
using System.Threading;
using static System.IO.Path;

namespace Neo.Plugins
{
public abstract class Plugin : IDisposable
{
public static readonly List<Plugin> Plugins = new List<Plugin>();
private static readonly List<ILogPlugin> Loggers = new List<ILogPlugin>();
internal static readonly List<ILogPlugin> Loggers = new List<ILogPlugin>();
internal static readonly Dictionary<string, IStoragePlugin> Storages = new Dictionary<string, IStoragePlugin>();
internal static readonly List<IRpcPlugin> RpcPlugins = new List<IRpcPlugin>();
internal static readonly List<IPersistencePlugin> PersistencePlugins = new List<IPersistencePlugin>();
internal static readonly List<IP2PPlugin> P2PPlugins = new List<IP2PPlugin>();
internal static readonly List<IMemoryPoolTxObserverPlugin> TxObserverPlugins = new List<IMemoryPoolTxObserverPlugin>();

private static readonly string pluginsPath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Plugins");
public static readonly string PluginsDirectory = Combine(GetDirectoryName(Assembly.GetEntryAssembly().Location), "Plugins");
private static readonly FileSystemWatcher configWatcher;

private static int suspend = 0;

protected static NeoSystem System { get; private set; }
public virtual string ConfigFile => Combine(PluginsDirectory, GetType().Assembly.GetName().Name, "config.json");
public virtual string Name => GetType().Name;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we really make this virtual? Aren't we going to end in a similar situation described here neo-project/neo-node#452?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But Erik, if I understand it correctly, you can have a name for the plugin and a different name for the dll. You are using it as default, but you are allowing people to change it (because of virtual).
I will test to see what happens.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The answer is nothing happened. The Name is just a name.

public string Path => Combine(PluginsDirectory, GetType().Assembly.ManifestModule.ScopeName);
protected static NeoSystem System { get; private set; }
public virtual Version Version => GetType().Assembly.GetName().Version;
public virtual string ConfigFile => Path.Combine(pluginsPath, GetType().Assembly.GetName().Name, "config.json");

static Plugin()
{
if (Directory.Exists(pluginsPath))
if (Directory.Exists(PluginsDirectory))
{
configWatcher = new FileSystemWatcher(pluginsPath, "*.json")
configWatcher = new FileSystemWatcher(PluginsDirectory)
{
EnableRaisingEvents = true,
IncludeSubdirectories = true,
Expand All @@ -58,74 +59,112 @@ protected Plugin()
Configure();
}

public abstract void Configure();

protected virtual void OnPluginsLoaded()
protected virtual void Configure()
{
}

private static void ConfigWatcher_Changed(object sender, FileSystemEventArgs e)
{
foreach (var plugin in Plugins)
switch (GetExtension(e.Name))
{
if (plugin.ConfigFile == e.FullPath)
{
plugin.Configure();
plugin.Log($"Reloaded config for {plugin.Name}");
case ".json":
Plugins.FirstOrDefault(p => p.ConfigFile == e.FullPath)?.Configure();
break;
case ".dll":
if (e.ChangeType != WatcherChangeTypes.Created) return;
if (GetDirectoryName(e.FullPath) != PluginsDirectory) return;
try
{
LoadPlugin(Assembly.Load(File.ReadAllBytes(e.FullPath)));
}
catch { }
break;
}
}
}

private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.Contains(".resources"))
return null;

Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
if (assembly != null)
return assembly;

AssemblyName an = new AssemblyName(args.Name);
string filename = an.Name + ".dll";

try
{
return Assembly.Load(File.ReadAllBytes(filename));
}
catch (Exception ex)
{
Utility.Log(nameof(Plugin), LogLevel.Error, $"Failed to resolve assembly or its dependency: {ex.Message}");
return null;
}
}

public virtual void Dispose()
{
}

protected IConfigurationSection GetConfiguration()
{
return new ConfigurationBuilder().AddJsonFile(ConfigFile, optional: true).Build().GetSection("PluginConfiguration");
}

internal static void LoadPlugins(NeoSystem system)
private static void LoadPlugin(Assembly assembly)
{
System = system;
if (!Directory.Exists(pluginsPath)) return;
foreach (string filename in Directory.EnumerateFiles(pluginsPath, "*.dll", SearchOption.TopDirectoryOnly))
foreach (Type type in assembly.ExportedTypes)
{
var file = File.ReadAllBytes(filename);
Assembly assembly = Assembly.Load(file);
foreach (Type type in assembly.ExportedTypes)
{
if (!type.IsSubclassOf(typeof(Plugin))) continue;
if (type.IsAbstract) continue;
if (!type.IsSubclassOf(typeof(Plugin))) continue;
if (type.IsAbstract) continue;

ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
try
{
constructor?.Invoke(null);
}
catch (Exception ex)
{
Log(nameof(Plugin), LogLevel.Error, $"Failed to initialize plugin: {ex.Message}");
}
ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
try
{
constructor?.Invoke(null);
}
catch (Exception ex)
{
Utility.Log(nameof(Plugin), LogLevel.Error, $"Failed to initialize plugin: {ex.Message}");
}
}
}

internal static void NotifyPluginsLoadedAfterSystemConstructed()
internal static void LoadPlugins(NeoSystem system)
{
foreach (var plugin in Plugins)
plugin.OnPluginsLoaded();
System = system;
if (!Directory.Exists(PluginsDirectory)) return;
List<Assembly> assemblies = new List<Assembly>();
foreach (string filename in Directory.EnumerateFiles(PluginsDirectory, "*.dll", SearchOption.TopDirectoryOnly))
{
try
{
assemblies.Add(Assembly.Load(File.ReadAllBytes(filename)));
}
catch { }
}
foreach (Assembly assembly in assemblies)
{
LoadPlugin(assembly);
}
}

protected void Log(string message, LogLevel level = LogLevel.Info)
{
Log($"{nameof(Plugin)}:{Name}", level, message);
Utility.Log($"{nameof(Plugin)}:{Name}", level, message);
}

public static void Log(string source, LogLevel level, string message)
protected virtual bool OnMessage(object message)
{
foreach (ILogPlugin plugin in Loggers)
plugin.Log(source, level, message);
return false;
}

protected virtual bool OnMessage(object message) => false;
internal protected virtual void OnPluginsLoaded()
{
}

protected static bool ResumeNodeStartup()
{
Expand All @@ -148,32 +187,5 @@ protected static void SuspendNodeStartup()
Interlocked.Increment(ref suspend);
System.SuspendNodeStartup();
}

private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.Contains(".resources"))
return null;

Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
if (assembly != null)
return assembly;

AssemblyName an = new AssemblyName(args.Name);
string filename = an.Name + ".dll";

try
{
return Assembly.LoadFrom(filename);
}
catch (Exception ex)
{
Log(nameof(Plugin), LogLevel.Error, $"Failed to resolve assembly or its dependency: {ex.Message}");
return null;
}
}

public virtual void Dispose()
{
}
}
}
7 changes: 7 additions & 0 deletions src/neo/Utility.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Extensions.Configuration;
using Neo.Cryptography.ECC;
using Neo.Plugins;
using Neo.SmartContract;
using Neo.Wallets;
using System;
Expand Down Expand Up @@ -72,5 +73,11 @@ public static IConfigurationRoot LoadConfig(string config)
.AddJsonFile(configFile, true)
.Build();
}

public static void Log(string source, LogLevel level, string message)
{
foreach (ILogPlugin plugin in Plugin.Loggers)
plugin.Log(source, level, message);
}
}
}
2 changes: 1 addition & 1 deletion tests/neo.UnitTests/Ledger/UT_MemoryPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Neo.UnitTests.Ledger
{
internal class TestIMemoryPoolTxObserverPlugin : Plugin, IMemoryPoolTxObserverPlugin
{
public override void Configure() { }
protected override void Configure() { }
public void TransactionAdded(Transaction tx) { }
public void TransactionsRemoved(MemoryPoolTxRemovalReason reason, IEnumerable<Transaction> transactions) { }
}
Expand Down
4 changes: 2 additions & 2 deletions tests/neo.UnitTests/Plugins/TestLogPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ public TestLogPlugin() : base() { }

public string Output { set; get; }

public override void Configure() { }
protected override void Configure() { }

public new void Log(string source, LogLevel level, string message)
void ILogPlugin.Log(string source, LogLevel level, string message)
{
Output = source + "_" + level.ToString() + "_" + message;
}
Expand Down
8 changes: 0 additions & 8 deletions tests/neo.UnitTests/Plugins/UT_Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,6 @@ public void TestSendMessage()
}
}

[TestMethod]
public void TestNotifyPluginsLoadedAfterSystemConstructed()
{
var pp = new TestLogPlugin();
Action action = () => Plugin.NotifyPluginsLoadedAfterSystemConstructed();
action.Should().NotThrow();
}

[TestMethod]
public void TestResumeNodeStartupAndSuspendNodeStartup()
{
Expand Down