Potential "Bugs", due to the unsynchronized unit finalization

I have a Web Service Server application that uses Aurelius. The application when shutdown had memory leak and access violation. After looking into Aurelius code, I nailed down the cause:

Aurelius has a lot of static global vars that are destroyed in the defining Unit's finalization section, and this may sometime cause problems, for example,

TAureliusModelEvents.Destroy internally will access the global ExplorerManager, which could possibly already been destroyed at the app shutdown

Likewise, TSQLiteDatabase.Destroy internally calls f_sqlite3_close, but the sqlite3.dll may already be unloaded

Again - all these problems are caused by the Delphi system unloading process (calling the finalization section), while at the same time, some Aurelius objects wrapped in interface (hence reference counted) are destroyed after those global vars destruction.

For now, I made the following "fixes" myself, but @wlandgraf probably can come out with some better systematic ways:

Aurelius.Drivers.SQLite.Import.pas

procedure FinalizeSQLiteUnit;
begin
  if LibraryHandle <> 0 then begin
    FreeLibrary(LibraryHandle);
    LibraryHandle := 0;  // <------ set this to 0
    f_sqlite3_close := nil; // <-----set this to nil
  end;
end;

Aurelius.Drivers.SQLite.Classes.pas

destructor TSqliteDatabase.Destroy;
begin
  if (FHandle <> nil) and Assigned(f_sqlite3_close) then  // <---- check if f_sqlite3_close is nil or not
  	f_sqlite3_close(FHandle);
  inherited;
end;

Aurelius.Comp.ModelEvents.pas

procedure TAureliusModelEvents.UnsubscribeEvents;
begin
  if csDesigning in ComponentState then Exit;

  OnCollectionItemAdded := nil;
  OnCollectionItemRemoved := nil;
  OnDeleted := nil;
  OnDeleting := nil;
  OnInserted := nil;
  OnInserting := nil;
  OnSQLExecuting := nil;
  OnUpdated := nil;
  OnUpdating := nil;

  { // Comment these out.
  Events.OnInserting.Unsubscribe(FInsertingProc);
  Events.OnInserted.Unsubscribe(FInsertedProc);
  Events.OnUpdating.Unsubscribe(FUpdatingProc);
  Events.OnUpdated.Unsubscribe(FUpdatedProc);
  Events.OnDeleted.Unsubscribe(FDeletedProc);
  Events.OnCollectionItemAdded.Unsubscribe(FCollectionItemAddedProc);
  Events.OnCollectionItemRemoved.Unsubscribe(FCollectionItemRemovedProc);
  Events.OnSQLExecuting.Unsubscribe(FSQLExecutingProc);
  Events.OnDeleting.Unsubscribe(FDeletingProc);
  }
end;

There is no need for such changes. What you must do is actually clean your interface references in the same scope you created them - probably you have kept global references to them.

By commenting or ignoring the proper closing above you are just changing one problem by the other - f_sqlite3_close won't be called, events won't be subscribed, etc.

This is NOT true. If the DLL is still loaded, f_sqlite3_close is always called. Only if the DLL is unloaded, then f_sqlite3_close won't be called. My suggested code fix won't cause any problem or break any existing code:

procedure FinalizeSQLiteUnit;
begin
  if LibraryHandle <> 0 then begin
    FreeLibrary(LibraryHandle);
    LibraryHandle := 0;  // <------ set this to 0
    f_sqlite3_close := nil; // <-----set this to nil
  end;
end;

destructor TSqliteDatabase.Destroy;
begin
  if (FHandle <> nil) and Assigned(f_sqlite3_close) then  // <---- check if f_sqlite3_close is nil or not
  	f_sqlite3_close(FHandle);
  inherited;
end;

Again, the suggested code change just adds extra protection for some edge situations. It won't cause any problems or break any existing code. Hope it makes sense to you.

I agree with you. This may cause problems. Hence I withdraw my suggestion on this matter, with apology.

1 Like