🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: borland.public.cppbuilder.database.ado
Article #169

Re: TADOQuery Memory Leak

#169
From: Clayton Arends
Date: Mon, 30 Apr 2007 14:42
408 lines
11368 bytes
I found time off and on during the week to test this issue.  I made two test 
applications.  One in BCB6 and one in BDS2006 Delphi personality.  Both 
applications were built standalone.  Both applications yielded almost 
identical results.  Memory consumption was measured using a custom memory 
usage function and the Windows Task Manager.  I have included my BCB6 test 
code at the end of this post if anyone wishes to double-check my test and 
results.

The application algorithm constructs 10 threads.  After 500 ms it terminates 
those threads and then creates 10 more threads.  There are 3 testing levels 
that perform progressively more tasks:

- Test 1: Construction and destruction of the ADO objects does not result
  in memory leaks.

- Test 2: Opening the ADO connection results in a very slow memory leak.

- Test 3: Opening the ADO query results in a slightly faster yet still
  slow memory leak.

On test 3 I let the application run over night.  Over 900,000 pairs of ADO 
objects were created, opened, and destroyed.  Memory use was around 95MB and 
continued to jump a few K every few minutes.

At one point, to rule out the VCL implementation of ADO, I rewrote the test 
to use the raw ADO interface classes (_Connection, and _RecordSet) with 
similar memory leak results.

I performed some general searches for ADO memory leaks and found some 
scattered references.  Many threads concluded that the leaks were caused by 
misuse of the ADO objects.  Some concluded that certain versions of ADO 
drivers were causing the leaks.  A majority had no conclusions.  It is hard 
to know for sure what is going on here.  However, the fact that the leak 
doesn't happen until the ADO objects are put to use tells me that the leak 
is somewhere outside of the domain of the VCL memory manager.

- Clayton

  // Unit1.h
  #ifndef Unit1H
  #define Unit1H
  //-----------------------------------------------------------------------
  #include <Classes.hpp>
  #include <Controls.hpp>
  #include <ExtCtrls.hpp>
  #include <StdCtrls.hpp>
  //-----------------------------------------------------------------------
  class TForm1 : public TForm
  {
    __published:
    TLabel *Label1;
    TLabel *Label2;
    TButton *Level1Btn;
    TButton *Level2Btn;
    TButton *Level3Btn;
    TButton *StopBtn;
    TTimer *Timer1;
    void __fastcall Level1BtnClick(TObject *Sender);
    void __fastcall Level2BtnClick(TObject *Sender);
    void __fastcall Level3BtnClick(TObject *Sender);
    void __fastcall StopBtnClick(TObject *Sender);
    void __fastcall Timer1Timer(TObject *Sender);
    private:
      void SetLabel1Caption();

    public:
      __fastcall TForm1(TComponent* Owner);
  };

  extern TForm1 *Form1;

  #endif


  // Unit1.cpp
  #include <vcl.h>
  #pragma hdrstop

  #include "Unit1.h"
  #pragma package(smart_init)
  #pragma resource "*.dfm"
  //-----------------------------------------------------------------------
  #include <ADODB.hpp>
  #include <DateUtils.hpp>
  #include <SyncObjs.hpp>
  //-----------------------------------------------------------------------
  #include <memory>
  //-----------------------------------------------------------------------
  #include <ComObj.hpp>
  //-----------------------------------------------------------------------

  TForm1 *Form1;

  class auto_CoInitializeEx
  {
    public:
      auto_CoInitializeEx()
      { ::CoInitializeEx(NULL, COINIT_MULTITHREADED); }
      ~auto_CoInitializeEx()
      { ::CoUninitialize(); }
  };

  class TThreadTestDB : public TThread
  {
    private:
      TADOConnection* fConnection;
      TADOQuery* fQuery;

      int TestLevel;

      void CreateObjects();
      void DeleteObjects();

    protected:
      virtual void __fastcall Execute();

    public:
      __fastcall TThreadTestDB(int ATestLevel);
      __fastcall ~TThreadTestDB();
  };

  TCriticalSection* Sync;
  unsigned __int64 CreatedObjectCount = 0;
  unsigned __int64 ThreadCount = 0;

  __fastcall TThreadTestDB::TThreadTestDB(int ATestLevel)
    : TThread(true), TestLevel(ATestLevel)
  {
    Sync->Acquire();
    ++ThreadCount;
    Sync->Release();

    FreeOnTerminate = true;
  }

  __fastcall TThreadTestDB::~TThreadTestDB()
  {
    Sync->Acquire();
    --ThreadCount;
    Sync->Release();

    DeleteObjects(); // In case of early termination due to exception
  }

  void TThreadTestDB::CreateObjects()
  {
    Sync->Acquire();
    ++CreatedObjectCount;
    Sync->Release();

    fConnection = new TADOConnection(NULL);
    fConnection->LoginPrompt = false;
    fConnection->ConnectionString =
      AnsiString(
        "Provider=Microsoft.Jet.OLEDB.4.0;"
        "Data Source=Chapter2.mdb;"
        "Persist Security Info=False");

    fQuery = new TADOQuery(NULL);
    fQuery->Connection = fConnection;
    fQuery->ParamCheck = false;
  }

  void TThreadTestDB::DeleteObjects()
  {
    delete fQuery, fQuery = NULL;
    delete fConnection, fConnection = NULL;
  }

  void __fastcall TThreadTestDB::Execute()
  {
    auto_CoInitializeEx auto_coinit;

    while (!Terminated)
    {
      CreateObjects();

      if (TestLevel > 1)
      {
        fConnection->Open();

        if (TestLevel > 2)
        {
          fQuery->SQL->Text = "select * from product";
          for (fQuery->Open();
               !fQuery->Eof && !Terminated;
               fQuery->Next())
          {
          }

          fQuery->Close();
        }

        fConnection->Close();
      }

      if (TestLevel == 1)
      { // Only sleep if performing the first test
        Sleep(10);
      }

      DeleteObjects();
    }
  }

  /* GetMemoryInUse
   *
   * Retrieves the rough amount of memory in use by the application.
   */
  unsigned __int64 GetMemoryInUse()
  {
    unsigned __int64 memInUse = 0;
    MEMORY_BASIC_INFORMATION mbi;
    void* address = NULL;
    memset(&mbi, 0, sizeof(MEMORY_BASIC_INFORMATION));
    while (::VirtualQuery(address, &mbi, sizeof(mbi)) == sizeof(mbi))
    {
      if (mbi.State == MEM_COMMIT && mbi.Type == MEM_PRIVATE)
        memInUse += mbi.RegionSize;
      address = (void*) (((int) mbi.BaseAddress) + mbi.RegionSize);
      memset(&mbi, 0, sizeof(MEMORY_BASIC_INFORMATION));
    }
    return memInUse;
  } // GetMemoryInUse

  static const MaxThreads = 10;
  TThreadTestDB* Threads[MaxThreads];

  void StartEm(int Level)
  {
    for (int index = 0; index < MaxThreads; ++index)
    {
      Threads[index] = new TThreadTestDB(Level);
      Threads[index]->Resume();
    }
  }

  void StopEm()
  {
    for (int index = 0; index < MaxThreads; ++index)
    {
      TThread* thread = Threads[index];
      if (thread)
      {
        Threads[index] = NULL;
        thread->Terminate();
      }
    }
  }

  __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
  {
    Sync = new TCriticalSection;
  }

  void __fastcall TForm1::Level1BtnClick(TObject *Sender)
  {
    SetLabel1Caption();
    Timer1->Tag = 1;
  }

  void __fastcall TForm1::Level2BtnClick(TObject *Sender)
  {
    SetLabel1Caption();
    Timer1->Tag = 2;
  }

  void __fastcall TForm1::Level3BtnClick(TObject *Sender)
  {
    SetLabel1Caption();
    Timer1->Tag = 3;
  }

  void TForm1::SetLabel1Caption()
  {
    Label1->Caption = "Started: " + Now().DateTimeString();
  }

  void __fastcall TForm1::StopBtnClick(TObject *Sender)
  {
    Timer1->Tag = 0;
    StopEm();
  }

  __int64 lastmax = 0;
  __int64 lastmin = 0;
  TDateTime lastmaxtime(0);

  void __fastcall TForm1::Timer1Timer(TObject *Sender)
  {
    if (Timer1->Tag > 0)
    {
      Sync->Acquire();
      int threadCount = ThreadCount;
      Sync->Release();

      StopEm();
      if (threadCount > 0)
      {
        Timer1->Interval = 100;
      }
      else
      {
        StartEm(Timer1->Tag);
        Timer1->Interval = 500;
      }
    }

    __int64 mem = GetMemoryInUse();
    Caption = String(ThreadCount) + " active threads : " +
      FormatFloat("#,##0", mem) + " bytes";

    static String ratemsg;
    if (mem > lastmax)
    { // Determines the minimum used since the last time the max changed
      ratemsg =
        "Last Min: " + FormatFloat("#,##0", lastmin) +
            " Max: " + FormatFloat("#,##0", mem) +
        "\nLast Increase: " + lastmaxtime.TimeString();

      lastmaxtime = Now();
      lastmax = mem;
      lastmin = 0;
    }

    if (lastmin == 0 || mem < lastmin)
    {
      lastmin = mem;
    }

    Label2->Caption = ratemsg +
        "\nCreated Objects: " + String(CreatedObjectCount);
  }


  // Unit1.dfm
  object Form1: TForm1
    Left = 374
    Top = 114
    Width = 435
    Height = 264
    Caption = 'Form1'
    Color = clBtnFace
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = []
    OldCreateOrder = False
    PixelsPerInch = 96
    TextHeight = 13
    object Label1: TLabel
      Left = 24
      Top = 12
      Width = 32
      Height = 13
      Caption = 'Label1'
    end
    object Label2: TLabel
      Left = 24
      Top = 72
      Width = 32
      Height = 13
      Caption = 'Label2'
    end
    object Level1Btn: TButton
      Left = 24
      Top = 36
      Width = 75
      Height = 25
      Caption = 'Test Level 1'
      TabOrder = 0
      OnClick = Level1BtnClick
    end
    object Level2Btn: TButton
      Left = 108
      Top = 36
      Width = 75
      Height = 25
      Caption = 'Test Level 2'
      TabOrder = 2
      OnClick = Level2BtnClick
    end
    object Level3Btn: TButton
      Left = 192
      Top = 36
      Width = 75
      Height = 25
      Caption = 'Test Level 3'
      TabOrder = 3
      OnClick = Level3BtnClick
    end
    object StopBtn: TButton
      Left = 272
      Top = 36
      Width = 75
      Height = 25
      Caption = 'Stop'
      TabOrder = 1
      OnClick = StopBtnClick
    end
    object Timer1: TTimer
      Interval = 500
      OnTimer = Timer1Timer
      Left = 4
      Top = 4
    end
  end 
Technical Details
Message-ID: <4636005c$1@newsgroups.borland.com>
Path: arc1-ca.pugleaf.net!g2news1.google.com!news1.google.com!border1.nntp.dca.giganews.com!border2.nntp.dca.giganews.com!nntp.giganews.com!newspeer1.nwr.nac.net!newsfeeds.ihug.co.nz!ihug.co.nz!newsgroups.borland.com!not-for-mail
References: <462d7a19$1@newsgroups.borland.com> <462f9b39$1@newsgroups.borland.com> <462ffa42$1@newsgroups.borland.com> <463018d2$1@newsgroups.borland.com>

Show Full Headers
Complete Article Headers
X-Google-Thread: 10698b,f6b9f007fd37682e
X-Google-Attributes: gid10698b,public
X-Google-Language: ENGLISH,ASCII-7-bit
From: "Clayton Arends" <nospam_claytonarends@hotmail.com>
Newsgroups: borland.public.cppbuilder.database.ado
References: <462d7a19$1@newsgroups.borland.com> <462f9b39$1@newsgroups.borland.com> <462ffa42$1@newsgroups.borland.com> <463018d2$1@newsgroups.borland.com>
Subject: Re: TADOQuery Memory Leak
Date: Mon, 30 Apr 2007 07:42:56 -0700
Lines: 410
X-Priority: 3
X-MSMail-Priority: Normal
X-Newsreader: Microsoft Outlook Express 6.00.2900.3028
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.3028
X-RFC2646: Format=Flowed; Response
NNTP-Posting-Host: 71.93.4.10
Message-ID: <4636005c$1@newsgroups.borland.com>
X-Trace: newsgroups.borland.com 1177944156 71.93.4.10 (30 Apr 2007 07:42:36 -0700)
Path: arc1-ca.pugleaf.net!g2news1.google.com!news1.google.com!border1.nntp.dca.giganews.com!border2.nntp.dca.giganews.com!nntp.giganews.com!newspeer1.nwr.nac.net!newsfeeds.ihug.co.nz!ihug.co.nz!newsgroups.borland.com!not-for-mail