Friday, December 16, 2005

Installing Delphi 2006

A few days ago, I received my shiny, fresh, the latest and greatest Delphi 2006 in a beautiful package. Very exciting! With much regret, I had to put it aside because I was busy at work and I simply didn't have time to play with it yet. I put it on my table, and I squinted at it occasionally while working on my current project using Delphi 7.

Then yesterday evening I finally got around to giving it a try. Needless to say, I was very disappointed when I couldn't install it right away. I had the following problems:

First, I got InstallShield error saying something like "Failed to extract file 'Dll_.ini' from the binary table".
I googled for the error message and came up with a solution: run DCOM configuration utility (dcomcnfg.exe) and grant the necessary user(s) and/or group(s) the default launch permissions.
Initially, I only included the local Administrators group (which my interactive user account is a member of) and also specifically my interactive user account (which was probably unnecessary). This solved the above problem but the installation still failed a bit later... with a message roughly like "Installation interrupted, nothing installed". I checked my running processes and noticed that Windows installer (msiexec.exe) is running under local SYSTEM account. So I granted the default launch permissions to this account, too.

That solved the problem and I have now successfully installed Delphi 2006!
W00t! It feels like Christmas already! ;-)

Wednesday, September 14, 2005

Could not find server in ObjectManager list

You can register your DataSnap appserver as stateless by calling RegisterPooled in the overriden UpdateRegistry class method of your remote data module.The problem with the way httpsrvr.dll manages stateless objects is that, ehm, it's stateful. ;-) More precisely, it's stateless only within a single IIS session.

The Object Manager uses a stringlist of class IDs and each item contains a list of instances of that class. Here is a brief summary of what happens between the client (TWebConnection) and the server (httpsrvr), in case of stateless/pooled appservers:
  1. The client sends asCreateObject request with the appserver's CLSID to the server. (see TStreamedConnection.DoConnect).
  2. The server checks its list of CLSIDs (creating a new item if it does not exist) and return its index within the list back to the client. No instance of the class is created at this point.
  3. The client stores the returned integer value which is used to identify the class in subsequent asInvoke (method call) requests.
  4. The client sends a asInvoke request to execute a method remotely on the appserver. (The communication between a TClientDataSet and a TProvider also works via remote method calls, the relevant methods are defined in the IAppServer interface which is implemented by all TRemoteDataModule descendants.)
  5. The server checks its list of CLSIDs, finds the entry and checks its instance pool for any unlocked (ie. not currently processing a request) instance. In case no idle instance can be found at the moment, it will create a new one, up to the maximum size of the pool you specified in your RegisterPooled call. (In case all instances are locked and the pool is already at its maximum size, the server will return a "Server is busy" error.)
  6. The client may send additional asInvoke requests as needed. The server dispatches the calls to any currently idle instance in the appropriate pool. Hence, your appserver logic must be stateless, ie. it cannot assume that any context is preserved between the calls since they may be executed by different instances.
  7. Cleanup on the server is performed by a separate garbage collector thread which releases any unused instances after a specified timeout. Even if no instance is any longer active (the pool size drops to zero), the item in the list of CLSIDs remains unchanged so clients may issue additional requests; these will be served by instances created on demand.
  8. When the client is finished with the appserver, it will send an asFreeObject request (see TDataDispatch.Destroy which is called due to reference counting). If the appserver has been registered as stateless, the server does not release any instance at this point - rather, the instances are kept in the pool, ready to serve additional requests or eventually be released by the garbage collector.

But what happens if you restart IIS anywhere after step 3? Sure, upon receiving another HTTP request with a relevant URL, IIS will load and run httpsrvr.dll again, but the Object Manager's list of CLSIDs is empty after a fresh start; the integer value sent by the client makes no sense since it cannot be resolved to a CLSID. This is the case when the server sends the "Could not find server in ObjectManager list" error back to the client. In an even worse case, if other clients have sent requests in the meantime, the list indexes may be different so the call will be dispatched to an instance of a different class, which will probably result in some COM exception because of the (very probable) typeinfo mismatch. (In a yet worse scenario, think about what happens when the method dispatch IDs and signatures of the two different appservers match by coincidence. ;-)) In either case, from the client's point of view, important state information (the association between the CLSID and its token) is lost.

So how can this problem be solved? I can imagine two options:

  1. Modify the DataSnap code so it never uses the tokens. Let the client pack the CLSID with every call. The disadvantages of this approach are:
    • GUIDs take 16 bytes, as opposed to 4 bytes for an integer.
    • All your client executables out there will become obsolete/incompatible with your appservers; you'll have to recompile and redistribute them.
  2. Modify httpsrvr.dll so it ensures that the token values don't change from one IIS session to another. This can be done by preloading the list in the initialization and not on demand so the index values are not dependent on the order in which clients send requests.

I have chosen the latter option. To preload the list of registered appservers, I use the (slightly modified) GetMIDASAppServerList procedure from the MConnect unit. The thousands of already distributed client applications will continue working as before; in addition, I can restart my web server between their calls without interrupting their work.

Saturday, August 13, 2005

Hallvard's Blog: The ultimate Delphi IDE start-up hack

Hallvard's Blog: The ultimate Delphi IDE start-up hack
Petr Vones has written a hack (a patch for rtlxx.bpl) to turn off duplicate unit checking which will improve Delphi IDE start-up speed. If you're going to use the patch, be careful - make sure that the packages which you load into your Delphi IDE do not contain duplicate units.

Tuesday, February 08, 2005

HTTPSRVR with Apache

Interesting: HTTPSRVR with Apache. On Windows only, of course. I should try it when I find the time.