Trouble with services
Multithreaded programming is a pain in the <insert body part>. Whatever you do, sooner or later your code will stop working correctly. Service writing is not simple, either. There are many limitations that should be taken into account (lack of graphical user interface, for example). For quite some time now, I was sure I mastered them both, but not so long ago a service/thread combination proved me wrong. There's a catch in service programming that I was not aware of until I had to spend several days tracing down one rarely occurring problem. It was so rare that it happened only at one customer and even there the application crashed only once in several hours. So what's the catch? It is quite simple. Every Windows service is multithreaded. Even if you don't create threads in the service application, it will be using two threads. It gets even worse. On the service form, OnCreate and OnDestroy are called from the main application thread while OnStart and OnStop are called from the main service thread. There is a logic behind this, and it is quite obvious once you stop to think about it. A service application can contain several servicesand as they are independent entities, each must run in a separate thread. On the other hand, OnCreate/OnDestroy are called from the main application which creates those services, and are therefore called from the main application thread. Simple. But it can bite you. If you drop some components on the service form, they will be created in destroyed from the main application thread. Of course, they will be used from the appropriate service thread (or from the service thread, if there's only one service in the application, which is usually the case). Usually, you won't notice this. But things start getting weird if component in question creates hidden message-processing window. Main application thread will process messages for this hidden window, but you'll usually want to process them in the service thread. Even worse, if you implement some kind of message pump in the service thread, two threads will be processing messages for the same component. It won't happen frequently, but it surely will happen and you can imagine the result. Deadlocks. Data corruption. Mayhem. Yes, that's what happened to me. BTW, that's also why TTimer doesn't work correctly if you drop it on the service form. If you don't believe me, try it for yourself. Create a new service application and add some logging to the OnCreate/OnStart/OnStop/OnDestroy: procedure TService1.ServiceDestroy(Sender: TObject); I used following trivial logger in the demo: procedure Log(const msg: string); Compile and run:
Service will get installed, started, stopped and uninstalled. Now check the log file: Create called from process 6020 thread 4188 What happened here? First OnCreate/OnDestroy got called (when ServiceDemo /install was run). Next, OnCreate was called from thread 3184, OnStart/OnStop were called from thread 4588 (I told you!) and OnDestroy was called from thread 3184. Finally OnCreate/OnDestroy were called then ServiceDemo was run with /uninstall switch. So there's another problem with dropping components on the service form - they will also be created/destroy whenever service application is called with /install or /uninstall switch! OK, you're asking, but what can we do? I suggest creating components in the OnStart event - either manually (in source) or by dropping them into a separate data module, which is not created automatically but in the OnStart event: procedure TService1.ServiceStart(Sender: TService; var Started: boolean); (BTW, in Delphi 2006 you can only prevent data module from being created automatically in service application by removing the appropriate CreateForm line in the project source.) It's obvious that data module's OnCreate is now called in the context of the same thread as services OnStart, but just to be sure we can install the service again and check the log: Create called from process 2472 thread 4344 The moral for today: Dont drop anything on the service form. Ever! Complete demo project is available at: http://17slon.com/blogs/gabr/files/svc_demo.zip |
4 Comments:
Hi
Services made easy = SVCOM
SVCOM has its own timer etc
Agreed:Using datamodules that gets created in onstart is the best way.
In onstart I use:
CoInitialize(nil);
CreateForms;
and in OnStop:
FreeForms;
CoUninitialize;
Using CreateForms to create all DMs
and FreeForms to free them
It's true, SvCom is great. I'm using it everytime I have to write a service.
But it exhibits the same "problem" (we've seen that it is really a design decision, not a problem per se) - OnCreate is called from one thread and OnStart from others.
Alternate solution:
Make your service module ULTRA lightweight. Put all your actual code into a datamodule. Datamodule gets created and destroyed in the OnExecute.
Do real work in the data module, and voila, problem solved, except for knowing when you have have extra threading issues brought by third party libraries like the BDE.
Of course, for a long time I just used Peter Sawatzki which does let you use VCL code - it all runs in the primary thread and you just put a lightweight loop in the service thread itself. Sadly, he hasn't maintained it and as delphi and windows evolved, it has started to develop "issues".
That certainly sounds like a good advice - something that I may adopt in the future.
Post a Comment
Links to this post:
Create a Link
<< Home