Skip to content
This repository has been archived by the owner on Dec 18, 2017. It is now read-only.

Commit

Permalink
Add DTH Plugin Registration recovery.
Browse files Browse the repository at this point in the history
- Also modified all plugin activities to no longer throw but to send down error messages
- Separated message handling and message processing.
- Added faulted registration recovery bits.

#1405
  • Loading branch information
NTaylorMullen committed Mar 15, 2015
1 parent 6f17d21 commit a5cfb19
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 49 deletions.
59 changes: 47 additions & 12 deletions src/Microsoft.Framework.DesignTimeHost/ApplicationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class ApplicationContext

private readonly Trigger<string> _appPath = new Trigger<string>();
private readonly Trigger<string> _configuration = new Trigger<string>();
private readonly Trigger<Void> _pluginRegistration = new Trigger<Void>();
private readonly Trigger<Void> _pluginWorkNeeded = new Trigger<Void>();
private readonly Trigger<Void> _filesChanged = new Trigger<Void>();
private readonly Trigger<Void> _rebuild = new Trigger<Void>();
private readonly Trigger<Void> _restoreComplete = new Trigger<Void>();
Expand Down Expand Up @@ -169,6 +171,11 @@ public void DoProcessLoop()
SendOutgoingMessages();
}

if (PerformPluginWork())
{
SendOutgoingMessages();
}

lock (_inbox)
{
// If there's no more messages queued then bail out.
Expand Down Expand Up @@ -304,20 +311,19 @@ private bool ProcessMessage()
break;
case "Plugin":
{
var pluginData = message.Payload.ToObject<PluginMessage>();
var pluginMessage = message.Payload.ToObject<PluginMessage>();

Project project;
if (!Project.TryGetProject(_appPath.Value, out project))
var result = _pluginHandler.OnReceive(pluginMessage);

switch (result)
{
throw new InvalidOperationException(
Resources.FormatPlugin_UnableToFindProjectJson(_appPath.Value));
case PluginMessageResult.PluginRegistration:
_pluginRegistration.Value = default(Void);
break;
case PluginMessageResult.Message:
_pluginWorkNeeded.Value = default(Void);
break;
}

var loadContextFactory = GetRuntimeLoadContextFactory(project);

var assemblyLoadContext = loadContextFactory.Create();

_pluginHandler.ProcessMessage(pluginData, assemblyLoadContext);
}
break;
}
Expand All @@ -334,7 +340,8 @@ private bool ResolveDependencies()
_filesChanged.WasAssigned ||
_rebuild.WasAssigned ||
_restoreComplete.WasAssigned ||
_sourceTextChanged.WasAssigned)
_sourceTextChanged.WasAssigned ||
_pluginRegistration.WasAssigned)
{
bool triggerBuildOutputs = _rebuild.WasAssigned || _filesChanged.WasAssigned;
bool triggerDependencies = _restoreComplete.WasAssigned || _rebuild.WasAssigned;
Expand Down Expand Up @@ -490,6 +497,34 @@ private bool PerformCompilation()
return true;
}

private bool PerformPluginWork()
{
if (_pluginRegistration.WasAssigned ||
_pluginWorkNeeded.WasAssigned ||
_filesChanged.WasAssigned ||
_rebuild.WasAssigned ||
_restoreComplete.WasAssigned ||
_sourceTextChanged.WasAssigned)
{
Project project;
if (!Project.TryGetProject(_appPath.Value, out project))
{
throw new InvalidOperationException(
Resources.FormatPlugin_UnableToFindProjectJson(_appPath.Value));
}

var loadContextFactory = GetRuntimeLoadContextFactory(project);

var assemblyLoadContext = loadContextFactory.Create();

_pluginHandler.Process(assemblyLoadContext);

return true;
}

return false;
}

private bool UpdateProjectCompilation(ProjectWorld project, out ProjectCompilation compilation)
{
var export = project.ApplicationHostContext.LibraryManager.GetLibraryExport(_local.ProjectInformation.Name);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Microsoft.Framework.DesignTimeHost.Models.OutgoingMessages
{
public class PluginResponseMessage
{
public string MessageName { get; set; }
public bool Success { get; set; }
public string Error { get; set; }
}
}
191 changes: 154 additions & 37 deletions src/Microsoft.Framework.DesignTimeHost/PluginHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,77 +3,170 @@

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Framework.DesignTimeHost.Models.IncomingMessages;
using Microsoft.Framework.DesignTimeHost.Models.OutgoingMessages;
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Runtime.Common.DependencyInjection;

namespace Microsoft.Framework.DesignTimeHost
{
public class PluginHandler
{
private const string RegisterPluginMessageName = "RegisterPlugin";
private const string UnregisterPluginMessageName = "UnregisterPlugin";
private const string PluginMessageMessageName = "PluginMessage";

private readonly object _processLock = new object();
private readonly Action<object> _sendMessageMethod;
private readonly IServiceProvider _hostServices;
private readonly ConcurrentDictionary<string, IPlugin> _plugins;
private readonly Queue<PluginMessage> _faultedRegisterPluginMessages;
private readonly ConcurrentQueue<PluginMessage> _messageQueue;
private readonly IDictionary<string, IPlugin> _plugins;

public PluginHandler(IServiceProvider hostServices, Action<object> sendMessageMethod)
{
_sendMessageMethod = sendMessageMethod;
_hostServices = hostServices;
_plugins = new ConcurrentDictionary<string, IPlugin>(StringComparer.Ordinal);
_messageQueue = new ConcurrentQueue<PluginMessage>();
_faultedRegisterPluginMessages = new Queue<PluginMessage>();
_plugins = new Dictionary<string, IPlugin>(StringComparer.Ordinal);
}

public void ProcessMessage(PluginMessage data, IAssemblyLoadContext assemblyLoadContext)
public PluginMessageResult OnReceive(PluginMessage message)
{
switch (data.MessageName)
var result = PluginMessageResult.Message;

if (message.MessageName == RegisterPluginMessageName)
{
case "RegisterPlugin":
ProcessRegisterMessage(data, assemblyLoadContext);
break;
case "UnregisterPlugin":
ProcessUnregisterMessage(data);
break;
case "PluginMessage":
ProcessPluginMessage(data);
break;
result = PluginMessageResult.PluginRegistration;
}

_messageQueue.Enqueue(message);

return result;
}

private void ProcessPluginMessage(PluginMessage data)
public void Process(IAssemblyLoadContext assemblyLoadContext)
{
IPlugin plugin;
if (_plugins.TryGetValue(data.PluginId, out plugin))
lock (_processLock)
{
plugin.ProcessMessage(data.Data);
TryRegisterFaultedPlugins(assemblyLoadContext);
DrainMessages(assemblyLoadContext);
}
else
}

private void TryRegisterFaultedPlugins(IAssemblyLoadContext assemblyLoadContext)
{
// Capture count here so when we enqueue later on we don't result in an infinite loop below.
var faultedCount = _faultedRegisterPluginMessages.Count;

for (var i = faultedCount; i > 0; i--)
{
throw new InvalidOperationException(
Resources.FormatPlugin_UnregisteredPluginIdCannotReceiveMessages(data.PluginId));
var faultedRegisterPluginMessage = _faultedRegisterPluginMessages.Dequeue();
var response = RegisterPlugin(faultedRegisterPluginMessage, assemblyLoadContext);

if (response.Success)
{
SendMessage(faultedRegisterPluginMessage.PluginId, response);
}
else
{
// We were unable to recover, re-enqueue the faulted register plugin message.
_faultedRegisterPluginMessages.Enqueue(faultedRegisterPluginMessage);
}
}
}

private void ProcessUnregisterMessage(PluginMessage data)
private void DrainMessages(IAssemblyLoadContext assemblyLoadContext)
{
IPlugin removedPlugin;
if (!_plugins.TryRemove(data.PluginId, out removedPlugin))
PluginMessage message;
while (_messageQueue.TryDequeue(out message))
{
throw new InvalidOperationException(
Resources.FormatPlugin_UnregisteredPluginIdCannotUnregister(data.PluginId));
switch (message.MessageName)
{
case RegisterPluginMessageName:
RegisterMessage(message, assemblyLoadContext);
break;
case UnregisterPluginMessageName:
UnregisterMessage(message);
break;
case PluginMessageMessageName:
PluginMessage(message);
break;
}
}
}

private void RegisterMessage(PluginMessage message, IAssemblyLoadContext assemblyLoadContext)
{
var response = RegisterPlugin(message, assemblyLoadContext);

if (!response.Success)
{
_faultedRegisterPluginMessages.Enqueue(message);
}

SendMessage(message.PluginId, response);
}

private void ProcessRegisterMessage(PluginMessage data, IAssemblyLoadContext assemblyLoadContext)
private void UnregisterMessage(PluginMessage message)
{
var pluginId = data.PluginId;
var registerData = data.Data.ToObject<PluginRegisterData>();
var assembly = assemblyLoadContext.Load(registerData.AssemblyName);
var pluginType = assembly.GetType(registerData.TypeName);
if (!_plugins.Remove(message.PluginId))
{
OnError(
message.PluginId,
UnregisterPluginMessageName,
errorMessage: Resources.FormatPlugin_UnregisteredPluginIdCannotUnregister(message.PluginId));
}
}

private void PluginMessage(PluginMessage message)
{
IPlugin plugin;
if (_plugins.TryGetValue(message.PluginId, out plugin))
{
plugin.ProcessMessage(message.Data);
}
else
{
OnError(
message.PluginId,
PluginMessageMessageName,
errorMessage: Resources.FormatPlugin_UnregisteredPluginIdCannotReceiveMessages(message.PluginId));
}
}

private PluginResponseMessage RegisterPlugin(
PluginMessage message,
IAssemblyLoadContext assemblyLoadContext)
{
var registerData = message.Data.ToObject<PluginRegisterData>();
var response = new PluginResponseMessage
{
MessageName = RegisterPluginMessageName
};

Type pluginType = null;
try
{
var assembly = assemblyLoadContext.Load(registerData.AssemblyName);
pluginType = assembly.GetType(registerData.TypeName);
}
catch (Exception exception)
{
response.Error = exception.Message;

return response;
}

if (pluginType != null)
{
// We build out a custom plugin service provider to add a PluginMessageBroker to the potential
// services that can be used to construct an IPlugin.
var pluginId = message.PluginId;

// We build out a custom plugin service provider to add a PluginMessageBroker and
// IAssemblyLoadContext to the potential services that can be used to construct an IPlugin.
var pluginServiceProvider = new PluginServiceProvider(
_hostServices,
assemblyLoadContext,
Expand All @@ -83,15 +176,39 @@ private void ProcessRegisterMessage(PluginMessage data, IAssemblyLoadContext ass

if (plugin == null)
{
throw new InvalidOperationException(
Resources.FormatPlugin_CannotProcessMessageInvalidPluginType(
pluginId,
pluginType.FullName,
typeof(IPlugin).FullName));
response.Error = Resources.FormatPlugin_CannotProcessMessageInvalidPluginType(
pluginId,
pluginType.FullName,
typeof(IPlugin).FullName);

return response;
}

_plugins[pluginId] = plugin;
}

response.Success = true;

return response;
}

private void SendMessage(string pluginId, PluginResponseMessage message)
{
var messageBroker = new PluginMessageBroker(pluginId, _sendMessageMethod);

messageBroker.SendMessage(message);
}

private void OnError(string pluginId, string messageName, string errorMessage)
{
SendMessage(
pluginId,
message: new PluginResponseMessage
{
MessageName = messageName,
Error = errorMessage,
Success = false
});
}

private class PluginServiceProvider : IServiceProvider
Expand Down
10 changes: 10 additions & 0 deletions src/Microsoft.Framework.DesignTimeHost/PluginMessageResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Microsoft.Framework.DesignTimeHost
{
public enum PluginMessageResult
{
Message,
PluginRegistration,
}
}

0 comments on commit a5cfb19

Please sign in to comment.