5

I have an application that uses statically-linked runtime packages as well as designtime packages that use them. For some reason the code in any unit finalization section is not being run at runtime (I can't tell when this started happening).

finalization
  ShowMessage('Goodbye');
end.

Shutting Delphi down shows the message, but not when my application shuts down. It gets weirder in that if I put a breakpoint on ShowMessage, it breaks there but does not execute the line. If there are multiple lines in the finalization, the debugger stops on the first line, does not execute it and then jumps to the end.

procedure ProcOne;
begin
  SomeObject.Free; // Debugger does not enter or stop here
  SomeObject := nil;
end;

finalization
  ProcOne; // Debugger stops here, doesn't execute, jumps to "end."
  ProcTwo; // Every line has a blue dot
  ShowMessage('Bye');
end.

The call stack on ProcOne breakpoint shows @Halt0 => FinalizeUnits => MyPackage.MyUnit.Finalization.

If I include the unit in an application that doesn't use packages, everything executes properly.

Does anyone have an idea what could be causing this?

EDIT:

Thanks to Allen Bauer's comment pointing in the right direction, I have managed to isolate the problem. It seems the problem arises if an application is built with a runtime package, then dynamically loads another package that also references that package and unit.

I have created a test project that demonstrates the problem: TestFinalization

Does anyone know the reason for this and/or a workaround? You normally might not notice that your finalization is not being run until you notice that external resources are not being cleaned up.

avenmore
  • 2,809
  • 3
  • 33
  • 34
  • Are you sure that `ProcOne` and `ProcTwo` do not throw any exceptions? – jpfollenius Sep 27 '11 at 09:36
  • Yes, whatever is there doesn't execute. E.g. MyInt := 5; if it was 3 when the breakpoint lands, after stepping over it, it is still 3. – avenmore Sep 27 '11 at 09:40
  • What do you mean by statically linked runtime packages? – David Heffernan Sep 27 '11 at 09:46
  • I mean the package containing the unit is specified in the .exe's options "Build with runtime packages". The application also loads other (not specified) packages at runtime to facilitate a plug-in architecture. – avenmore Sep 27 '11 at 09:52
  • It looks like your source code is out of sync with the binaries. Try rebuilding **all** your binaries - packages and executables. – Ondrej Kelle Sep 27 '11 at 10:09
  • @TOndrej: that was my first reaction, but it happens on any machine which made it seem unlikely. To be certain I have tried removing all copies of the unit except one, and made sure that the freshly-compiled BPL was being used and the problem remains. Also, after the breakpoint in my unit, continuing to "step-into" the finalization of other units (Indy, DevExpress, MAPI, etc.) all exhibit the same behaviour. – avenmore Sep 27 '11 at 10:36
  • I've also done a full build and used AQTime to try to profile the finalization, but any referenced procedures are shown not to be called. – avenmore Sep 27 '11 at 10:41
  • Might be linking with an out-of-date .dcp. Try a search & destroy on all binaries manually, then rebuild all once more. – Ondrej Kelle Sep 27 '11 at 10:46
  • Is the Initialization being hit, or is that missed too? – Chris Thornton Sep 27 '11 at 12:03
  • This question about Initialization order might help you in finding the cause: http://stackoverflow.com/questions/4076425/can-i-determine-the-order-in-which-my-units-have-been-initialized/4077426#4077426 – Remko Sep 27 '11 at 12:40
  • Did you try in Debug mode with Optimization=False? – Remko Sep 27 '11 at 14:28
  • @Remko: Yes, tried different build configs. I've narrowed it slightly to two dynamically-loaded packages (out of 24). If either one of those gets loaded, the finalization code does not run properly (anywhere!), so I'm currently trying a brute-force approach of deleting things that catch my eye to see if they make a difference. – avenmore Sep 27 '11 at 14:39
  • @Warren P: The only place it is declared is in the Indy unit IdASN1Util. I'll follow up on that. – avenmore Sep 27 '11 at 14:42
  • 1
    Also, check your .dpk files by hand in a text editor, and look for this: `{$WeakPackageUnit ON}` – Warren P Sep 27 '11 at 14:45
  • @Warren P: Removing that one instance of the compiler directive did not make a difference. – avenmore Sep 27 '11 at 14:50
  • @Warren P: Used GExperts grep on *.pas;*.dpr;*.inc;*.dpk from the project root. – avenmore Sep 27 '11 at 14:51
  • 1
    During the executing of your application, do you dynamically load other packages? Do those packages also reference the same problem package/unit? – Allen Bauer Sep 27 '11 at 18:01
  • 1
    @Allen Bauer: Yes, other packages that reference the packages that contain the problem units are dynamically loaded at runtime. But not all of the dynamically loaded packages cause the problem, even though all of the loaded packages reference the static packages. And the problem is not just in the one unit, but NO code in finalizations of any unit get executed. – avenmore Sep 28 '11 at 07:28

2 Answers2

10

Make sure you're calling UnloadPackage for each dynamically loaded package before shutdown. If you're simply calling UnloadLibrary (or simply relying on the OS to unload them), then the finalizations for that the units in that package and all the units from other packages aren't being called. Initializations and finalizations are done using a reference counting system because in the face of dyanmically loaded packages, there is no way to know what units will be initialized and when. Only when you've balanced the finalization calls with the initialization calls will the last finalization call actually execute the code block in the finalization section. Likewise only the first call to the initialization section will actually execute the code block.

Initializations/finalizations are done using a compiler-generated table for a given module. When you build an exe or dll linked with packages, this table contains references to all the units that are actually used, even those from the linked packages. Note that only the units actually referenced are actually initialized. IOW, if you have 100 units in PackageA and the exe only references one of them, then only that unit and any units it uses will be initialized.

For dynamically loaded packages, there is really no way to know what units will actually be used, so the compiler generates the init/finit table as if every unit were initialized. This table is not processed upon loading of the package during the call to LoadLibrary, but rather is handled by calling a special export called Initialize(). The LoadPackage function ensures that this function is called. This table only ensures that all the units in the loading package are initialized. Only the units actually touched in any other package are initialized, similar to the exe/dll case I mentioned above. UnloadPackge does the reverse, and calls special export Finalize() before calling UnloadLibrary().

Finally, if you've made changes to uses lists of any packaged units and only rebuild the package, you can run into confusing cases where initializations/finalizations may not get called even though your units within a given package properly "use" each other. This is because the init/finit is controlled by the loading module and not from within itself. Only in the case where a package is explicitly loaded using LoadPackage will every unit in that package (and that package only) be initialized/finalized.

Allen Bauer
  • 16,657
  • 2
  • 56
  • 74
  • Thank you for your time and detailed explanation. Is it necessary to call UnRegisterModuleClasses() first for any classes that have registered by a dynamically-loaded package? – avenmore Sep 29 '11 at 07:47
  • You should not have to call it, no. Classes will call AddModuleUnloadProc, in order to get unload notifications which will call UnRegisterModuleClasses(). – Allen Bauer Sep 29 '11 at 17:52
  • Just for interest, I found the line UnloadPackage() commented out for some reason. Scratching in our source archive this was done before 2003, so it has taken at least 8 years for anyone to notice that the finalizations are not run, and only noticed it now because a COM server was not being shut down on app exit. – avenmore Sep 30 '11 at 07:07
0

For anyone in the same situation as me, in light of Allen Bauer's answer:

I have a number of units which use initialization/finalization to self-register. In the spirit of Raymond Chen's advice I only run deregistration when it matters:

initialization
  RegisterUnit();
finalization
//In debug mode we track memory leaks so properly deregister
//In release mode the app is shutting down; do not waste time
//freeing memory that's going to be freed anyway
{$IFDEF DEBUG}
  UnloadUnit();
{$ENDIF}

I moved a bunch of these into packages but this broke core package finalization, as the question describes.

From Allen Bauer's answer it follows that you must call UnloadPackage() for all dynamically loaded packages, or you will not get proper finalization calls in the core.

But then I cannot use that optimization anymore. I have to painfully deregister every package on finalization because once I unload the package DLL the objects it registered in the core are going to be zombies.

This feels like a wasted effort. Unloading should be quick. I'd like to leave all dynamically loaded packages hanging until everything is simply destroyed.

What you can do instead is call FinalizePackage() on all dynamically loaded packages! This evens out the reference counters while leaving the packages loaded.

If a package employs this "skip deinit" trick its objects are going to remain alive until the process destruction. (It's the job of the package to make sure anything that can be called on them does not break). If it doesn't, it's fully deinitialized and what remains is an inert DLL.

This wouldn't work with packages that you plan on unloading truly dynamically of course.

himself
  • 4,806
  • 2
  • 27
  • 43