Tuesday, July 18, 2006

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);
begin
Log(Format('Destroy called from process %d thread %d',
[GetCurrentProcessID, GetCurrentThreadID]));
end;

procedure TService1.ServiceCreate(Sender: TObject);
begin
Log(Format('Create called from process %d thread %d',
[GetCurrentProcessID, GetCurrentThreadID]));
end;

procedure TService1.ServiceStart(Sender: TService; var Started: boolean);
begin
Log(Format('Start called from process %d thread %d',
[GetCurrentProcessID, GetCurrentThreadID]));
Started := true;
end;

procedure TService1.ServiceStop(Sender: TService; var Stopped: boolean);
begin
Log(Format('Stop called from process %d thread %d',
[GetCurrentProcessID, GetCurrentThreadID]));
Stopped := true;
end;

I used following trivial logger in the demo:

procedure Log(const msg: string);
var
fLog: textfile;
begin
AssignFile(fLog, 'c:\ServiceDemo.log');
if FileExists('c:\ServiceDemo.log') then
Append(fLog)
else
Rewrite(fLog);
Writeln(fLog, msg);
CloseFile(fLog);
end;

Compile and run:
  • ServiceDemo /install

  • net start ServiceDemo

  • net stop ServiceDemo

  • ServiceDemo /uninstall


Service will get installed, started, stopped and uninstalled. Now check the log file:
Create called from process 6020 thread 4188
Destroy called from process 6020 thread 4188
Create called from process 4840 thread 3184
Start called from process 4840 thread 4588
Stop called from process 4840 thread 4588
Destroy called from process 4840 thread 3184
Create called from process 2968 thread 4640
Destroy called from process 2968 thread 4640

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);
begin
Log(Format('Start called from process %d thread %d',
[GetCurrentProcessID, GetCurrentThreadID]));
FDataModule := TDataModule2.Create(nil);
Started := true;
end;

procedure TService1.ServiceStop(Sender: TService; var Stopped: boolean);
begin
Log(Format('Stop called from process %d thread %d',
[GetCurrentProcessID, GetCurrentThreadID]));
FreeAndNil(FDataModule);
Stopped := true;
end;

(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
Destroy called from process 2472 thread 4344
Create called from process 4824 thread 5216
Start called from process 4824 thread 3144
DM Create called from process 4824 thread 3144
Stop called from process 4824 thread 3144
DM Destroy called from process 4824 thread 3144
Destroy called from process 4824 thread 5216
Create called from process 3588 thread 332
Destroy called from process 3588 thread 332

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

Wednesday, July 12, 2006

Each day I learn something new

(or at least I try to)

Today I found the Delphi Wiki. Not very full - yet - but still contains some useful info. It is waiting for your additions at http://delphi.wikia.com/wiki/Main_Page.

Also, Deepak Shenoy's blog entry on live templates and PInvoke contains many useful links:
And it is a very interesting coverage on extending live templates.

Updated on 2006-07-17: I just found a wonderful Delphi blog aggregator: DelphiFeeds.com. Recommended!

Wednesday, June 28, 2006

Multi-monitor Delphi

As many developers have already noted, developing with Delphi 2005/2006 on a single monitor is a major pain in every part of the body. That's why I hate developing on my notebook. Code fixing is doable, barely, but serious development is out of the question.

Many developers have also noted that 'undocked' layout works better on multiple
monitors. I don't think so - D2006 works just fine in 'docked' mode. The important
thing is to undock some of the windows and keep others docked or everything goes
kaboom.

Something you definitely don't want to do is undock Object Inspector and Messages.
Weirdest things start happening if you do.

In case you're wondering how I have organized my Delphi desktop, here's the picture:

normal work environment



As you can see, my primary monitor is on the right and secondary on the left. This configuration alone makes problems in VCL-based programs. For example, poDesktopCenter form position is calculated incorrectly - but that's another story.

Main monitor is filled with the Editor (of course!), onto which Object Inspector, Tool Palette, and Messages are docked. I said it before and I'll say it again: it is a wise man who keeps Object Inspector and Messages docked to the Editor.

On the left monitor I have Project Manager and ModelMake Code Explorer docked together (also, when needed, Model View appears here), small windows for To-Do List and Structure (to see syntax errors while I'm typing), another window shared between Find References and Refactorings and one hold-them-all container where other useful stuff is bunched together (Clipboard History from GExperts, Breakpoint List, Event Log etc).

I also have a slightly narrower layout for the time when I need more space (to watch TV, for example):

slightly narrower layout



In this layout, Refactorings and Find References are moved into the hold-them-all window and other windows are rearranged to use less space.

Sometimes I need lots of space - for example when comparing current code with an older version, or to look at the project specifications, various standards, internet pages ... To use Delphi on a single monitor, I keep Project Manager and Tool Palete docked and minimized on the right side of the Editor:

tweaked 'default' layout



Rarely used windows (Refactorings, Find References etc) take their share off the Editor space (for the short time they are visible):

tweaked 'default' layout with Find References



When debugging, I'm usually only using one monitor (and in some rare cases even less):

debug layout



That leaves me one full monitor to run the application on. This helps a lot, especially when debugging OnPaint handlers.

Monday, June 26, 2006

Remote access to the fourth power

Just a little list of what I'm using when I need remote access or remote control.

On the top of the list is Microsoft's Remote Deskop. Besides the accessibility (included in standard Windows XP installation), it is also blindingly fast, especially on DSL connections. Even more, it still works fine on dual ISDN connection (128 Kb/s).

What I hate about it is that it messes with my desktop. I'm running dual monitor configuration and everytime I connect remotely to my computer, RD moves all my desktop icons to the primary monitor. Yuck! (A small shell extension named Desktop Icons Save and Restore helps me restore them, but still - Yuck!)

That's why I sometimes use TightVNC for the same purpose. Not so fast, not so good (sometimes the screen is not redrawn when it should be), but it works quite fine on very fast networks; for example on the internal 100 Mb/s Ethernet.

When I'm using notebook and main computer at the same time, I usually use Synergy to control both machines with one keyboard and mouse. Read more about it in Steve Trefethen's blog.

Most rarely used of the great four (and the only one that is not free) is the MaxiVista. It allows you to turn additional computer (a notebook, for example) into an additional screen for your main computer. So when my two displays are not enough, I place the notebook on the table next to them, fire MaxiVista client on the notebook, MaxiVista server on the main computer and configure screen options to use the third monitor. MaxiVista handles image transfer over the network very well and most of the time I don't even notice that the third screen is not physically attached to my main computer.

Saturday, June 24, 2006

Compiler-generated getters and setters

(Sorry, not today. Maybe in a not-too-distant future.)

A very common task when implementing interfaces is writing getter and setter methods. In many cases, getters only access internal fields. In some cases, even setters only access internal fields. Still, we have to code those pesky methods by hand (or in a slightly simpler way using great ModelMaker Code Explorer - oh I like it so much!). I think that Delphi compiler could lend us a hand here and automatically generate trivial accessors when needed.

Let’s take a look at the example:

type
ISimpleInterface = interface
function GetProp1: integer;
procedure SetProp1(value: integer);
property Prop1: integer read GetProp1 write SetProp1;
end;
TSimpleObject = class(TInterfacedObject, ISimpleInterface)
private
FProp1: integer;
public
function GetProp1: integer;
procedure SetProp1(value: integer);
end;
{ TSimpleObject }
function TSimpleObject.GetProp1: integer;
begin
Result := FProp1;
end;
procedure TSimpleObject.SetProp1(value: integer);
begin
FProp1 := value;
end;

Even though we only want to implement the Prop1 property using direct access to a field, we still have to write two very dull methods.

We should be able to tell the compiler to do that for us. It is quite obvious that the compiler has enough data (it already knows the getter/setter signatures from the interface declaration) to generate both methods. The main question is how to instruct it to do so.

At the beginning I intended to abuse the ‘automated’ keyword (as it is already tokenized by the parser) like so:
TSimpleObject = class(TInterfacedObject, ISimpleInterface)
private
FProp1: integer
public
property Prop1: integer read GetProp1 automated FProp1 write SetProp1 automated FProp1;
end;

This was not very optimal as it requires you to declare the Prop1 property in the TSimpleObject, which is not requirement per se when implementing interfaces.

Next idea was:
TSimpleObject = class(TInterfacedObject, ISimpleInterface)
private
FProp1: integer;
public
property Prop1: integer read GetProp1 = FProp1 write SetProp1 = FProp1;
end;

It doesn’t abuse the ‘automated’ keyword but carries same problems.

My current favorite, suggested by a fellow programmer Lee_Nover, uses same syntax as method aliasing:
TSimpleObject = class(TInterfacedObject, ISimpleInterface)
private
FProp1: integer;
public
function ISimpleInterface.GetProp1 = FProp1;
procedure ISimpleInterface.SetProp1 = FProp1;
end;

Although this syntax is very clear and self-describing, I would prefer to see a following simplified form:
TSimpleObject = class(TInterfacedObject, ISimpleInterface)
private
FProp1: integer;
public
function GetProp1 = FProp1;
procedure SetProp1 = FProp1;
end;

So, dear reader, what do you think? If you like the suggestion, open QC #30533 and vote for it.

Friday, June 23, 2006

GpProfile, encore

Once upon a time, there was a nice little profiler called GpProfile. Admittedly, it could be better, faster, and more user friendly, but I wrote it and I liked it. That was then.

But now, now is seven years later and that is in programmer's years, which is 49 normal years. Or something like that. And those years are showing, trust me if I tell you.

So there came a time to rewrite the damn thing from scratch. I'm a great fan of Office 2007 GUI and I like to have my internals modularized and organised (OK, not really my internals ...) so the new version will have a nice clean look and even nicer and cleaner source code.

Just for teaser, here is the old interface:
GpProfile 1.3

and here's the new one:
GpProfile 2.0 alpha

There is always a beginning ...

And it is now. 20060623T201433 UTC.

(Hey, time is important. And time is interesting. I wrote an article on time a long time ago.)

If you want to know more about me, read the profile. Or Google me. Or ask. I'll tell. Or not.

Enough for now.

... and there is always an end. But we have yet to see when that will be.