i'm trying find way of calling task needs called @ shutdown of singleton object created in tinyioc inside nancyfx self hosted application.
as of yet, i've been unable come answer, , i'm open better ideas of implementing scenario i'm describe.
overview
i have php based web application i'm working on, because php, there's no spinning thread/server sit listening , processing long running tasks, php code exists life span of browser request requesting it.
the application in question needs make requests web service can potentially take time complete, , result i've come idea of implementing end services in c#, using topshelf, nancyfx , stackexchange.redis.
the service standard nancyfx self host console app 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 unable 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: not connect redis queue, aborting."); } registerprocessor(); redismonitor(); } ~processorkernel() { var response = requestderegistration(); // checks on 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 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 deregister. aborting"); } return jsonconvert.deserializeobject<deregistrationresponse>(deregistrationresponse); } private void registerprocessor() { console.writeline("registering processor php application"); registrationresponse response = requestregistration(); // checks on 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); // message here } } } as these 3 classes, have couple of standard nancyfx routing modules, handle various endpoints etc.
all if works great, can see processorkernel make call method called registerprocessor method, contacts web application responsible managing processor instance , registers itself, saying web app, hey i'm here, , ready send me requests via redis queue.
this processorkernel instantiated singleton, using 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 has singleton because has accept queued requests redis, process them , send them various webservices @ different locations.
there several different channels, 1 processor can listening 1 channel @ time. means when processorkernel shuts down, has make call php web application "de-register" itself, , inform application nothing serving channel.
it's finding hook point call de-registration that's causing me problem.
approaches tried
as can see code above i've tried using class destructor, , while not best solution in world, called class get's torn down before returns, , actual de-registration never completes, , never de-registers correctly.
i've tried making singleton class idisposable, , seeing if putting de-register code inside "destroy", didn't called @ all.
beacuse i'm using topshelf , have start/stop methods inside serviceapp know can call routines there, can't access singleton processorkernel @ point beacuse it's not nancy module , tinyioc doesn't resolve dependency.
i tried create processorkernel in program.cs , pass in via serviceapp constructor, , while created service, wasn't registered singleton, , wasn't accessible inside nancy route modules, status endpoints unable query created instance status updates got singleton tinyioc created.
if access singleton in start/stop place couple of public methods on call register/deregister functions.
for completeness, code i'm using send post requests php web app follows:
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 + " not valid resource"); } } return resultcontent; } } } summary
all need reliable way call de-register code, , ensure service informs php-app it's going away, because of nature of php-app can't send message using redis it's not listening, app standard linear php app running under apache web-server.
i'm open better ideas on how architect way, long there ever 1 instance of actual processorkernel running. once it's registered has process every request sent channel, , since requests being processed may accessed several times , processed several different web services, requests need held in memory until processing complete.
thanks in advance thoughts on this.
update - next day :-)
so after seeing kristian's answer below, , seeing his/grumpydev's , phillip haydon's replies on twitter stream, changed singleton class idisposable, got rid of ~finalizer, changed serviceapp.cs called host.dispose() rather host.stop()
this correctly called dispose routine on singleton class, allowed me shut things down correctly.
i did come workaround, have admit
- a) ugly
- b) has been removed
however in spirit of sharing (and @ risk of embarrassment) i'm going detail here note: don't recommend doing this
first off adjusted serviceapp.cs looks like:
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 { { 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 unable 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 can see allowed me reference singleton in service app, using tinyioc, allowed me keep reference call reg/dereg on in start/stop service routines.
the public static kernel property added nancy modules call serviceapp.kernel... status info needed in response queries on nancy endpoints.
as however, change has been undone in favor of going idisposable way of doing things.
thanks took time reply.
there standard way of dealing "cleanup" in .net - it's called idisposable, , nancy (along core components, such ioc container) uses extensively.
as long objects correctly implements idisposable , dispose method called (preferably through using block) @ top level, objects should disposed @ right time.
instead of calling stop on nancyhost, should call dispose. this'll result in following:
- stop
nancyhost(like do)- dispose bootstrapper
- dispose application container (in case
tinyioccontainer)- dispose singleton instances implementing
idisposable
- dispose singleton instances implementing
- dispose application container (in case
- dispose bootstrapper
in order disposal go way processorkernel, have make sure implements idisposable , move code finalizer dispose method.
ps! unless you're dealing unmanaged resources, should never implement finalizer. here some reasons. i'm sure you'll find more if google it.
Comments
Post a Comment