Subject Re: [IBO] AV when switching to another app
Author Helen Borrie
At 11:59 AM 6/05/2005 -0500, Toby Leonard wrote:
>I've been having some issues with profiling (using AQtime) an app that uses
>the Unified Interbase components, and figured I'd give IBO a try. The test
>system is XPSP2, D2005 Update 1, IBO Eval 4.5, and FB embedded
>I can run the code below in the IDE or standalone, and as long as I leave it
>focused until I close it, it performs as expected. However if I switch to
>another app while the main processing is going on or after I've stopped it,
>the app AVs at 00000000 trying to read 00000000. Under AQtime I get the AV
>Testing this, it looks like I can have as little as the TIB_Session
>created in
>the thread, and the AV occurs.
>(Yeah I know some of the stuff in here is bad coding practice, like the A.PM
>and Sleep(1) combo. I wouldn't normally do such things - this is just a
>throwaway test app.)
>Any help would be greatly appreciated. Thanks!

There are a few things...

1. Make absolutely certain that your app is loading the correct client
library. Since, by default, IBO looks for gds32.dll, you'll need to copy
fbclient.dll (located in firebird's bin directory) to the system32
directory and rename it.

2. The main thread-killer is that you can't run multiple threads over the
Windows local protocol (IPServer, Protocal cpLocal). Each thread, including
the main one, must use TCP/IP. For server-based apps, *usually* TCP/IP
local loopback is OK; if you are connecting via a terminal service, it
must be the node address of the server.

Tip :: With IBO, you'll get best results by ignoring the DatabaseName
property of your IB_Connection and setting Server, Path and Protocol instead.

3. You are creating your IB_Transaction too late! Create it and assign it
*before* you connect to the database.

4. Your IB_Cursor has no reference to the database. Explicitly set its
IB_Connection property. This and/or 3) will be the prime cause of the AV
being thrown on a nil reference.

5. You do not call Open to begin fetching via an IB_Cursor, since
IB_Cursor does not buffer the rows it fetches. Replace the Open call with

6. Although TIB_Dataset (IB_Cursor's ancestor) should take care of
starting a transaction if it's not started, it is always a good precaution
to test whether the transaction is started and take the required
action. For example, the transaction might be started but is waiting for
resolution of an exception; or it might have been committed and needs
explicitly restarting. This is particularly important when you are creating
and destroying threads.

7. The same applies to Prepare. Test whether the IB_Cursor is prepared
before calling First.

8. Your create and destroy code is suspect. Use 'self' when you create
the IB_Session; its destructor will take care of unwinding the objects it
created. If you go about willy-nilly calling Free on objects that are
still busy, you will come to grief. As an example, the IB_Cursor has a
SELECT request running, pulling one row at a time from the server's
buffer. You have to allow each of the objects to finish their jobs and
take care of their responsibilities in the correct sequence.

9. Because of the multi-threading, take care that your code is referencing
the correct IB_Session. The main application thread has its own
IB_Session, which is created by default. Once you have resolved the other
problems, it would be worth taking the trouble to use an explicit
IB_Session for the application, rather than lean on the default
session. Make this IB_Session the very first thing in the creation order.

10. Not everyone loves datamodules - but I do! I like to have one for the
application and one to wrap *each* threaded operation that I'm going to
instantiate. Then, it's a total breeze to create and destroy the thread
instances and to wind everything down when the application closes. You do
need to take care in the DPR file to ensure that these objects are not
created at startup.

11. Don't ignore your transaction! If a transaction is started, it must
be committed or rolled back in order to end it.

A few notes inline:

>unit Unit22;
> Forms, Controls, StdCtrls, Classes, ExtCtrls;
> TForm22 = class(TForm)
> Panel1: TPanel;
> Button1: TButton;
> LB: TListBox;
> procedure Button1Click(Sender: TObject);
> end;
> Form22: TForm22;
>{$R *.dfm}
> Windows, SysUtils, IB_Components, SyncObjs;
> TIBTestThread = class(TThread)
> private
> FDBSession: TIB_Session;
> FDBConnection: TIB_Connection;
> FDBTransaction: TIB_Transaction;
> protected
> procedure Execute; override;
> public
> constructor Create;
> destructor Destroy; override;
> end;
> KeepGoing: Boolean;
> TotalThreads: Cardinal;
> CS: TCriticalSection;
>constructor TIBTestThread.Create;
> inherited Create(False);
> FreeOnTerminate := True;
> FDBSession := TIB_Session.Create(nil);
> FDBConnection := TIB_Connection.Create(FDBSession);
> FDBConnection.IB_Session := FDBSession;
> FDBConnection.Path := ExtractFilePath(ParamStr(0)) + 'foo.fdb';

FDBConnection.Protocol := cp_TCPIP;
FDBConnection.Server := 'localhost';

> FDBConnection.Username := 'foo';
> FDBConnection.Password := 'bar';
> FDBConnection.Connect;

// Too late!

> FDBTransaction := TIB_Transaction.Create(FDBSession);
> FDBTransaction.IB_Connection := FDBConnection;
>destructor TIBTestThread.Destroy;
> CS.Enter;
> try
> Dec(TotalThreads);
> finally
> CS.Leave;
> end;

// wrong order. Disconnect must be given the opportunity to execute its
wind-down code. If you need to destroy anything, do it *after* Disconnect
has finished - and test the Connected property before destroying
anything. (By packaging everything in a datamodule, and taking proper care
with creation order, you can totally avoid all this stuff!!)

> FDBTransaction.Free;
> FDBConnection.Disconnect;
> FDBConnection.Free;
> FDBSession.Free;
> inherited;

There's probably more here than you need or want, but I hope it helps you
to pin things down somewhat!