Attualmente sto cercando di trovare un modo per chiamare un'attività che deve essere chiamata all'arresto di un oggetto singleton creato in TinyIOC all'interno di un'applicazione ospitata da NancyFX.Chiamare le attività di spegnimento in un singleton NancyFX Singleton
Fino ad ora, non sono stato in grado di trovare una risposta, e sono anche aperto a idee migliori per implementare lo scenario che sto per descrivere.
Panoramica
Ho un'applicazione web based PHP che sto lavorando su, perché questo è il PHP, non c'è nessun filo di filatura/server per sedersi ad ascoltare e l'elaborazione di attività in esecuzione lunghe, esiste il codice php per la vita span della richiesta del browser che lo richiede.
L'applicazione in questione deve fare alcune richieste a un servizio web che può richiedere del tempo per completare, e come risultato ho avuto l'idea di implementare alcuni servizi di back-end in C#, usando Topshelf, NancyFX e Stackexchange.Redis.
Il servizio è un NancyFX sé ospite console app standard come segue:
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();
// Do some checks on the response here
}
private RegistrationResponse RequestRegistration()
{
string registrationResponse = string.Empty;
try
{
registrationResponse = Utils.PostData("/processorapi/register", new Dictionary<string, string>
{
{"channelname", Settings.ChannelName},
});
}
catch (ApplicationException ex)
{
throw new ApplicationException("ERROR: " + ex.Message + " occured while trying to register. Aborting");
}
return JsonConvert.DeserializeObject<RegistrationResponse>(registrationResponse);
}
private DeregistrationResponse RequestDeregistration()
{
var deregistrationResponse = "";
try
{
deregistrationResponse = Utils.PostData("/processorapi/deregister", new Dictionary<string, string>
{
{"channelname", Settings.ChannelName},
});
}
catch (ApplicationException ex)
{
throw new ApplicationException("ERROR: " + ex.Message + " occured while trying to deregister. Aborting");
}
return JsonConvert.DeserializeObject<DeregistrationResponse>(deregistrationResponse);
}
private void RegisterProcessor()
{
Console.WriteLine("Registering processor with php application");
RegistrationResponse response = RequestRegistration();
// Do some checks on the response here
Console.WriteLine("Processor Registered");
}
private void RedisMonitor(string channelName)
{
_redisSubscriber = _redis.GetSubscriber();
_redisSubscriber.Subscribe(channelName, (channel, message) =>
{
RedisMessageHandler(message);
});
}
private void RedisMessageHandler(string inboundMessage)
{
RedisRequest request = JsonConvert.DeserializeObject<RedisRequest>(inboundMessage);
// Do something with the message here
}
}
}
Oltre a queste 3 classi, ho anche un paio di moduli NancyFX standard di routing, per gestire vari endpoint ecc
Tutto se questo funziona alla grande, come si può vedere dalla ProcessorKernel I effettua una chiamata a un metodo chiamato RegisterProcessor questo metodo, contatta l'applicazione web responsabile della gestione di questa istanza di processore e si registra, essenzialmente dicendo all'app Web, hey sono qui, e pronto a inviarmi richieste tramite il coda rossa.
Questo ProcessorKernel però viene istanziato come Singleton, utilizzando il 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();
}
}
}
Il motivo deve essere un singleton è perché deve accettare le richieste in coda da Redis, poi li elaborano e inviarli a vari servizi web in luoghi diversi.
Esistono diversi canali, ma SOLO un processore può ascoltare un canale alla volta. Ciò significa che quando questo ProcessorKernel si arresta, deve effettuare una chiamata all'applicazione web PHP su "De-Register" e informare l'applicazione che nulla sta servendo il canale.
Sta trovando un buon punto di aggancio per chiamare questa de-registrazione che mi sta causando il problema.
Approcci provato
Come si può vedere dal codice di cui sopra Ho provato con il distruttore di classe, e anche se non la migliore soluzione al mondo, lo fa arrivare chiamato ma la 's Get classe abbattute prima che ritorni e quindi la cancellazione effettiva non viene mai completata e quindi non viene mai annullata correttamente.
Ho anche provato a rendere la classe singleton un IDisposable, e vedere se si inserisce il codice di de-register in "Destroy", ma questo non è stato richiamato affatto.
: A causa sto usando TopShelf e ho i metodi Start/Stop all'interno ServiceApp so che posso chiamare mia routine lì, ma non riesco a ottenere l'accesso al Singleton ProcessorKernel a questo punto è allucinante non un modulo Nancy e quindi TinyIOC non risolve la dipendenza.
inizialmente ho anche cercato di creare il ProcessorKernel in Program.cs e passarlo in via del costruttore ServiceApp, e mentre questo ha creato un servizio, non è stato registrato come un Singleton, e wasn' t accessibile all'interno dei moduli Nancy Route, in modo che gli endpoint di stato non siano in grado di eseguire query sull'istanza creata per gli aggiornamenti di stato poiché hanno ottenuto solo il singleton creato da TinyIOC.
Se potessi accedere al singleton in Start/Stop, potrei semplicemente posizionare un paio di metodi pubblici su di esso per chiamare le funzioni di registro/annullamento.
Per completezza, il codice che sto usando per inviare le richieste POST per l'applicazione web PHP è la seguente:
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;
}
}
}
Sommario
All I è necessario un modo affidabile per chiamare il codice di de-register e assicurarsi che il servizio informi l'app php che sta andando via, a causa della natura della php-app non posso inviarmi un messaggio usando redis dato che è semplicemente non ascoltando, l'app è a app php lineare standard in esecuzione sul server Web Apache.
Sono aperto a idee migliori su come progettare questo a proposito, a patto che ci sia sempre una sola istanza del processore effettivo ProcessorKernel in esecuzione. Una volta registrato, deve elaborare ogni richiesta inviata al suo canale e, poiché le richieste in elaborazione possono essere consultate più volte ed elaborate da diversi servizi Web, tali richieste devono essere conservate in memoria fino al completamento dell'elaborazione.
Grazie in anticipo per qualsiasi idea su questo.
Update - The Next Day :-)
Così, dopo aver visto la risposta di Kristian di seguito, e di vedere le risposte del suo/di GrumpyDev e Phillip Haydon sul mio flusso di Twitter, ho cambiato la mia classe Singleton di nuovo ad un IDisposable, sbarazzati di il finalizzatore ~, quindi modificato ServiceApp.cs in modo che chiamasse Host.Dispose() anziché host .Stop()
Questo poi chiamato correttamente il Dispose di routine sulla mia classe Singleton, che mi ha permesso chiuso le cose correttamente.
ho anche venire con un'altra soluzione, che devo ammettere
- a) era brutta
- b) è stato rimosso
tuttavia nello spirito di condivisione (e a rischio di imbarazzo) Vado a dettagliarlo qui NOTA: In realtà non lo raccomando di fare questo
Prima di tutto Ho regolato ServiceApp.cs in modo che appaia ora come:
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.");
}
}
}
Come si può vedere questo mi ha permesso di ottenere un riferimento al mio Singleton in app di servizio, utilizzando TinyIOC, che mi ha permesso di mantenere il riferimento per chiamare Reg/DeReg su nelle routine di servizio Start/Stop.
Il pubblico statico Kernel proprietà è stato poi aggiunto in modo che i miei moduli Nancy poteva chiamare ServiceApp.Kernel ... per avere le informazioni di stato di cui avevano bisogno in risposta alle query sugli endpoint Nancy.
Come ho detto, tuttavia, questo cambiamento è stato annullato in favore del ritorno al modo idisposabile di fare le cose.
Grazie a tutti quelli che hanno avuto il tempo di rispondere.
Oh, giusto, capito ... Ecco perché il mio metodo Dispose non ha funzionato. * slaps self * Ho implementato il modo IDisposable di fare le cose, ma non ho mai cambiato la chiamata * service.stop *. Darò una prova e tornerò su questo. – shawty
PS: Sono d'accordo, sto usando Finalizer, non volevo farlo in quel modo (per tutti i motivi citati) ma ieri sera è stato l'unico punto di aggancio su cui stavo prendendo un morso. – shawty
O ero mezzo addormentato (era l'1 di questa mattina) o semplicemente non vedevo il bosco per gli alberi. In entrambi i casi, cambiando la mia chiamata * Host.Stop * a * Host.Displose *, quindi binning il mio Finalizer e tornare a IDisposable era la soluzione. Mentre stavo cercando di fermare l'interfaccia IDisposable, il mio metodo * Dispose * non è mai stato chiamato, ora va tutto bene. – shawty