Friday, June 8, 2018

Delphi fix up mechanism

There are a few methods and functions in Classes.pas unit which take care of something that I call "fix up mechanism". Just search for "fixup" inside Classes.pas and you will find them. The fix up mechanism exists to "fix" (really?) references to other components which are owned by a different container, when they are created at runtime, loaded from a DFM resource.

So, let's say you have a ADOQuery1 contained in DataModule1. Then you have Form1 which contains DataSource1 which in turn references ADOQuery1 through DataSet property. This relationship could be set at runtime as:

Form1.DataSource1.DataSet := DataModule1.ADOQuery1;

However, most Delphi developers choose to set this relationship at design time, using Object Inspector. This has some advantages - it requires zero code - but also its disadvantages - it requires that some mechanism "fixes" this reference at runtime. So, when you create Form1 at runtime, it also creates/loads DataSource1. Inside the DFM file you will find that DataSource1.DataSet contains a reference to DataModule1.ADOQuery1 - referenced by name. But when the form loads from DFM, the Delphi runtime library doesn't know how to set this property. The form (a DFM resource at this stage) only contains a string (DataModule1.ADOQuery1) but RTL needs to find the actual object and set the reference accordingly. That's the fix up mechanism mission.

Most Delphi developers never worried about this (they probably don't even know about it) because (a) it works transparently and very well and (b) it doesn't have any effect on performance of most applications. When I say "most" I mean, desktop applications. It definitely may have some - important - influence on multi-threaded applications created with Delphi because it uses a global lock, implemented as a TMultiReaderExclusiveWriterSynchronizer. If your application contains, for instance, TDataModules loading from DFM resources this effect can be serious.

Let's say you have some server application (it might be a DataSnap server, COM+, WebBroker, whatever) which loads DataModules from DFM files. For example, WebModules *are* DataModules. If all objects contained in that DataModule reference other objects contained in the same DataModule, you are also safe. However, if one component references another component in another DataModule, the fix up mechanism will need to do its job and the nasty global lock will occur. 

If your application receives several requests at the same time and 2 or more requests require the creation of one instance of such DataModule, the first one will acquire the global lock and the second will have to wait until the fix up finishes (and unlocks the global list), so it can proceed and lock the fix up list again. There is another - more complicated - scenario where this global lock might create some deadlock situation if you also have other global locks (e.g. Critical Sections) being used at the same time.

So, my first recommendation is "never link objects owned by different containers at design time in a multi-threaded application". If you link them via code it will perform better and also save you from a possible deadlock which - believe me - is very very hard to find and almost impossible to reproduce in a controlled environment. As a bonus you will never have that nasty surprise when you find out - in production, of course - that your DataSource1 is not pointing to DataSet1 anymore because, God knows how, the DataSet property is blank (who never experienced that?)...
Link objects via code!! OnCreate event of Forms and DataModules are good places to put that linkage code.

Sometimes I know that it is hard to change the whole application. Your application is ready, tested and you don't want to risk such a core change now. That's fine. If your application is an IntraWeb application you can turn on our - still experimental - new feature which replaces the global fix up mechanism completely, with a new, much more scalable and thread-friendly one. To turn it on you should set TIWServerControllerBase.FixupPatchEnabled* to true, before starting your application. The best place to do it is in your .DPR file. Example:

  TIWServerControllerBase.FixupPatchEnabled := True;

That should do it. Have in mind that it might need some adjustments in your application but, in general, it should work in most cases.

Available in IntraWeb 15 only.

1 comment:

cjmuller said...

Hi Alexandre,

I found references to this feature in IW 14. Does it not work in this version ?