Board index » cppbuilder » A comedy of errors

A comedy of errors


2007-02-24 03:39:16 AM
cppbuilder89
Hi all,
I am trying to track a bug in BDS 2006 regarding codeguard,
mutithreading, and memory management. I have been having problems with
what appear to be incorrect codeguard errors in my application. Clayton
Arends posted a very small sample project in b.p.c.thirdpartytools
that produced the same symptoms.
class TTestThread : public TThread
{
protected:
virtual void __fastcall Execute()
{
try
{
String str;
for (int index = 0; index < 1000; ++index)
str += ' '; // <-- (*)
}
catch (...)
{
}
}
public:
TTestThread() : TThread(false)
{
FreeOnTerminate = true;
}
};
void __fastcall TForm1::Button1Click(TObject *Sender)
{
for (int index = 0; index < 10; ++index)
new TTestThread;
}
His program did not reliably break under BDS 2006 (though he said it did
with BCB 6). While watch the event log I saw that the threads were
basically completing in on time slice so that each was terminated before
the next started. I increased the loop limit in the Execute method to
100,000 so each thread would take longer to execute, and the program now
reliably breaks when built and run with codeguard enabled (all options).
It runs until it gets an EOutOfMemory exception in one of the threads
(which doesn't take very long).
I was trying to track the cause of the problems using BDS 20006 and have
been hitting a continuous stream of errors that make it almost
impossible to get anywhere.
When the exception happened I break into the de{*word*81} and started poking
around to see what the state of each thread is. I'm using the Call Stack
pane and the Local Variables pane to see the loop counter value in each
thread.
I managed to collect the following before the IDE crashed.
Event Log:
Thread Start: Thread ID: 320. Process Project1.exe (3288)
Process Start: C:\Documents and Settings\DennisC\My
Documents\Borland Studio
Projects\codeguard_memory_bug\Debug_Build\Project1.exe. Base Address:
$00400000. Process Project1.exe (3288)
Module Load: Project1.exe. Has Debug Info. Base Address: $00400000.
Process Project1.exe (3288)
Module Load: ntdll.dll. No Debug Info. Base Address: $7C900000.
Process Project1.exe (3288)
Module Load: KERNEL32.dll. No Debug Info. Base Address: $7C800000.
Process Project1.exe (3288)
Module Load: rtl100.bpl. No Debug Info. Base Address: $51F20000.
Process Project1.exe (3288)
Module Load: OLEAUT32.dll. No Debug Info. Base Address: $77120000.
Process Project1.exe (3288)
Module Load: msvcrt.dll. No Debug Info. Base Address: $77C10000.
Process Project1.exe (3288)
Module Load: USER32.dll. No Debug Info. Base Address: $77D40000.
Process Project1.exe (3288)
Module Load: GDI32.dll. No Debug Info. Base Address: $77F10000.
Process Project1.exe (3288)
Module Load: ADVAPI32.dll. No Debug Info. Base Address: $77DD0000.
Process Project1.exe (3288)
Module Load: RPCRT4.dll. No Debug Info. Base Address: $77E70000.
Process Project1.exe (3288)
Module Load: ole32.dll. No Debug Info. Base Address: $774E0000.
Process Project1.exe (3288)
Module Load: VERSION.dll. No Debug Info. Base Address: $77C00000.
Process Project1.exe (3288)
Module Load: MPR.dll. No Debug Info. Base Address: $71B20000.
Process Project1.exe (3288)
Module Load: IMAGEHLP.dll. No Debug Info. Base Address: $76C90000.
Process Project1.exe (3288)
Module Load: WSOCK32.dll. No Debug Info. Base Address: $71AD0000.
Process Project1.exe (3288)
Module Load: WS2_32.dll. No Debug Info. Base Address: $71AB0000.
Process Project1.exe (3288)
Module Load: WS2HELP.dll. No Debug Info. Base Address: $71AA0000.
Process Project1.exe (3288)
Module Load: OLEACC.dll. No Debug Info. Base Address: $74C80000.
Process Project1.exe (3288)
Module Load: MSVCP60.dll. No Debug Info. Base Address: $76080000.
Process Project1.exe (3288)
Module Load: vcl100.bpl. No Debug Info. Base Address: $52000000.
Process Project1.exe (3288)
Module Load: MSIMG32.dll. No Debug Info. Base Address: $76380000.
Process Project1.exe (3288)
Module Load: COMCTL32.dll. No Debug Info. Base Address: $5D090000.
Process Project1.exe (3288)
Module Load: SHELL32.dll. No Debug Info. Base Address: $7C9C0000.
Process Project1.exe (3288)
Module Load: SHLWAPI.dll. No Debug Info. Base Address: $77F60000.
Process Project1.exe (3288)
Module Load: WINSPOOL.DRV. No Debug Info. Base Address: $73000000.
Process Project1.exe (3288)
Module Load: comdlg32.dll. No Debug Info. Base Address: $763B0000.
Process Project1.exe (3288)
Module Load: oledlg.dll. No Debug Info. Base Address: $7DF70000.
Process Project1.exe (3288)
Module Load: borlndmm.dll. No Debug Info. Base Address: $41000000.
Process Project1.exe (3288)
Module Load: cg32.dll. No Debug Info. Base Address: $0CD00000.
Process Project1.exe (3288)
Module Load: cc3270mt.dll. No Debug Info. Base Address: $32700000.
Process Project1.exe (3288)
Module Load: COMCTL32.dll. No Debug Info. Base Address: $773D0000.
Process Project1.exe (3288)
Module Load: MSCTF.dll. No Debug Info. Base Address: $74720000.
Process Project1.exe (3288)
Module Load: UxTheme.dll. No Debug Info. Base Address: $5AD70000.
Process Project1.exe (3288)
Thread Start: Thread ID: 3436. Process Project1.exe (3288)
Thread Start: Thread ID: 1872. Process Project1.exe (3288)
Thread Start: Thread ID: 3892. Process Project1.exe (3288)
Thread Start: Thread ID: 3916. Process Project1.exe (3288)
Thread Start: Thread ID: 1708. Process Project1.exe (3288)
Thread Start: Thread ID: 2588. Process Project1.exe (3288)
Thread Start: Thread ID: 712. Process Project1.exe (3288)
Thread Start: Thread ID: 3040. Process Project1.exe (3288)
Thread Start: Thread ID: 3924. Process Project1.exe (3288)
Thread Start: Thread ID: 3148. Process Project1.exe (3288)
First chance exception at $7C812A5B. Exception class EOutOfMemory
with message 'Out of memory'. Process Project1.exe (3288)
Call stack of Thread 3040 :
:7c812a5b kernel32.RaiseException + 0x52
:51f278d1 rtl100.@System@@NewAnsiString$qqri + 0x11
:00403BAB System::AnsiString::vprintf(this=:018AFF58,
format=:00405710, paramList=:018AFEE0)
:00403BDC System::AnsiString::sprintf(this=:018AFF58,
format=:00405710, paramList=????)
:00403B15 System::AnsiString::AnsiString(this=:018AFF58, src=' ')
:00402281 TTestThread::Execute(this=:00E776B8)
:51f5e8ff ; C:\WINDOWS\system32\rtl100.bpl
:51f27772 rtl100.@System@@Assert$qqrx17System@AnsiStringt1i + 0x4a
:7c80b683 ; C:\WINDOWS\system32\kernel32.dll
Local Variables in Thread 3040 TTestThread::Execute :
Name Value
this :00E776B8
Classes::TThread::FHandle 3924 (0x00000F54)
Classes::TThread::FThreadID 3040 (0x00000BE0)
Classes::TThread::FCreateSuspended false
Classes::TThread::FTerminated false
Classes::TThread::FSuspended false
Classes::TThread::FFreeOnTerminate true
Classes::TThread::FFinished false
Classes::TThread::FReturnValue 0 (0x00000000)
Classes::TThread::FOnTerminate {NULL,NULL}
Classes::TThread::FSynchronize { NULL, {NULL,NULL}, NULL }
FThread NULL
FMethod {NULL,NULL}
FSynchronizeException NULL
Classes::TThread::FFatalException NULL
str { " " }
Data :00F6418C " "
[0] ' ' 32 (0x20)
[1] ' ' 32 (0x20)
[2] ' ' 32 (0x20)
[3] ' ' 32 (0x20)
[4] ' ' 32 (0x20)
[5] ' ' 32 (0x20)
[6] ' ' 32 (0x20)
[7] ' ' 32 (0x20)
[8] ' ' 32 (0x20)
[9] ' ' 32 (0x20)
[10] ' ' 32 (0x20)
[11] ' ' 32 (0x20)
[12] ' ' 32 (0x20)
[13] '\0' 0 (0x00)
index 13 (0x0000000D)
All threads are at $7C90EB94 except
3924 at $0CD17BFA
3040 at $7C812A5B
3892 at $7C9010FF
Thread 320 (main thread) call stack:
:7c90eb94 ntdll.KiFastSystemCallRet
:77d49418 USER32.WaitMessage + 0xc
:52079340 vcl100.@Forms@TApplication@HandleMessage$qqrv + 0x1c
:00401883 WinMain( =:00400000, =NULL, =:00141F57, =9)
:32778e1b ; C:\WINDOWS\system32\CC3270MT.DLL
Thread 3436 call stack:
:7c90eb94 ntdll.KiFastSystemCallRet
:7c90e591 ntdll.NtSetEventBoostPriority + 0xc
:7c90110a ntdll.RtlLeaveCriticalSection + 0x1d
:0cd015d6 ; C:\PROGRA~1\Borland\BDS\4.0\Bin\CG32.DLL
:0cd01661 CG32.@_cg_SysGetMem + 0x9
:51f23bd6 rtl100.@System@@GetMem$qqri + 0xa
:00403BAB System::AnsiString::vprintf(this=:011AFF58,
format=:00405710, paramList=:011AFEE0)
:00403BDC System::AnsiString::sprintf(this=:011AFF58,
format=:00405710, paramList=????)
:00403B15 System::AnsiString::AnsiString(this=:011AFF58, src=' ')
:00402281 TTestThread::Execute(this=:00E77488)
:51f5e8ff ; C:\WINDOWS\system32\rtl100.bpl
:51f27772 rtl100.@System@@Assert$qqrx17System@AnsiStringt1i + 0x4a
:7c80b683 ; C:\WINDOWS\system32\kernel32.dll
When the IDE crashed it displayed the usual Windows crash dialog
(IDE_crash.png) with the message "Borland Developer Studio for Windows
has encountered a problem and needs to close. We are sorry for the
inconvenience." I tried to capture the "technical details" from the
dialog but did not find any way to do so (only the list of loaded
modules could be captured from the temp file the dialog created to send
to MS). I clicked send, waited for the report to be sent, and then
waited for BDS to be killed.
Instead, BDS displayed a series of error dialogs (IDE_errors.png) saying
it was out of memory in rtl100.bpl. After capturing a few I acknowledged
them all. After this most of the content of the BDS window was cleared
to a blank background.
I then got another error dialog reporting an access violation in
DelphiSpeedUp with a Send (or is it Details) button that is used to
create an automated incident report. I dutifully filled in the requested
information and clicked send. The AIR report failed
(IDE_report_fail.png) with an error about trying to put a string of 3740
bytes into a field that can only hold 40 bytes. So I gave up on that and
closed the AIR dialog.
Then The BDS window went completely white, no content at all, and
displayed another error dialog (IDE_app_error.png) saying that the
instruction at an address referenced memory at the same address and that
memory could not be read. It said to click the OK button to terminate
the program. Each time I did that it displayed another copy of the same
dialog.
After about 10 dialogs the IDE had redrawn part of its contents and then
displayed another new error dialog (IDE_after10Errors.png) saying an
unknown software exception had occurred in the application.
At this point I gave up and terminated the BDS process with the task
manager and continued.
I have posted all the screenshots mentioned above in the attachments
group under the same subject (A comedy of errors).
For me, this is a typical example of using BDS to debug an application.
It kind of works, but then fails spectacularly with a crashes,
phenomenal slowdowns, or silent hangs. It is very frustrating.
I will continue to try to figure out why codeguard causes this simple
multithreaded application to fail, and report an out of memory
condition, when at the very most it should be allocating only about 1 MB
(10 threads * 100K string per thread) on a machine with lots of free
memory and virtual memory space.
Dennis Cote
 
 

Re:A comedy of errors

"Dennis Cote" < XXXX@XXXXX.COM >wrote in message
Quote
catch (...)
Don't use '...' to ctch VCL exceptions. Catch them explitically
instead:
catch (const Exception &)
Quote
TTestThread() : TThread(false)
All VCL constructors (and destructors) use the __fastcall calling
convention:
__fastcall TTestThread()
Quote
It runs until it gets an EOutOfMemory exception in one of the
threads
(which doesn't take very long).
You are re-allocating the main AnsiString 100,000 times per thread,
and also generating 100,000 temporary AnsiString objects per thread as
well. You are likely fragmenting memory alot, hense the EOutOfMemory
error.
Gambit
 

Re:A comedy of errors

Quote
His program did not reliably break under BDS 2006 (though he said it did
with BCB 6).
Please note I claimed that it *did not* break in BDS2006. I never claimed
that it would break in BDS2006. I realize that you know this but someone
reading the above statement might think that I intended it to be used to
show a flaw in CodeGuard in BDS2006. Rather, this code was created to show
a flaw in CodeGuard in BCB6.
Quote
I increased the loop limit in the Execute method to 100,000 so each
thread would take longer to execute
100,000 bytes per string per thread is an awful lot. Given the
fragmentation that is likely to happen a great deal more memory than 100,000
* 10 will be used, though I'm not sure how to calculate that amount. Maybe
something like 99,999 + 99,998 + ... + 1 bytes of memory per thread.
Though, for fun I plugged in your numbers into my BDS2006 test app and
couldn't cause the EOutOfMemory error to happen.
Since your intent was to keep the thread in memory longer and still have
allocations taking place try something like this instead:
for (int outerIndex = 0; outerIndex < 1000; ++outerIndex)
{
String str;
for (int index = 0; index < 1000; ++index)
str += ' ';
}
The affect is to cause each thread to perform roughly 2,000,000 allocations.
It will force the thread to stay in memory longer (which is what you wanted)
and be more reasonable on memory usage (each thread should only consume a
maximum of approximately 1000 bytes). When I use the above code, and 100
threads rather than 10, I still can't cause the EOutOfMemory problem in
BDS2006.
- Clayton
 

{smallsort}

Re:A comedy of errors

Quote
Don't use '...' to ctch VCL exceptions. Catch them explitically
instead:
Unnecessary for this example. The catch is there simply to keep the thread
from crashing the system. Changing the catch to the following has no change
to the outcome:
catch (Exception const&)
{
}
catch (...)
{
}
Quote
All VCL constructors (and destructors) use the __fastcall calling
convention:
A descendant can create whatever constructor it needs with whatever calling
convention it requires. Using __fastcall in this case is merely a
preference. Note, changing the code to use __fastcall has no altered
affect.
Quote
You are re-allocating the main AnsiString 100,000 times per thread,
and also generating 100,000 temporary AnsiString objects per thread as
well. You are likely fragmenting memory alot, hense the EOutOfMemory
error.
Agreed. See my other reply for my full response on this subject.
The unaltered posted code will reliably break CodeGuard in BCB6 on my
computer. I have achieved the crash with only 2 threads and 100 bytes per
string. Though, increasing the number of threads will make the exception
come quicker. The cause of the exception is the allocation of the temporary
string (' '). If you try this code in BCB6 and separate the concatenation
to two steps you will see this:
String tmpString(' '); // <-- EOutOfMemory here
str += tmpString;
When CodeGuard is not used the number of threads can number in the hundreds
and the size of the string in the 10's of thousands without any problem.
- Clayton
 

Re:A comedy of errors

I did not look at the code very closely but it may be similar to a memory
leak that occurs with CodeGuard in BCB 5. The way around it was to put the
declaration of index outside of the while loop. It basically allocates the
String but never deletes it.
This might not have anything to do with using all your memory but the only
way to find out is to trace thru the "for" loop and watch CodeGuard allocate
and delete the String. When I trace the "while" loop I saw it call
CodeGuard to allocate the string but the "delete" was skipped.
Good luck.
groups.google.com/group/borland.public.cppbuilder.language.cpp/browse_thread/thread/e8c13cfd4ab9bb51/d9ec1c28552e8281
Quote
String str;
int index;
for (index = 0; index < 1000; ++index)
str += ' '; // <-- (*)
"Dennis Cote" < XXXX@XXXXX.COM >wrote in message
Quote
Hi all,

I am trying to track a bug in BDS 2006 regarding codeguard, mutithreading,
and memory management. I have been having problems with what appear to be
incorrect codeguard errors in my application. Clayton Arends posted a very
small sample project in b.p.c.thirdpartytools that produced the same
symptoms.

class TTestThread : public TThread
{
protected:
virtual void __fastcall Execute()
{
try
{
String str;
for (int index = 0; index < 1000; ++index)
str += ' '; // <-- (*)
}
catch (...)
{
}
}

public:
TTestThread() : TThread(false)
{
FreeOnTerminate = true;
}
};

void __fastcall TForm1::Button1Click(TObject *Sender)
{
for (int index = 0; index < 10; ++index)
new TTestThread;
}

His program did not reliably break under BDS 2006 (though he said it did
with BCB 6). While watch the event log I saw that the threads were
basically completing in on time slice so that each was terminated before
the next started. I increased the loop limit in the Execute method to
100,000 so each thread would take longer to execute, and the program now
reliably breaks when built and run with codeguard enabled (all options).

It runs until it gets an EOutOfMemory exception in one of the threads
(which doesn't take very long).

I was trying to track the cause of the problems using BDS 20006 and have
been hitting a continuous stream of errors that make it almost impossible
to get anywhere.

When the exception happened I break into the de{*word*81} and started poking
around to see what the state of each thread is. I'm using the Call Stack
pane and the Local Variables pane to see the loop counter value in each
thread.

I managed to collect the following before the IDE crashed.

Event Log:

Thread Start: Thread ID: 320. Process Project1.exe (3288)
Process Start: C:\Documents and Settings\DennisC\My Documents\Borland
Studio Projects\codeguard_memory_bug\Debug_Build\Project1.exe. Base
Address: $00400000. Process Project1.exe (3288)
Module Load: Project1.exe. Has Debug Info. Base Address: $00400000.
Process Project1.exe (3288)
Module Load: ntdll.dll. No Debug Info. Base Address: $7C900000.
Process Project1.exe (3288)
Module Load: KERNEL32.dll. No Debug Info. Base Address: $7C800000.
Process Project1.exe (3288)
Module Load: rtl100.bpl. No Debug Info. Base Address: $51F20000.
Process Project1.exe (3288)
Module Load: OLEAUT32.dll. No Debug Info. Base Address: $77120000.
Process Project1.exe (3288)
Module Load: msvcrt.dll. No Debug Info. Base Address: $77C10000.
Process Project1.exe (3288)
Module Load: USER32.dll. No Debug Info. Base Address: $77D40000.
Process Project1.exe (3288)
Module Load: GDI32.dll. No Debug Info. Base Address: $77F10000.
Process Project1.exe (3288)
Module Load: ADVAPI32.dll. No Debug Info. Base Address: $77DD0000.
Process Project1.exe (3288)
Module Load: RPCRT4.dll. No Debug Info. Base Address: $77E70000.
Process Project1.exe (3288)
Module Load: ole32.dll. No Debug Info. Base Address: $774E0000.
Process Project1.exe (3288)
Module Load: VERSION.dll. No Debug Info. Base Address: $77C00000.
Process Project1.exe (3288)
Module Load: MPR.dll. No Debug Info. Base Address: $71B20000. Process
Project1.exe (3288)
Module Load: IMAGEHLP.dll. No Debug Info. Base Address: $76C90000.
Process Project1.exe (3288)
Module Load: WSOCK32.dll. No Debug Info. Base Address: $71AD0000.
Process Project1.exe (3288)
Module Load: WS2_32.dll. No Debug Info. Base Address: $71AB0000.
Process Project1.exe (3288)
Module Load: WS2HELP.dll. No Debug Info. Base Address: $71AA0000.
Process Project1.exe (3288)
Module Load: OLEACC.dll. No Debug Info. Base Address: $74C80000.
Process Project1.exe (3288)
Module Load: MSVCP60.dll. No Debug Info. Base Address: $76080000.
Process Project1.exe (3288)
Module Load: vcl100.bpl. No Debug Info. Base Address: $52000000.
Process Project1.exe (3288)
Module Load: MSIMG32.dll. No Debug Info. Base Address: $76380000.
Process Project1.exe (3288)
Module Load: COMCTL32.dll. No Debug Info. Base Address: $5D090000.
Process Project1.exe (3288)
Module Load: SHELL32.dll. No Debug Info. Base Address: $7C9C0000.
Process Project1.exe (3288)
Module Load: SHLWAPI.dll. No Debug Info. Base Address: $77F60000.
Process Project1.exe (3288)
Module Load: WINSPOOL.DRV. No Debug Info. Base Address: $73000000.
Process Project1.exe (3288)
Module Load: comdlg32.dll. No Debug Info. Base Address: $763B0000.
Process Project1.exe (3288)
Module Load: oledlg.dll. No Debug Info. Base Address: $7DF70000.
Process Project1.exe (3288)
Module Load: borlndmm.dll. No Debug Info. Base Address: $41000000.
Process Project1.exe (3288)
Module Load: cg32.dll. No Debug Info. Base Address: $0CD00000. Process
Project1.exe (3288)
Module Load: cc3270mt.dll. No Debug Info. Base Address: $32700000.
Process Project1.exe (3288)
Module Load: COMCTL32.dll. No Debug Info. Base Address: $773D0000.
Process Project1.exe (3288)
Module Load: MSCTF.dll. No Debug Info. Base Address: $74720000.
Process Project1.exe (3288)
Module Load: UxTheme.dll. No Debug Info. Base Address: $5AD70000.
Process Project1.exe (3288)
Thread Start: Thread ID: 3436. Process Project1.exe (3288)
Thread Start: Thread ID: 1872. Process Project1.exe (3288)
Thread Start: Thread ID: 3892. Process Project1.exe (3288)
Thread Start: Thread ID: 3916. Process Project1.exe (3288)
Thread Start: Thread ID: 1708. Process Project1.exe (3288)
Thread Start: Thread ID: 2588. Process Project1.exe (3288)
Thread Start: Thread ID: 712. Process Project1.exe (3288)
Thread Start: Thread ID: 3040. Process Project1.exe (3288)
Thread Start: Thread ID: 3924. Process Project1.exe (3288)
Thread Start: Thread ID: 3148. Process Project1.exe (3288)
First chance exception at $7C812A5B. Exception class EOutOfMemory with
message 'Out of memory'. Process Project1.exe (3288)

Call stack of Thread 3040 :

:7c812a5b kernel32.RaiseException + 0x52
:51f278d1 rtl100.@System@@NewAnsiString$qqri + 0x11
:00403BAB System::AnsiString::vprintf(this=:018AFF58,
format=:00405710, paramList=:018AFEE0)
:00403BDC System::AnsiString::sprintf(this=:018AFF58,
format=:00405710, paramList=????)
:00403B15 System::AnsiString::AnsiString(this=:018AFF58, src=' ')
:00402281 TTestThread::Execute(this=:00E776B8)
:51f5e8ff ; C:\WINDOWS\system32\rtl100.bpl
:51f27772 rtl100.@System@@Assert$qqrx17System@AnsiStringt1i + 0x4a
:7c80b683 ; C:\WINDOWS\system32\kernel32.dll

Local Variables in Thread 3040 TTestThread::Execute :

Name Value
this :00E776B8
Classes::TThread::FHandle 3924 (0x00000F54)
Classes::TThread::FThreadID 3040 (0x00000BE0)
Classes::TThread::FCreateSuspended false
Classes::TThread::FTerminated false
Classes::TThread::FSuspended false
Classes::TThread::FFreeOnTerminate true
Classes::TThread::FFinished false
Classes::TThread::FReturnValue 0 (0x00000000)
Classes::TThread::FOnTerminate {NULL,NULL}
Classes::TThread::FSynchronize { NULL, {NULL,NULL}, NULL }
FThread NULL
FMethod {NULL,NULL}
FSynchronizeException NULL
Classes::TThread::FFatalException NULL
str { " " }
Data :00F6418C " "
[0] ' ' 32 (0x20)
[1] ' ' 32 (0x20)
[2] ' ' 32 (0x20)
[3] ' ' 32 (0x20)
[4] ' ' 32 (0x20)
[5] ' ' 32 (0x20)
[6] ' ' 32 (0x20)
[7] ' ' 32 (0x20)
[8] ' ' 32 (0x20)
[9] ' ' 32 (0x20)
[10] ' ' 32 (0x20)
[11] ' ' 32 (0x20)
[12] ' ' 32 (0x20)
[13] '\0' 0 (0x00)
index 13 (0x0000000D)

All threads are at $7C90EB94 except
3924 at $0CD17BFA
3040 at $7C812A5B
3892 at $7C9010FF

Thread 320 (main thread) call stack:

:7c90eb94 ntdll.KiFastSystemCallRet
:77d49418 USER32.WaitMessage + 0xc
:52079340 vcl100.@Forms@TApplication@HandleMessage$qqrv + 0x1c
:00401883 WinMain( =:00400000, =NULL, =:00141F57, =9)
:32778e1b ; C:\WINDOWS\system32\CC3270MT.DLL

Thread 3436 call stack:

:7c90eb94 ntdll.KiFastSystemCallRet
:7c90e591 ntdll.NtSetEventBoostPriority + 0xc
:7c90110a ntdll.RtlLeaveCriticalSection + 0x1d
:0cd015d6 ; C:\PROGRA~1\Borland\BDS\4.0\Bin\CG32.DLL
:0cd01661 CG32.@_cg_SysGetMem + 0x9
:51f23bd6 rtl100.@System@@GetMem$qqri + 0xa
:00403BAB System::AnsiString::vprintf(this=:011AFF58,
format=:00405710, paramList=:011AFEE0)
:00403BDC System::AnsiString::sprintf(this=:011AFF58,
format=:00405710, paramList=????)
:00403B15 System::AnsiString::AnsiString(this=:011AFF58, src=' ')
:00402281 TTestThread::Execute(this=:00E77488)
:51f5e8ff ; C:\WINDOWS\system32\rtl100.bpl
:51f27772 rtl100.@System@@Assert$qqrx17System@AnsiStringt1i + 0x4a
:7c80b683 ; C:\WINDOWS\system32\kernel32.dll

When the IDE crashed it displayed the usual Windows crash dialog
(IDE_crash.png) with the message "Borland Developer Studio for Windows has
encountered a problem and needs to close. We are sorry for the
inconvenience." I tried to capture the "technical details" from the dialog
but did not find any way to do so (only the list of loaded modules could
be captured from the temp file the dialog created to send to MS). I
clicked send, waited for the report to be sent, and then waited for BDS to
be killed.

Instead, BDS displayed a series of error dialogs (IDE_errors.png) saying
it was out of memory in rtl100.bpl. After capturing a few I acknowledged
them all. After this most of the content of the BDS window was cleared to
a blank background.

I then got another error dialog reporting an access violation in
DelphiSpeedUp with a Send (or is it Details) button that is used to create
an automated incident report. I dutifully filled in the requested
information and clicked send. The AIR report failed (IDE_report_fail.png)
with an error about trying to put a string of 3740 bytes into a field that
can only hold 40 bytes. So I gave up on that and closed the AIR dialog.

Then The BDS window went completely white, no content at all, and
displayed another error dialog (IDE_app_error.png) saying that the
instruction at an address referenced memory at the same address and that
memory could not be read. It said to click the OK button to terminate the
program. Each time I did that it displayed another copy of the same
dialog.

After about 10 dialogs the IDE had redrawn part of its contents and then
displayed another new error dialog (IDE_after10Errors.png) saying an
unknown software exception had occurred in the application.

At this point I gave up and terminated the BDS process with the task
manager and continued.

I have posted all the screenshots mentioned above in the attachments group
under the same subject (A comedy of errors).

For me, this is a typical example of using BDS to debug an application. It
kind of works, but then fails spectacularly with a crashes, phenomenal
slowdowns, or silent hangs. It is very frustrating.

I will continue to try to figure out why codeguard causes this simple
multithreaded application to fail, and report an out of memory condition,
when at the very most it should be allocating only about 1 MB (10 threads
* 100K string per thread) on a machine with lots of free memory and
virtual memory space.

Dennis Cote
 

Re:A comedy of errors

Clayton Arends wrote:
Quote
Please note I claimed that it *did not* break in BDS2006. I never claimed
that it would break in BDS2006. I realize that you know this but someone
reading the above statement might think that I intended it to be used to
show a flaw in CodeGuard in BDS2006. Rather, this code was created to show
a flaw in CodeGuard in BCB6.

Yes, I know that you claimed it worked in BDS 2006, and it did for me
most times as well. But from the event log it was obvious that the 10
threads were not executing simultaneously, so by increasing the run time
of the threads I caused it to fail reliably in BDS 2006. I thought you
had simply not been interested in the BDS 2006 case because you were
working on a BCB 6 problem.
Quote

100,000 bytes per string per thread is an awful lot.
Why do you say that? My machine has 1GB of RAM and 1.3GB of virtual
memory. It is using about 355MB leaving about 1GB of virtual memory
free. More than enough for 10 threads to allocate 0.1 MB each. In fact
each thread could have about 1000 string that size before memory becomes
an issue at which point the OS should start expanding the VM file.
Given the
Quote
fragmentation that is likely to happen a great deal more memory than 100,000
* 10 will be used, though I'm not sure how to calculate that amount. Maybe
something like 99,999 + 99,998 + ... + 1 bytes of memory per thread.
Though, for fun I plugged in your numbers into my BDS2006 test app and
couldn't cause the EOutOfMemory error to happen.

I think you are making too big an issue of memory fragmentation. The
memory for the temp string is released at the end of each loop and
presumably reused at the beginning of the next. That may not happen
every time due to task switching, but most loops will execute in the
same thread within one timeslice.
Also, I assume that the AnsiString implementation is smart enough to
resize the string in a binary fashion (ie doubling the buffer size)
rather than reallocating with a size one byte larger. Even if that is
not the case each allocation is followed by a release of the previous
buffer which the memory manager should recombine with the other
previously buffers to make a buffer that is larger than the new request.
In any case, at no time does the memory allocation for this program go
above about 9 MB.
Quote
Since your intent was to keep the thread in memory longer and still have
allocations taking place try something like this instead:

for (int outerIndex = 0; outerIndex < 1000; ++outerIndex)
{
String str;
for (int index = 0; index < 1000; ++index)
str += ' ';
}

The affect is to cause each thread to perform roughly 2,000,000 allocations.
It will force the thread to stay in memory longer (which is what you wanted)
and be more reasonable on memory usage (each thread should only consume a
maximum of approximately 1000 bytes). When I use the above code, and 100
threads rather than 10, I still can't cause the EOutOfMemory problem in
BDS2006.

I doubt this will make any difference, but I will try it. I don't think
the program is really anywhere close to running out of real memory.
Dennis Cote
 

Re:A comedy of errors

Remy Lebeau (TeamB) wrote:
Quote

Don't use '...' to ctch VCL exceptions. Catch them explitically
instead:

catch (const Exception &)

Why? What is the difference? I don't really want to handle the errors. I
stop in the de{*word*81} when the exception is thrown.
Quote
>TTestThread() : TThread(false)

All VCL constructors (and destructors) use the __fastcall calling
convention:

__fastcall TTestThread()

OK. I didn't write code, and the compiler didn't complain, so I didn't
really care. What impact would this have?
Quote

You are re-allocating the main AnsiString 100,000 times per thread,
and also generating 100,000 temporary AnsiString objects per thread as
well. You are likely fragmenting memory alot, hense the EOutOfMemory
error.


I know I am hammering the memory manager, I'm trying to trigger a bug
that is related to codeguard, memory allocation, and multithreading.
This program never allocates more than about 9MB on a machine with about
1GB of free virtual memory (before the OS expands it). I don't believe
it is really running out of memory.
Dennis Cote
 

Re:A comedy of errors

Quote
>100,000 bytes per string per thread is an awful lot.

Why do you say that? My machine has 1GB of RAM and 1.3GB of virtual
memory.
I was speaking in terms of fragmentation.
Quote
I think you are making too big an issue of memory fragmentation
...
In any case, at no time does the memory allocation for this program go
above about 9 MB.
My memory utilization also did not get very high. Perhaps I am making too
big of an issue of fragmentation. However, if you can duplicate this
problem using less memory I believe the cause will be more apparent.
- Clayton
 

Re:A comedy of errors

Clayton Arends wrote:
Quote

Since your intent was to keep the thread in memory longer and still have
allocations taking place try something like this instead:

for (int outerIndex = 0; outerIndex < 1000; ++outerIndex)
{
String str;
for (int index = 0; index < 1000; ++index)
str += ' ';
}

The affect is to cause each thread to perform roughly 2,000,000 allocations.
It will force the thread to stay in memory longer (which is what you wanted)
and be more reasonable on memory usage (each thread should only consume a
maximum of approximately 1000 bytes). When I use the above code, and 100
threads rather than 10, I still can't cause the EOutOfMemory problem in
BDS2006.

OK, I modified the code as followed based on the previous comments.
class TTestThread : public TThread
{
protected:
virtual void __fastcall Execute()
{
try
{
for (int i = 0; i < 1000; ++i) {
String str;
for (int index = 0; index < 1000; ++index)
str += ' '; // <-- (*)
}
}
catch (const Exception& e)
{
}
}
public:
__fastcall TTestThread() : TThread(false)
{
FreeOnTerminate = true;
}
};
void __fastcall TForm1::Button1Click(TObject *Sender)
{
for (int index = 0; index < 10; ++index)
new TTestThread;
}
This version crashes in BDS 2006 in the same way. I get an EOutOfMemory
exception in one of the threads.
The call stack from the thread where the exception is thrown:
:7c812a5b kernel32.RaiseException + 0x52
:51f278d1 rtl100.@System@@NewAnsiString$qqri + 0x11
:00403BFB System::AnsiString::vprintf(this=:018AFF50, format=:00405710,
paramList=:018AFED0)
:00403C2C System::AnsiString::sprintf(this=:018AFF50, format=:00405710,
paramList=????)
:00403B65 System::AnsiString::AnsiString(this=:018AFF50, src=' ')
:00402291 TTestThread::Execute(this=:00E7769C)
:51f5e8ff ; C:\WINDOWS\system32\rtl100.bpl
:51f27772 rtl100.@System@@Assert$qqrx17System@AnsiStringt1i + 0x4a
:7c80b683 ; C:\WINDOWS\system32\kernel32.dll
I have noticed that it always seems to be the allocation of the single
byte temporary string that fails. This may just be coincidence but it
has happen quite a few times in the last couple of days.
With the program stopped in the de{*word*81} the task manager shows the
memory allocated to the process is 7,116K. Free virtual memory is 860
MB. All 10 threads have started, and none have completed. At the time of
the error the threads were at the following points in their execution.
Thread i index
1 63 646
2 59 667
3 67 256
4 121 957
5 109 233
6 136 620
7 126 537
8 55 509
9 165 194
10 41 NA
Any other ideas?
Dennis Cote
 

Re:A comedy of errors

"Clayton Arends" < XXXX@XXXXX.COM >wrote in message
Quote
Unnecessary for this example. The catch is there simply to keep the
thread from crashing the system.
An uncaught exception inside a TThread's Execute() method will not
crash the system. TThread catches uncaught exceptions internally and
then terminates itself gracefully.
Gambit
 

Re:A comedy of errors

I reset the program and ran it again to see if the problem was
repeatable. The program started, I clicked the button, and this time the
IDE locked up trying to display two dialogs with unknown contnet. I have
put a screen capture in the attachments group under this subject as
IDE2_crash_on_run.png. When crashed the task manager is showing BDS
using 98% of the CPU, but all other tasks are running.
The one difference is that I had the local variables pane open when I
ran the program. I have a open private report about a de{*word*81} crash
that CodeGear says is caused by the local variables pane, so that may be
what has happened here. I really wish they would fix that problem!
Note all other programs like Thunderbird used to post this message
continue to work just fine.
Dennis Cote
 

Re:A comedy of errors

"Dennis Cote" < XXXX@XXXXX.COM >wrote in message
Quote
Why? What is the difference?
Borland changed the compiler in BCB 6. '...' does not always handle
VCL exceptions properly anymore.
Quote
This program never allocates more than about 9MB on a machine with
about 1GB of free virtual memory (before the OS expands it). I don't
believe it is really running out of memory.
It is not a matter of running out of memory in general. It is a
matter of the memory manager being able to find enough consecutive
bytes to handle such large allocations. The more fragmented the
memory becomes, the harder to can be to find suitable memory blocks.
Gambit
 

Re:A comedy of errors

"Dennis Cote" < XXXX@XXXXX.COM >wrote in message
Quote
I think you are making too big an issue of memory fragmentation. The
memory for the temp string is released at the end of each loop and
presumably reused at the beginning of the next.
You have to keep in mind that when expanding an AnsiString, the memory
manager has to allocate a new memory block that is the size of the
existing data plus the size of the data being appended. Before the
contents of the AnsiString are replaced and the old memory released,
there is at least twice as much memory being used up, however brief a
time it exists. The larger the AnsiString becomes, the more memory is
being used on each iteration of the loop.
Quote
Also, I assume that the AnsiString implementation is smart enough
to resize the string in a binary fashion (ie doubling the buffer
size)
It is not.
Quote
rather than reallocating with a size one byte larger.
That is exactly what it is doing. Given the original code:
String str;
for (int index = 0; index < 1000; ++index)
str += ' ';
The above is effectively doing the following:
AnsiString str;
for (int index = 0; index < 1000; ++index)
{
AnsiString temp(' ');
str += temp;
}
Here is what is happening at each step:
AnsiString str;
- the constructor initializes the data member to NULL. No memory
is allocated yet
AnsiString temp(' ');
- the constructor calls sprintf()
- sprintf() calculates the length and calls SetLength(1)
- SetLength() calls System::LStrSetLength(1)
- LStrSetLength() calls NewAnsiString(1)
- NewAnsiString() calls GetMem(10)
(8 for string descriptor + 1 for string data + 1 for null
terminator)
- GetMem() allocates 10+ bytes
(10 requested + memory manager overhead)
- NewAnsiString() sets the reference count to 1
- LStrSetLength() assigns the allocated pointer to the data member
- sprintf() formats the character into the data member
str += temp;
- the '+=' operator calls System::LStrCat(this, temp)
- on the first iteration of the loop:
- LStrCat() calls System::LStrAsg() because str is empty
- LStrAsg() copies the data pointer from temp to str and
increments str's reference count
- on the remaining iterations of the loop:
- LStrCat() calls LStrSetLength(str.length + temp.length)
because str is not empty
- LStrSetLength() calls ReallocMem((str.data - 8), 8 +
str.length + temp.length + 1) because the reference count is 1
- ReallocMem() calls MemoryManager.ReallocMem((str.data - 8),
8 + str.length + temp.length + 1)
- MemoryManager.ReallocMem() checks if the block can be
resized inlined
- if not, it allocates a new block, copies the old data
into it, and releases the old block
- LStrSetLength copies the new data into the end of the new
memory
temp.~AnsiString();
- the reference count is decremented
- the memory is freed if the reference count falls to 0
Quote
Even if that is not the case each allocation is followed by a
release of
the previous buffer which the memory manager should recombine with
the other previously buffers to make a buffer that is larger than
the new request.
Not necessaily, especially when threads are involved.
Gambit
 

Re:A comedy of errors

"Dennis Cote" < XXXX@XXXXX.COM >wrote in message
Quote
OK, I modified the code as followed based on the previous comments.
<snip>
This version crashes in BDS 2006 in the same way. I get an
EOutOfMemory exception in one of the threads.
I would expect it to. By introducing a second loop, you are
fragmenting memory that much more, not reducing it. Before, you only
had one loop, so when it finished, the AnsiString memory is freed and
the thread exits. Now, you have a second loop running the
allocate-resize-free operations over and over a bunch of extra times.
Multiply that by the number of threads, and the memory manager is
being hit that much harder.
Gambit
 

Re:A comedy of errors

Quote
I would expect it to. By introducing a second loop, you are
fragmenting memory that much more, not reducing it.
With the added loop fragmented memory is being released more frequently back
to the memory manager and memory allocation can begin again with smaller
numbers. This means gaps are more frequently filled in than made.
- Clayton