// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; using System.Threading.Tasks; using System.IO; using System.Threading; using System.Reflection; namespace Spludlow { public class Service : IService { public class MethodTask { public string Name; public Thread Thread; public DateTime Started; public Spludlow.CallMethod MethodInfo; public Exception LastError; public DateTime LastErrorTime; public object Instance; public MethodInfo StopMethod; public DateTime Parked; } public class ServerTask { public string Name; public DateTime Started; public string Assembly; public string Type; public int Port; public ServiceHost ServiceHost; public Exception LastError; public DateTime LastErrorTime; public DateTime Parked; } private Spludlow.LogFile LogFile; private Dictionary MethodTasks; private Dictionary ServerTasks; private string ServiceKey; public Service() { this.ServiceKey = CurrentServiceKey(); } public static Spludlow.IService MakeClient(string serviceAppKey) { string address = Spludlow.Config.RemotingAddress(serviceAppKey); NetTcpBinding binding = new NetTcpBinding(SecurityMode.None); EndpointAddress endpointAddress = new EndpointAddress(address); ChannelFactory channelFactory = new ChannelFactory(binding, endpointAddress); Spludlow.IService channel = channelFactory.CreateChannel(); return channel; } public static string CurrentServiceKey() { string key = System.AppDomain.CurrentDomain.FriendlyName; key = key.Substring(0, key.Length - 4); if (key == "TestForm") key = "Spludlow-Service"; return key; } public static ServiceHost ServiceStart() { AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException1; string key = CurrentServiceKey(); DateTime start = DateTime.Now; try { ServiceHost serviceHost = Spludlow.ServiceModels.StartServer(key, typeof(Spludlow.Service), false); IService client = MakeClient(key); try { client.Start(); } finally { ServiceModels.Close(client); } if (key == "Spludlow-Service") Spludlow.Diagnostics.HostBootCheck(); Spludlow.Log.Report("Service; Service Process Started: " + key + ", took: " + Spludlow.Text.TimeTook(start)); return serviceHost; } catch (Exception ee) { Spludlow.Log.Error("Service; Service Process Starting: " + key, ee); throw ee; } } private static void CurrentDomain_UnhandledException1(object sender, UnhandledExceptionEventArgs e) { Spludlow.Log.Error("CurrentDomain_UnhandledException", (Exception)e.ExceptionObject); } public static void ServiceStop(ServiceHost serviceHost) { string key = CurrentServiceKey(); DateTime start = DateTime.Now; try { IService client = MakeClient(key); try { client.Stop(); } finally { ServiceModels.Close(client); } serviceHost.Close(); Spludlow.Log.Report("Service; Service Process Stopped: " + key +", took: " + Spludlow.Text.TimeTook(start)); } catch (Exception ee) { Spludlow.Log.Error("Service; Service Process Stopping: " + key, ee); throw ee; } } public static void StartThread(string host, string serviceName, string threadName) { Spludlow.Call.Now(host, "Spludlow", "Spludlow.Service", "StartThreadWork", new object[] { serviceName, threadName }); } public static void StartThreadWork(string serviceName, string threadName) { DateTime start = DateTime.Now; IService client = MakeClient(serviceName); try { client.StartTask(threadName); } finally { ServiceModels.Close(client); } Spludlow.Log.Info("Service; Thead Started, thrad:" + threadName + ", took: " + Spludlow.Text.TimeTook(start)); } public static void StopThread(string host, string serviceName, string threadName) { Spludlow.Call.Now(host, "Spludlow", "Spludlow.Service", "StopThreadWork", new object[] { serviceName, threadName }); } public static void StopThreadWork(string serviceName, string threadName) { DateTime start = DateTime.Now; IService client = MakeClient(serviceName); try { client.StopTask(threadName); } finally { ServiceModels.Close(client); } Spludlow.Log.Info("Service; Thead Stopped, thrad:" + threadName + ", took: " + Spludlow.Text.TimeTook(start)); } public static void ParkTask(string host, string serviceName, string threadName) { Spludlow.Call.Now(host, "Spludlow", "Spludlow.Service", "ParkTaskWork", new object[] { serviceName, threadName }); } public static void ParkTaskWork(string serviceName, string threadName) { IService client = MakeClient(serviceName); try { client.ParkTask(threadName); } finally { ServiceModels.Close(client); } Spludlow.Log.Info("Service; Thead Parked:" + threadName); } public static void UnParkTask(string host, string serviceName, string threadName) { Spludlow.Call.Now(host, "Spludlow", "Spludlow.Service", "UnParkTaskWork", new object[] { serviceName, threadName }); } public static void UnParkTaskWork(string serviceName, string threadName) { IService client = MakeClient(serviceName); try { client.UnParkTask(threadName); } finally { ServiceModels.Close(client); } Spludlow.Log.Info("Service; Thead Parked:" + threadName); } private bool ReLoad = false; public void Start() { this.LogFile = new LogFile(this.ServiceKey + ".txt"); string mes; try { mes = "Service; Tasks Starting"; this.LogFile.Append(mes); Spludlow.Log.Info(mes + ": " + this.ServiceKey); this.MethodTasks = new Dictionary(); this.ServerTasks = new Dictionary(); this.LoadTasks(this.ServiceKey); // wrap starts !!!! ??? foreach (string threadName in this.MethodTasks.Keys) this.StartTask(threadName); foreach (string threadName in this.ServerTasks.Keys) this.StartTask(threadName); this.ReLoad = true; mes = "Service; Tasks Started"; this.LogFile.Append(mes); Spludlow.Log.Report(mes + ": " + this.ServiceKey); } catch (Exception ee) { mes = "Service; Tasks Starting"; this.LogFile.Append(mes, ee); Spludlow.Log.Error(mes + ": " + this.ServiceKey, ee); this.LogFile.Close(); throw ee; } } private void LoadTasks(string serviceName) { DataSet dataSet = LoadServiceConfig(serviceName); DataTable methodsTable = dataSet.Tables["Methods"]; DataTable serversTable = dataSet.Tables["Servers"]; foreach (DataRow methodRow in methodsTable.Rows) { this.LoadMethodRow(methodRow); } foreach (DataRow serverRow in serversTable.Rows) { this.LoadServerTask(serverRow); } } private void LoadTask(string key) { DataSet dataSet = LoadServiceConfig(this.ServiceKey); DataTable methodsTable = dataSet.Tables["Methods"]; DataTable serversTable = dataSet.Tables["Servers"]; DataRow row; row = methodsTable.Rows.Find(key); if (row != null) { lock (this.MethodTasks) { this.MethodTasks.Remove(key); this.LoadMethodRow(row); } return; } row = serversTable.Rows.Find(key); if (row != null) { lock (this.ServerTasks) { this.ServerTasks.Remove(key); this.LoadServerTask(row); } return; } throw new ApplicationException("Service: Re-Load Task not found: " + key); } private void LoadMethodRow(DataRow methodRow) { string key = (string)methodRow["Key"]; Spludlow.CallMethod callMethod = (Spludlow.CallMethod)methodRow["CallMethod"]; MethodTask info = new MethodTask(); info.Name = key; info.MethodInfo = callMethod; this.MethodTasks.Add(key, info); } private void LoadServerTask(DataRow serverRow) { string key = (string)serverRow["Key"]; string assembly = (string)serverRow["Assembly"]; string type = (string)serverRow["Type"]; int port = (int)serverRow["Port"]; ServerTask info = new ServerTask(); info.Name = key; info.Assembly = assembly; info.Type = type; info.Port = port; this.ServerTasks.Add(key, info); } public static DataSet LoadServiceConfig(string serviceName) { return LoadServiceConfigAltProgData(serviceName, Spludlow.Config.ProgramData); } public static DataSet LoadServiceConfigAltProgData(string serviceName, string programData) { string configFilename = programData + @"\Config\" + serviceName + ".txt"; DataTable methodsTable = Spludlow.Data.TextTable.ReadText(new string[] { "Key CallMethod", "String* Spludlow.CallMethod", }); methodsTable.TableName = "Methods"; DataTable serversTable = Spludlow.Data.TextTable.ReadText(new string[] { "Key Assembly Type Port", "String* String String Int32", }); serversTable.TableName = "Servers"; using (FileStream fileStream = Spludlow.Io.Files.FileStreamOpenRead(configFilename)) { using (StreamReader reader = new StreamReader(fileStream)) { string rawLine; while ((rawLine = reader.ReadLine()) != null) { string line = rawLine.Trim(); if (line.Length == 0 || line[0] == '#') continue; try { string[] words = Spludlow.Text.Split(line, '\t', true, false); if (words.Length < 5) throw new ApplicationException("Not enough words in line"); string threadType = words[0].ToLower(); string key = words[1]; switch (threadType) { case "method": Spludlow.CallMethod callMethod = new CallMethod(); Spludlow.CallText.Read(callMethod, words, 2, reader); methodsTable.Rows.Add(new object[] { key, callMethod }); break; case "server": serversTable.Rows.Add(new object[] { key, words[2], words[3], Int32.Parse(words[4]) }); break; default: throw new ApplicationException("Unkown Thread type"); } } catch (Exception ee) { throw new ApplicationException("Service; Config, " + configFilename + "; Line Error, " + ee.Message + ":\t" + line, ee); } } } } DataSet dataSet = new DataSet(); dataSet.Tables.Add(methodsTable); dataSet.Tables.Add(serversTable); return dataSet; } public void StartTask(string threadName) { if (this.MethodTasks.ContainsKey(threadName) == true) { MethodTask info = this.MethodTasks[threadName]; if (info.Parked != DateTime.MinValue) { Spludlow.Log.Warning("Service, Start Task, Is PARKED at " + info.Parked); return; } if (this.ReLoad == true) this.LoadTask(threadName); info = this.MethodTasks[threadName]; lock (info) { info.Thread = new Thread(new ParameterizedThreadStart(this.RunMethod)); info.Thread.Name = threadName; info.Thread.Start(info); info.Started = DateTime.Now; info.LastError = null; info.LastErrorTime = DateTime.MinValue; info.Parked = DateTime.MinValue; } LogInfo("Service, Started Run Method Thread: " + threadName); } if (this.ServerTasks.ContainsKey(threadName) == true) { ServerTask info = this.ServerTasks[threadName]; if (info.Parked != DateTime.MinValue) { Spludlow.Log.Warning("Service, Start Task, Is PARKED at " + info.Parked); return; } if (this.ReLoad == true) this.LoadTask(threadName); info = this.ServerTasks[threadName]; lock (info) { info.ServiceHost = Spludlow.ServiceModels.StartServer(info.Assembly, info.Type, info.Port); info.Started = DateTime.Now; info.LastError = null; info.LastErrorTime = DateTime.MinValue; info.Parked = DateTime.MinValue; } string remotingAddress = Spludlow.ServiceModels.Address(Environment.MachineName, info.Type, info.Port); LogInfo("Service, Started Run Remoting Server:" + threadName + ", " + remotingAddress); } } private void RunMethod(object parameter) { MethodTask info = (MethodTask)parameter; try { LogInfo("Service, Run Method Preparing: " + info.Name + ", " + info.MethodInfo.Method); object[] parameters = Spludlow.Parameters.DecodeParameters(info.MethodInfo.Parameters); object[] constructorArguments = Spludlow.Parameters.DecodeParameters(info.MethodInfo.ConstructorArguments); Type type = Spludlow.Reflections.FindType(info.MethodInfo.Assembly, info.MethodInfo.Type); MethodInfo method = Spludlow.Reflections.FindMethod(type, info.MethodInfo.Method, parameters); info.Instance = Spludlow.Reflections.MakeInstance(type, method, constructorArguments); info.StopMethod = Spludlow.Reflections.FindMethod(type, "Stop", null); Spludlow.Log.Info("Service, Run Method Invoking:\t" + info.Name + "\t" + info.MethodInfo.Method); Spludlow.Reflections.Invoke(info.Instance, method, parameters); Spludlow.Log.Warning("Service, Run Method Thread Finished:\t" + info.Name); } catch (ThreadAbortException ee) { info.LastError = ee; info.LastErrorTime = DateTime.Now; LogWarning("Service, Run Method Aborted:\t" + info.Name, ee); } catch (Exception ee) { if (ee is TargetInvocationException && ee.InnerException != null) ee = ee.InnerException; info.LastError = ee; info.LastErrorTime = DateTime.Now; LogError("Service, Run Method Error:\t" + info.Name, ee); } } public void Stop() { try { LogInfo("Service; Tasks Stopping"); List tasks = new List(); foreach (string threadName in this.ServerTasks.Keys) tasks.Add(new Task(() => StopTask(threadName))); foreach (string threadName in this.MethodTasks.Keys) tasks.Add(new Task(() => StopTask(threadName))); foreach (Task task in tasks) task.Start(); Task.WaitAll(tasks.ToArray()); // Timeout ??????? // Kill child processes except the update process if its runnig List updatePids = new List(Spludlow.SpawnProcess.ProcessesWhereCommandLineLike(Spludlow.Release.UpdateMagic)); List childProcesses = new List(); foreach (uint pid in Spludlow.SpawnProcess.ChildProcesses()) { if (updatePids.Contains(pid) == false) childProcesses.Add(pid); else Spludlow.Log.Warning("UPDATE PID:" + pid); } Spludlow.SpawnProcess.KillProcesses(childProcesses.ToArray()); LogFinish("Service; Tasks Stopped"); } catch (Exception ee) { LogError("Service; Tasks Stopping", ee); } finally { this.LogFile.Close(); } } public void StopTask(string threadName) { if (this.ServerTasks.ContainsKey(threadName) == true) { ServerTask serverInfo = this.ServerTasks[threadName]; lock (serverInfo) { serverInfo.ServiceHost.Close(); } LogFinish("Service; TCP Server Task Stopped: " + threadName); return; } int secondsWaitAfterStop = 15; // Configure per thread ????????????????? MethodTask info = this.MethodTasks[threadName]; lock (info) { if (info.Thread.ThreadState == ThreadState.Running || info.Thread.ThreadState == ThreadState.WaitSleepJoin) { if (info.StopMethod != null) { Spludlow.Reflections.Invoke(info.Instance, info.StopMethod, null); bool joined = info.Thread.Join(secondsWaitAfterStop * 1000); } else { LogWarning("Service; Stopping Thread, No Stop() method to call:\t" + threadName + "\t" + info.Thread.ThreadState); } if (info.Thread.ThreadState == ThreadState.Running || info.Thread.ThreadState == ThreadState.WaitSleepJoin) { info.Thread.Abort(); info.Thread.Join(); } } else { LogWarning("Service; Stopping Thread, Already not running:\t" + threadName + "\t" + info.Thread.ThreadState); } LogFinish("Service; Stopping Thread; Stopped:\t" + threadName + "\t" + info.Thread.ThreadState); } } public void ParkTask(string threadName) { if (this.ServerTasks.ContainsKey(threadName) == true) { ServerTask serverInfo = this.ServerTasks[threadName]; lock (serverInfo) { serverInfo.Parked = DateTime.Now; } this.StopTask(threadName); return; } MethodTask info = this.MethodTasks[threadName]; lock (info) { info.Parked = DateTime.Now; } this.StopTask(threadName); } public void UnParkTask(string threadName) { if (this.ServerTasks.ContainsKey(threadName) == true) { ServerTask serverInfo = this.ServerTasks[threadName]; lock (serverInfo) { serverInfo.Parked = DateTime.MinValue; } this.StartTask(threadName); return; } MethodTask info = this.MethodTasks[threadName]; lock (info) { info.Parked = DateTime.MinValue; } this.StartTask(threadName); } public static DataSet StatusOnLine(string key) { IService client = MakeClient(key); try { return client.Status(); } finally { ServiceModels.Close(client); } } public DataSet Status() { Spludlow.SysTables.StatusDataTable table = new SysTables.StatusDataTable(); foreach (string threadKey in this.MethodTasks.Keys) { Spludlow.SysTables.StatusRow row = Spludlow.Status.NewStatusRow(table); MethodTask info = this.MethodTasks[threadKey]; lock (info) { row.Thread = threadKey; row.TaskType = "Method"; row.Assembly = info.MethodInfo.Assembly; row.Type = info.MethodInfo.Type; row.Method = info.MethodInfo.Method; row.Parameters = Spludlow.Parameters.ShortText(info.MethodInfo.Parameters); row.ConstructorArguments = Spludlow.Parameters.ShortText(info.MethodInfo.ConstructorArguments); row.ThreadState = info.Thread.ThreadState.ToString(); row.StartedTime = info.Started; string queue = QueueProcessorTask(info.MethodInfo); if (queue != null) row.QueueName = queue; if (info.LastError != null) { row.LastError = info.LastError.Message; row.LastErrorTime = info.LastErrorTime; } if (info.Parked != DateTime.MinValue) { row.LastError = "PARKED " + (row.IsLastErrorNull() == false ? row.LastError : ""); row.StartedTime = info.Parked; } } } foreach (string serverKey in this.ServerTasks.Keys) { Spludlow.SysTables.StatusRow row = Spludlow.Status.NewStatusRow(table); ServerTask info = this.ServerTasks[serverKey]; lock (info) { row.Thread = serverKey; row.TaskType = "Server"; row.Assembly = info.Assembly; row.Type = info.Type; row.ThreadState = "Singleton"; if (row.Type.EndsWith("*") == true) { row.Type = row.Type.Substring(0, row.Type.Length - 1); row.ThreadState = "SingleCall"; } string tcpError = Spludlow.ServiceModels.TcpServerError(info.Port); if (tcpError != null) row.ThreadState = tcpError; row.StartedTime = info.Started; if (info.LastError != null) { row.LastError = info.LastError.Message; row.LastErrorTime = info.LastErrorTime; } if (info.Parked != DateTime.MinValue) { row.LastError = "PARKED " + (row.IsLastErrorNull() == false ? row.LastError : ""); row.StartedTime = info.Parked; } } } EmptyMessageRow(table); string architecture = "64"; if (Environment.Is64BitProcess == false) architecture = "32"; foreach (Spludlow.SysTables.StatusRow row in table.Rows) { row.UserDomain = Environment.UserDomainName; row.UserName = Environment.UserName; row.Architecture = architecture; } return Spludlow.Data.ADO.WireDataSet(table); } public static DataSet StatusOffLine(string serviceName) { DataSet dataSet = LoadServiceConfig(serviceName); DataTable methodsTable = dataSet.Tables["Methods"]; DataTable serversTable = dataSet.Tables["Servers"]; Spludlow.SysTables.StatusDataTable table = new SysTables.StatusDataTable(); foreach (DataRow methodRow in methodsTable.Rows) { string key = (string)methodRow["Key"]; Spludlow.CallMethod callMethod = (Spludlow.CallMethod)methodRow["CallMethod"]; Spludlow.SysTables.StatusRow row = Spludlow.Status.NewStatusRow(table); row.Thread = key; row.TaskType = "Method"; row.Assembly = callMethod.Assembly; row.Type = callMethod.Type; row.Method = callMethod.Method; row.Parameters = Spludlow.Parameters.ShortText(callMethod.Parameters); row.ConstructorArguments = Spludlow.Parameters.ShortText(callMethod.ConstructorArguments); row.ThreadState = "Off Line"; string queue = QueueProcessorTask(callMethod); if (queue != null) row.QueueName = queue; } foreach (DataRow serverRow in serversTable.Rows) { string key = (string)serverRow["Key"]; string assembly = (string)serverRow["Assembly"]; string type = (string)serverRow["Type"]; int port = (int)serverRow["Port"]; Spludlow.SysTables.StatusRow row = Spludlow.Status.NewStatusRow(table); row.Thread = key; row.TaskType = "Server"; row.Assembly = assembly; row.Type = type; row.Method = port.ToString(); row.ThreadState = "Singleton"; if (row.Type.EndsWith("*") == true) { row.Type = row.Type.Substring(0, row.Type.Length - 1); row.ThreadState = "SingleCall"; } row.ThreadState = "Off Line"; // dont show type???? //row.Parameters = null; // show address ??? } EmptyMessageRow(table); foreach (Spludlow.SysTables.StatusRow row in table.Rows) { row.UserDomain = "Off Line"; // ? } return Spludlow.Data.ADO.WireDataSet(table); } private static string QueueProcessorTask(Spludlow.CallMethod info) { if (info.Assembly != "Spludlow" || info.Type != "Spludlow.QueueProcessor" || info.Method != "Run") return null; return (string)Spludlow.Parameters.DecodeParameter(info.Parameters[0]); } private static void EmptyMessageRow(Spludlow.SysTables.StatusDataTable table) { if (table.Rows.Count > 0) return; Spludlow.SysTables.StatusRow row = Spludlow.Status.NewStatusRow(table); row.LastError = "Service has no threads configured."; } private void LogInfo(string subject) { this.LogFile.Append(subject); Spludlow.Log.Info(subject); } private void LogWarning(string subject) { this.LogFile.Append(subject); Spludlow.Log.Warning(subject); } private void LogWarning(string subject, Exception ee) { this.LogFile.Append(subject); this.LogFile.Append(ee.ToString()); Spludlow.Log.Warning(subject, ee); } private void LogError(string subject, Exception ee) { this.LogFile.Append(subject); this.LogFile.Append(ee.ToString()); Spludlow.Log.Error(subject, ee); } private void LogFinish(string subject) { this.LogFile.Append(subject); Spludlow.Log.Finish(subject); } } public class InfiniteMethodTest { public static void Run() { System.Threading.Thread.Sleep(Timeout.Infinite); } } public class BadStopTest { public static void Stop() { } public static void Run() { System.Threading.Thread.Sleep(Timeout.Infinite); } } }