Merge pull request #173 from PepperDash/feature/fix-devMan-threading

Update DeviceManager to provide protections for multiple threads or reentrancy
This commit is contained in:
Andrew Welker
2020-05-12 10:10:51 -06:00
committed by GitHub
2 changed files with 176 additions and 134 deletions

View File

@@ -26,6 +26,8 @@ namespace PepperDash.Essentials
{ {
HttpLogoServer LogoServer; HttpLogoServer LogoServer;
private CTimer _startTimer;
private const long StartupTime = 500;
public ControlSystem() public ControlSystem()
: base() : base()
@@ -39,6 +41,11 @@ namespace PepperDash.Essentials
/// Entry point for the program /// Entry point for the program
/// </summary> /// </summary>
public override void InitializeSystem() public override void InitializeSystem()
{
_startTimer = new CTimer(StartSystem,StartupTime);
}
private void StartSystem(object obj)
{ {
DeterminePlatform(); DeterminePlatform();
@@ -75,11 +82,11 @@ namespace PepperDash.Essentials
}, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator); }, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.AddNewConsoleCommand(s =>
{ {
CrestronConsole.ConsoleCommandResponse("This system can be found at the following URLs:\r" + CrestronConsole.ConsoleCommandResponse("This system can be found at the following URLs:\r" +
"System URL: {0}\r" + "System URL: {0}\r" +
"Template URL: {1}", ConfigReader.ConfigObject.SystemUrl, ConfigReader.ConfigObject.TemplateUrl); "Template URL: {1}", ConfigReader.ConfigObject.SystemUrl, ConfigReader.ConfigObject.TemplateUrl);
}, "portalinfo", "Shows portal URLS from configuration", ConsoleAccessLevelEnum.AccessOperator); }, "portalinfo", "Shows portal URLS from configuration", ConsoleAccessLevelEnum.AccessOperator);
if (!Debug.DoNotLoadOnNextBoot) if (!Debug.DoNotLoadOnNextBoot)

View File

@@ -5,10 +5,6 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharpPro.EthernetCommunication;
using Crestron.SimplSharpPro.UI;
using Crestron.SimplSharp.Reflection;
using PepperDash.Core; using PepperDash.Core;
@@ -17,18 +13,23 @@ namespace PepperDash.Essentials.Core
{ {
public static class DeviceManager public static class DeviceManager
{ {
private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection();
private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true);
//public static List<Device> Devices { get { return _Devices; } } //public static List<Device> Devices { get { return _Devices; } }
//static List<Device> _Devices = new List<Device>(); //static List<Device> _Devices = new List<Device>();
static Dictionary<string, IKeyed> Devices = new Dictionary<string, IKeyed>(StringComparer.OrdinalIgnoreCase); static readonly Dictionary<string, IKeyed> Devices = new Dictionary<string, IKeyed>(StringComparer.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// Returns a copy of all the devices in a list /// Returns a copy of all the devices in a list
/// </summary> /// </summary>
public static List<IKeyed> AllDevices { get { return new List<IKeyed>(Devices.Values); } } public static List<IKeyed> AllDevices { get { return new List<IKeyed>(Devices.Values); } }
public static bool AddDeviceEnabled;
public static void Initialize(CrestronControlSystem cs) public static void Initialize(CrestronControlSystem cs)
{ {
AddDeviceEnabled = true;
CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices", CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices",
ConsoleAccessLevelEnum.AccessOperator); ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks", CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks",
@@ -37,18 +38,9 @@ namespace PepperDash.Essentials.Core
ConsoleAccessLevelEnum.AccessOperator); ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "", CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "",
ConsoleAccessLevelEnum.AccessOperator); ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
{ CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)); CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
}, "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s =>
{
CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s));
}, "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s =>
{
CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s));
}, "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive", CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive",
"Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator); "Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator);
} }
@@ -58,47 +50,56 @@ namespace PepperDash.Essentials.Core
/// </summary> /// </summary>
public static void ActivateAll() public static void ActivateAll()
{ {
// PreActivate all devices try
foreach (var d in Devices.Values) {
{ DeviceCriticalSection.Enter();
try AddDeviceEnabled = false;
{ // PreActivate all devices
if (d is Device) foreach (var d in Devices.Values)
(d as Device).PreActivate(); {
} try
catch (Exception e) {
{ if (d is Device)
Debug.Console(0, d, "ERROR: Device PreActivation failure:\r{0}", e); (d as Device).PreActivate();
} }
} catch (Exception e)
{
Debug.Console(0, d, "ERROR: Device PreActivation failure:\r{0}", e);
}
}
// Activate all devices // Activate all devices
foreach (var d in Devices.Values) foreach (var d in Devices.Values)
{ {
try try
{ {
if (d is Device) if (d is Device)
(d as Device).Activate(); (d as Device).Activate();
} }
catch (Exception e) catch (Exception e)
{ {
Debug.Console(0, d, "ERROR: Device Activation failure:\r{0}", e); Debug.Console(0, d, "ERROR: Device Activation failure:\r{0}", e);
} }
} }
// PostActivate all devices // PostActivate all devices
foreach (var d in Devices.Values) foreach (var d in Devices.Values)
{ {
try try
{ {
if (d is Device) if (d is Device)
(d as Device).PostActivate(); (d as Device).PostActivate();
} }
catch (Exception e) catch (Exception e)
{ {
Debug.Console(0, d, "ERROR: Device PostActivation failure:\r{0}", e); Debug.Console(0, d, "ERROR: Device PostActivation failure:\r{0}", e);
} }
} }
}
finally
{
DeviceCriticalSection.Leave();
}
} }
/// <summary> /// <summary>
@@ -106,11 +107,18 @@ namespace PepperDash.Essentials.Core
/// </summary> /// </summary>
public static void DeactivateAll() public static void DeactivateAll()
{ {
foreach (var d in Devices.Values) try
{ {
if (d is Device) DeviceCriticalSection.Enter();
(d as Device).Deactivate(); foreach (var d in Devices.Values.OfType<Device>())
} {
d.Deactivate();
}
}
finally
{
DeviceCriticalSection.Leave();
}
} }
//static void ListMethods(string devKey) //static void ListMethods(string devKey)
@@ -134,37 +142,37 @@ namespace PepperDash.Essentials.Core
// } // }
//} //}
static void ListDevices(string s) private static void ListDevices(string s)
{ {
Debug.Console(0, "{0} Devices registered with Device Mangager:",Devices.Count); Debug.Console(0, "{0} Devices registered with Device Manager:", Devices.Count);
var sorted = Devices.Values.ToList(); var sorted = Devices.Values.ToList();
sorted.Sort((a, b) => a.Key.CompareTo(b.Key)); sorted.Sort((a, b) => a.Key.CompareTo(b.Key));
foreach (var d in sorted) foreach (var d in sorted)
{ {
var name = d is IKeyName ? (d as IKeyName).Name : "---"; var name = d is IKeyName ? (d as IKeyName).Name : "---";
Debug.Console(0, " [{0}] {1}", d.Key, name); Debug.Console(0, " [{0}] {1}", d.Key, name);
} }
} }
static void ListDeviceFeedbacks(string devKey) private static void ListDeviceFeedbacks(string devKey)
{ {
var dev = GetDeviceForKey(devKey); var dev = GetDeviceForKey(devKey);
if(dev == null) if (dev == null)
{ {
Debug.Console(0, "Device '{0}' not found", devKey); Debug.Console(0, "Device '{0}' not found", devKey);
return; return;
} }
var statusDev = dev as IHasFeedback; var statusDev = dev as IHasFeedback;
if(statusDev == null) if (statusDev == null)
{ {
Debug.Console(0, "Device '{0}' does not have visible feedbacks", devKey); Debug.Console(0, "Device '{0}' does not have visible feedbacks", devKey);
return; return;
} }
statusDev.DumpFeedbacksToConsole(true); statusDev.DumpFeedbacksToConsole(true);
} }
//static void ListDeviceCommands(string devKey) //static void ListDeviceCommands(string devKey)
//{ //{
// var dev = GetDeviceForKey(devKey); // var dev = GetDeviceForKey(devKey);
// if (dev == null) // if (dev == null)
@@ -175,70 +183,97 @@ namespace PepperDash.Essentials.Core
// Debug.Console(0, "This needs to be reworked. Stay tuned.", devKey); // Debug.Console(0, "This needs to be reworked. Stay tuned.", devKey);
//} //}
static void ListDeviceCommStatuses(string input) private static void ListDeviceCommStatuses(string input)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var dev in Devices.Values) foreach (var dev in Devices.Values.OfType<ICommunicationMonitor>())
{ {
if (dev is ICommunicationMonitor) sb.Append(string.Format("{0}: {1}\r", dev,
sb.Append(string.Format("{0}: {1}\r", dev.Key, (dev as ICommunicationMonitor).CommunicationMonitor.Status)); dev.CommunicationMonitor.Status));
} }
CrestronConsole.ConsoleCommandResponse(sb.ToString()); CrestronConsole.ConsoleCommandResponse(sb.ToString());
} }
//static void DoDeviceCommand(string command) //static void DoDeviceCommand(string command)
//{ //{
// Debug.Console(0, "Not yet implemented. Stay tuned"); // Debug.Console(0, "Not yet implemented. Stay tuned");
//} //}
public static void AddDevice(IKeyed newDev) public static void AddDevice(IKeyed newDev)
{ {
// Check for device with same key try
//var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase)); {
////// If it exists, remove or warn?? if (!DeviceCriticalSection.TryEnter())
//if (existingDevice != null) {
if(Devices.ContainsKey(newDev.Key)) Debug.Console(0, Debug.ErrorLogLevel.Error, "Currently unable to add devices to Device Manager. Please try again");
{ return;
Debug.Console(0, newDev, "WARNING: A device with this key already exists. Not added to manager"); }
return; // Check for device with same key
} //var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase));
Devices.Add(newDev.Key, newDev); ////// If it exists, remove or warn??
//if (!(_Devices.Contains(newDev))) //if (existingDevice != null)
// _Devices.Add(newDev);
if (!AddDeviceEnabled)
{
Debug.Console(0, Debug.ErrorLogLevel.Error, "All devices have been activated. Adding new devices is not allowed.");
return;
}
if (Devices.ContainsKey(newDev.Key))
{
Debug.Console(0, newDev, "WARNING: A device with this key already exists. Not added to manager");
return;
}
Devices.Add(newDev.Key, newDev);
//if (!(_Devices.Contains(newDev)))
// _Devices.Add(newDev);
}
finally
{
DeviceCriticalSection.Leave();
}
} }
public static void RemoveDevice(IKeyed newDev) public static void RemoveDevice(IKeyed newDev)
{ {
if(newDev == null) try
return; {
if (Devices.ContainsKey(newDev.Key)) DeviceCriticalSection.Enter();
Devices.Remove(newDev.Key); if (newDev == null)
//if (_Devices.Contains(newDev)) return;
// _Devices.Remove(newDev); if (Devices.ContainsKey(newDev.Key))
else Devices.Remove(newDev.Key);
Debug.Console(0, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key); //if (_Devices.Contains(newDev))
// _Devices.Remove(newDev);
else
Debug.Console(0, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key);
}
finally
{
DeviceCriticalSection.Leave();
}
} }
public static IEnumerable<string> GetDeviceKeys() public static IEnumerable<string> GetDeviceKeys()
{ {
//return _Devices.Select(d => d.Key).ToList(); //return _Devices.Select(d => d.Key).ToList();
return Devices.Keys; return Devices.Keys;
} }
public static IEnumerable<IKeyed> GetDevices() public static IEnumerable<IKeyed> GetDevices()
{ {
//return _Devices.Select(d => d.Key).ToList(); //return _Devices.Select(d => d.Key).ToList();
return Devices.Values; return Devices.Values;
} }
public static IKeyed GetDeviceForKey(string key) public static IKeyed GetDeviceForKey(string key)
{ {
//return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); //return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (key != null && Devices.ContainsKey(key)) if (key != null && Devices.ContainsKey(key))
return Devices[key]; return Devices[key];
return null; return null;
} }
/// <summary> /// <summary>
@@ -256,7 +291,7 @@ namespace PepperDash.Essentials.Core
} }
//Debug.Console(2, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value); //Debug.Console(2, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value);
ComPortController com = GetDeviceForKey(match.Groups[1].Value) as ComPortController; var com = GetDeviceForKey(match.Groups[1].Value) as ComPortController;
if (com == null) if (com == null)
{ {
CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value); CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value);