Article View: borland.public.cppbuilder.database.ado
Article #169Re: TADOQuery Memory Leak
From: Clayton Arends
Date: Mon, 30 Apr 2007 14:42
Date: Mon, 30 Apr 2007 14:42
408 lines
11368 bytes
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-mailReferences:
<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