1

I am running a ISAPI service which runs with IdHTTPWebBrokerBridge (for debugging as standalone EXE) as well as in Apache with mod_isapi (in productive enviroment).

While logging some stuff at the destruction of the web module, I found following problem:

unit LogFactory;

...

initialization
  GlobalLogFactory := TMyLogFactory.Create;
finalization
  FreeAndNil(GlobalLogFactory);
end.

-

unit MyWebModuleUnit;

...

uses LogFactory;

procedure TMyWebModule.WebModuleDestroy(Sender: TObject);
begin
  Assert(Assigned(GlobalLogFactory)); // <-- failure
  GlobalLogFactory.GetLogger('D:\test.txt').LogLine('test'); // <-- Nullpointer Exception
end;

LogFactory.pas creates the object GlobalLogFactory on its initialization and destroys it on its finalization.

But LogFactory.pas:finalization gets called BEFORE TMyWebModule.WebModuleDestroy , since it was included by this unit only, and so the finalization will be done in reverse order.

How can I ensure that my GlobalLogFactory will be correctly freed (i.e. FastMM will not warn about a memory leak), but at the same time, I want to give all destruction/finalization-procedures the chance to log something?

One Workaround would be to include LogFactory.pas explicitely in the DPR file as the first unit. But I don't like that very much, since this Log-Unit should be used in many projects and it should be useable by simply including it in the unit where you need to log something. Putting this log unit in every DPR which might want to log something in future, is a big effort and forgetting it might cause problems for the developers who are not knowing what I did/require.

Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67
  • Is the `uses LogFactory` located in `interface` section? My experience tells me that the unit unloading is unpredictable for units referenced in the `implementation` section. – AlexSC Jun 18 '14 at 12:42
  • Regarding your last paragraph: Log4J (the inspiration for Log4Delphi and Log4D) checks if the framework has been configured properly, and prints a warning on the console if the developer forgot to do so. – mjn Jun 18 '14 at 13:58

2 Answers2

2

Instad of using a class instance in a global variable use an interface and a function to get that.

unit LogFactory;

interface

type
  ILogFactory = interface
    [{GUID}]
    function GetLogger(...) : TLogger;
    ...
  end;

  TLogFactory = class( TInterfacedObject, ILogFactory )
    function GetLogger(...) : TLogger;
  end;

function GlobalLogFactory : ILogFactory;

implementation

var
  _LogFactory : ILogFactory;

function GlobalLogFactory : ILogFactory;
begin
  if not Assigned( _LogFactory ) then
    _LogFactory := TLogFactory.Create;
  Result := _LogFactory;
end;

{ TLogFactory Implementation }

initialization

// nothing needed

finalization

// nothing needed

end.

There is no need for any initialization or finalization at all.

Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
  • This is exactly the same as explicit tidy up in finalization. The global interface variables go out of scope when the unit is finalized. The unit still has finalization code, it's just implicitly generated by the compiler. In other words, to be precise, the object behind `_LogFactory` is destroyed in the finalization section of your `LogFactory` unit. – David Heffernan Jun 18 '14 at 14:51
  • Hm... and what happens if I call the function `GlobalLogFactory` of the already finalized unit? Will the reading of `_LogFactory` be a use-after-free then resp. an access to an illegal pointer? – Daniel Marschall Jun 18 '14 at 14:53
  • As I said, it is exactly the same as if you explicitly created and destroyed the object. In this case, the code in the answer would re-create the object, and assign it to the global variable that had gone out of scope, thus leaking it. – David Heffernan Jun 18 '14 at 15:42
  • @Sir I'm not sure I made myself clear. You understand that you unit still contains finalization code that destroys the object? – David Heffernan Jun 18 '14 at 22:14
0

If you want

  1. Any unit in your program to be able to log, and
  2. Logging to be available during finalization, and
  3. The logging code to require tidy up,

then you don't have many options. One option is to include the log unit very early in the .dpr file. I don't know why you regard that to be a problem. It's the standard way to achieve your goals. It's the technique used by external memory managers for example. Delphi developers are familiar with the concept. You tell the developers what they need to do, and if they don't follow your instructions, the program does not work. That is their problem.

If you still cannot face doing it this way then I see one alternative. Arrange for the logger initialization to happen before any other initialization in your code. And the finalization after all other finalization. You can do that by putting the logger implementation into an external module that is linked with load time linking.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490