I'm currently trying to find a way to invoke a task that needs to be called when turning off a singleton object created in TinyIOC inside a standalone NancyFX application.
At the moment, I could not answer this question, and I am also open to the best ideas for implementing the scenario that I am going to describe.
Overview
I have a PHP-based web application that I am working on because it is PHP, there is not a single thread / server for sitting to listen to and process long-running tasks, php code exists for the service life of the request for a browser request it.
In this application, you need to fulfill some requests to the web service, which may take a while to complete, and as a result, I came up with the idea of implementing some back end services in C # using Topshelf, NancyFX and Stackexchange.Redis.
The service is a standard application for the NancyFX console host as follows:
Program.cs
using Topshelf; namespace processor { public class Program { static void Main() { HostFactory.Run(x => { x.UseLinuxIfAvailable(); x.Service<ServiceApp>(s => { s.ConstructUsing(app => new ServiceApp()); s.WhenStarted(sa => sa.Start()); s.WhenStopped(sa => sa.Stop()); }); }); } } }
ServiceApp.cs
using System; using Nancy.Hosting.Self; namespace processor { class ServiceApp { private readonly NancyHost _server; public ServiceApp() { _server = new NancyHost(new Uri(Settings.NancyUrl)); } public void Start() { Console.WriteLine("processor starting."); try { _server.Start(); } catch (Exception) { throw new ApplicationException("ERROR: Nancy Self hosting was unable to start, Aborting."); } Console.WriteLine("processor has started."); } public void Stop() { Console.WriteLine("processor stopping."); _server.Stop(); Console.WriteLine("processor has stopped."); } } }
ProcessorKernel.cs
using System; using System.Collections.Generic; using Newtonsoft.Json; using StackExchange.Redis; namespace processor { public class ProcessorKernel { private readonly ConnectionMultiplexer _redis; private ISubscriber _redisSubscriber; public ProcessorKernel() { try { _redis = ConnectionMultiplexer.Connect(Settings.RedisHost); } catch (Exception ex) { throw new ApplicationException("ERROR: Could not connect to redis queue, aborting."); } RegisterProcessor(); RedisMonitor(); } ~ProcessorKernel() { var response = RequestDeregistration();
Like these 3 classes, I also have several standard NancyFX routing modules for handling various endpoints, etc.
Everything, if this works fine, as you can see from ProcessorKernel , I make a call to a method called RegisterProcessor by this method, contact the web application responsible for managing the processor instance, and register itself, essentially speaking in the web application, hey, I am here and ready for you to send me requests through the redis queue.
This ProcessorKernel is created as a singleton using the Nancy Bootstrapper:
using Nancy; using Nancy.Bootstrapper; using Nancy.TinyIoc; namespace processor { public class Bootstrapper : DefaultNancyBootstrapper { protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) { base.ApplicationStartup(container, pipelines); container.Register<ProcessorKernel>().AsSingleton(); } } }
The reason it should be single point is because it has to accept queued requests from redis, then process them and send them to various web services in different places.
There are several different channels, but ONLY one processor can listen on one channel at a time. Ultimately, this means that when this ProcessorKernel closes, it must make the PHP web application call itself "de-register" and tell the application that nothing is serving the pipe.
It will find a suitable point to call this de-registration, which is causing me a problem.
Well-developed approaches
As you can see from the above code, I tried to use the class destructor, and although this is not the best solution in the world, it calls, but the class breaks down before it returns, and therefore the actual de-registration never completes and therefore never cancels registration.
I also tried to make the singleton class IDisposable and see if I put the de-register code inside "Destroy", but that didn't work at all.
Beacuse I use Topshelf, and I have Start / Stop methods inside ServiceApp . I know that I can call my routines there, but I can’t access singleton ProcessorKernel , at the moment, this is not a Nancy module, and therefore TinyIOC does not resolve the dependency.
At first, I also tried to create a ProcessorKernel in Program.cs and pass it through the ServiceApp constructor, and while this created the service, it was not registered as a singleton, and was not accessible inside the Nancy Route modules, so the endpoints where the created instance could not be requested for status updates, as they received only a singleton created by TinyIOC.
If I could access singleton in Start / Stop, then I could just put several public methods on it to call the register / deregister functions.
For completeness, the code I use to send mail requests to a PHP web application is shown below:
Utils.cs
using System; using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Reflection; using System.Text; namespace processor { public class Utils { public static string PostData(string url, Dictionary<string, string> data) { string resultContent; using (var client = new HttpClient()) { client.BaseAddress = new Uri(Settings.AppBase); var payload = new List<KeyValuePair<string, string>>(); foreach (var item in data) { payload.Add(new KeyValuePair<string, string>(item.Key, item.Value)); } var content = new FormUrlEncodedContent(payload); var result = client.PostAsync(url, content).Result; resultContent = result.Content.ReadAsStringAsync().Result; var code = result.StatusCode; if (code == HttpStatusCode.InternalServerError) { Console.WriteLine(resultContent); throw new ApplicationException("Server 500 Error while posting to: " + url); } if (code == HttpStatusCode.NotFound) { Console.WriteLine(resultContent); throw new ApplicationException("Server 404 Error " + url + " was not a valid resource"); } } return resultContent; } } }
Summary
All I need is a reliable way to call the de-register code and ensure that the service informs the php application that it is leaving, due to the nature of the php application I cannot send a message back, it uses redis, because it just not listening, the application is a standard linear php application running under the Apache web server.
I am open to better ideas on how to do this, by the way, while there is only one instance of the actual ProcessorKernel Run. After registration, it must process each request sent to its channel, and since the processed requests can be accessed several times and processed by several different web services, these requests must be stored in memory until their processing is completely completed.
Thanks in advance for any thoughts on this.
Update - the next day :-)
So, after seeing Christian's answer below, and seeing that his / GrumpyDev and Phillip Haydon are responding to my Twitter stream, I changed my Singleton class to IDisposable, got rid of ~ Finalizer, and then changed ServiceApp.cs so that it calls Host .Dispose () , not Host.Stop ()
Then it correctly called the Dispose routine in my singleton class, which allowed me to close things correctly.
I also came up with another workaround that I have to admit
However, in the spirit of sharing (and with the risk of embarrassment) I will talk about this in detail here NOTE. I really do not recommend doing this
First I set up ServiceApp.cs so that it now looks like this:
using System; using Nancy.Hosting.Self; using Nancy.TinyIoc; namespace processor { class ServiceApp { private readonly NancyHost _server; private static ProcessorKernel _kernel; public static ProcessorKernel Kernel { get { return _kernel;}} public ServiceApp() { _server = new NancyHost(new Uri(Settings.NancyUrl)); } public void Start() { Console.WriteLine("Processor starting."); _kernel = TinyIoCContainer.Current.Resolve<ProcessorKernel>(); try { _server.Start(); } catch (Exception) { throw new ApplicationException("ERROR: Nancy Self hosting was unable to start, Aborting."); } _kernel.RegisterProcessor(); Console.WriteLine("Processor has started."); } public void Stop() { Console.WriteLine("Processor stopping."); _kernel.DeRegisterProcessor(); _server.Dispose(); Console.WriteLine("Processor has stopped."); } } }
As you can see, this allowed me to get a link to my singleton in the service application using TinyIOC, which allowed me to save the link to the Reg / DeReg call in the Start / Stop service programs.
Then the Public Static Kernel property was added so that my Nancy modules could call ServiceApp.Kernel ... to get the status information they need in response to requests to Nancy's endpoints.
As I said, this change has now been canceled in favor of a return to the IDisposable way of doing things.
Thanks to everyone who took the time to respond.