We were contacted by a customer who wondered how EurekaLog works in service applications, considering it has options like freeze detection, while service apps don't have main thread.
A service application has main thread like any other application. Main thread is a thread which executes app's entry point (begin/end in your .dpr file). Its ID is stored in the
RTL's MainThreadID global variable.
Now, if we are talking about
VCL's service apps - the main thread is not doing any heavy lifting in these apps. It runs the TServiceApplication.Run, which basically waits for service to exit.
There are two ways to implement a service:
- Either a loop inside the
ServiceExecute event (with calling the
TServiceThread.ProcessRequests method in the loop);
- Or spawn worker background threads inside the
ServiceStart event and stop them in the
ServiceStop event.
However, no matter which design you choose - all events (ServiceExecute, ServiceStart, etc.) are already called from a background thread. Main thread does not call any events.
The picture is like this (it is not a call stack, it is more like sequence of calls):
[Main thread] -> Application.Run ->
[TServiceStartThread] -> StartServiceCtrlDispatcher -> Application.DispatchServiceMain -> TService.Main -> RegisterServiceCtrlHandler -> TService.DoStart ->
[TServiceThread] -> TServiceThread.OnStart -> .OnExecute -> .OnStop
Things in [] indicate a new thread was spawned. As you can see, its [Main thread] -> [TServiceStartThread] -> [TServiceThread] -> your code. You have at least two background threads.
From
MSDN:
When the service control manager starts a service process, it waits for the process to call the StartServiceCtrlDispatcher function. The "main thread" of a service process should make this call as soon as possible after it starts up (within 30 seconds). If StartServiceCtrlDispatcher succeeds, it connects the calling thread to the service control manager and does not return until all running services in the process have entered the SERVICE_STOPPED state. The service control manager uses this connection to send control and service start requests to the main thread of the service process. The "main thread" acts as a dispatcher by invoking the appropriate HandlerEx function to handle control requests, or by creating a new thread to execute the appropriate ServiceMain function when a new service is started.
As you can see: the
VCL does not follow this description toe to toe - it uses one additional intermediate background thread to call the StartServiceCtrlDispatcher function. However, even if
VCL would call the StartServiceCtrlDispatcher function on its main thread, the TService still creates a TServiceThread background thread to call your events. So your code is never executed in the main thread in
VCL service apps.
But if your code is never executed in the main thread - it does not mean there is no main thread! There is - it is just not calling your code.
Since even empty
VCL service application has at least 3 threads (main thread, TServiceStartThread, TServiceThread): it is already a multithreaded application - even if you don't create your own (additional) threads. Usually EurekaLog has to be
enabled for each background thread that you want to handle exceptions. If you are using default settings (like not disabling
low-level hooks), the EAppService
unit would detect when a new TServiceThread starts and enable EurekaLog for it automatically. However, it will
not know about your own background threads.
Therefore:
- [Optional] If you disable the low-level hooks in EurekaLog - you have to manually activate EurekaLog for TServiceThread like so:
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);begin {$IFDEF EUREKALOG} SetEurekaLogStateInThread(0, True); // activate EurekaLog for this thread {$ENDIF} // your own code hereend;
- [Mandatory] If you spawn your own background threads (like in the ServiceStart event) - you have to activate EurekaLog for these threads by
any available means. One possible example:
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);begin {$IFDEF EUREKALOG} SetEurekaLogStateInThread(0, True); // activate EurekaLog for the TServiceThread thread {$ENDIF} FWorkerThread := TMyThread.Create(False); // create a background worker threadend;procedure TMyThread.Execute;begin {$IFDEF EUREKALOG} SetEurekaLogStateInThread(0, True); // activate EurekaLog for the TMyThread thread {$ENDIF} // your own code hereend;
Now, about the freeze detection feature. It works by sending a message to the main thread and checking if it gets a reply in time. It uses the main thread specifically because it is the thread which performs the message loop processing in
VCL Forms apps. If app's UI is hang, then the main thread will not reply in time, so the freeze detection can say "app is not responding".
As you can see, this logic is completely useless in service apps, because service apps don't have windows (UI). It is even indicated in the
option's name: "Activate
UI hang detection".
However, the freeze detection can also use new Windows Vista
API to detect explicit deadlocks between two threads. It will work in any app, assuming it runs on Windows Vista or later. So, this feature can be useful even in service apps. Well, assuming your freeze detection timeout is less than system timeout for services. Otherwise the system would terminate the service before freeze detection would have time to react.
Read more about
hangs and deadlocks with EurekaLog.
Weiterlesen...