alank2 5 Posted February 10, 2023 I have a C++ Builder service application that uses a timer TProcess to do something every 60 seconds. The "something" was processing some data and making a SQL call. I was accessing one of the query columns outside of a try catch block and it was throwing an exception because I was accessing it as an integer and it was a bit type and needed to be accessed as a boolean. I get the error and how to fix it, but I am trying to troubleshoot how to stop the exception from being unhandled at a higher lever then leaving the service in a stuck state. try { while (!Terminated || !TProcess->Enabled) //do not quit if process is running until it is finished { ServiceThread->ProcessRequests(true); Sleep(250); } } catch(Exception &exception) { log1.Logf(L"ERROR: %s (main loop)", exception.Message.w_str()); } At first I just had what was in the try block above on its own. Then I wrapped it in the try block and expected that this would catch the exception and at least log it. What I don't understand is why the above did not catch the exception. I repeated the test and it still hung with no log message. When I moved the code that accesses the SQL field into its own try catch block with logging, it did log the error related to the issue. Share this post Link to post
Anders Melander 1815 Posted February 10, 2023 It's not clear where the code you posted is located but regardless, exceptions inside TServiceThread.ProcessRequests are caught and handled (logged) there so they won't propagate to your handler. If you need to catch unhandled exceptions on the service level you should create a custom TServiceApplication and override the DoHandleException method. By default, it logs exceptions to the event log but you can change it to do whatever you need. This is what it would look like in Delphi: program ServiceProject; uses Vcl.SvcMgr, MyUnit in 'MyUnit.pas' {MyService: TService}; {$R *.RES} type TMyServiceApplication = class(TMyServiceApplication) protected procedure DoHandleException(E: Exception); override; end; procedure TMyServiceApplication.DoHandleException(E: Exception); begin ...whatever... end; begin // Get rid of TServiceApplication... Vcl.SvcMgr.Application.Free; // ...and use our custom service application class instead Vcl.SvcMgr.Application := TMyServiceApplication.Create(nil); // Usual service code follows... if not Application.DelayInitialize or Application.Installing then Application.Initialize; Application.CreateForm(TMyService, MyService); Application.Run; end. Share this post Link to post
alank2 5 Posted February 10, 2023 Thanks Anders; my code snippet above was from within ServiceExecute. I appreciate the explanation, that makes sense! Share this post Link to post
Remy Lebeau 1436 Posted February 10, 2023 (edited) 7 hours ago, alank2 said: my code snippet above was from within ServiceExecute FYI, it is generally not a good idea to use the TService.OnExecute event at all, since it requires you to take control of the message loop that processes SCM requests. If you mess up the loop, the service won't respond to the SCM correctly. When there is no OnExecute handler assigned at all, TService's default behavior is sufficient to let the service process and response to SCM requests correctly. You should instead spawn your own thread from the TService.OnStart event, and terminate that thread in the TService.OnStop and TService.OnShutdown events. Then you can do whatever you want inside of your thread. Edited February 10, 2023 by Remy Lebeau Share this post Link to post
alank2 5 Posted February 10, 2023 Thanks for the tip Remy; I'll give that approach a try as well. Share this post Link to post
Anders Melander 1815 Posted February 10, 2023 11 minutes ago, Remy Lebeau said: Then you can do whatever you want inside of your thread. I agree that this is the better approach. Just remember to signal the thread to terminate (and wait for it to do so) when the service is stopping. Also, note that in the case of a COM-server-in-a-service there might not be a need for a separate thread since each COM connection will get its own thread anyway, depending on the apartment model used. Share this post Link to post
alank2 5 Posted February 10, 2023 How would you wait for the thread to stop? The current method does wait for any process running to finish. I get how to trigger the thread to stop using the onstop/onshutdown events, but where do you actually make it wait on the thread? Share this post Link to post
Anders Melander 1815 Posted February 10, 2023 32 minutes ago, alank2 said: How would you wait for the thread to stop? Keep a reference to the thread when you create it. Use TThread.Terminate to terminate it and use TThread.WaitFor to wait for it to actually terminate. 1 Share this post Link to post
Remy Lebeau 1436 Posted February 11, 2023 (edited) 23 hours ago, alank2 said: I get how to trigger the thread to stop using the onstop/onshutdown events, but where do you actually make it wait on the thread? You signal the thread to terminate, and then you wait for the thread to actually terminate right then and there, before you exit from the OnStop/OnShutdown event handler. The service enters a StopPending state before triggering the event, and then it enters the Stopped state once the event exits (or, in the case of the OnStop event, it enters back into the Running state if you set the event's Stopped parameter to False). 22 hours ago, Anders Melander said: Keep a reference to the thread when you create it. Use TThread.Terminate to terminate it and use TThread.WaitFor to wait for it to actually terminate. Note that using TThread.WaitFor() in this situation is not a good idea, since the service still needs to report status back to the SCM at regular intervals while the SCM is waiting for the service to stop itself. If the thread takes a long time to terminate, the SCM will fail after some time if the service doesn't actively tell the SCM that the stop is taking a long time. What I do instead is use TThread.Handle with WaitForSingleObject() (or equivalent) in a loop, using a timeout calculated from the TService.WaitHint property (usually WaitHint-100). When WFSO reports the thread has fully terminated, I break the loop and move on. When WFSO times out, I call TService.ReportStatus() and keep looping. Edited February 11, 2023 by Remy Lebeau 1 Share this post Link to post