inline var in Anonymous Thread cause memory leak [duplicate] - multithreading

with the code below:
Procedure TMyForm.doAction;
begin
generateToken;
VAR LToken := generatedToken; // Ltoken is set correctly to xxxxx
//-----
TThread.CreateAnonymousThread(
procedure
begin
GetData(LToken); // << here LToken is empty !!
end).Start;
end;
As you can see, the value of LToken is empty inside the anonymous procedure (so the capture didn't work). if I don't use inline var for LToken then it's work
Procedure TMyForm.doAction;
Var LToken: ansiString;
begin
....
end;
Is this a bug or a know limitation ?

There was a compiler bug in Delphi 10.3.x and inline variables are not properly captured by anonymous methods.
Inline variables don't work with anonymous methods
https://quality.embarcadero.com/browse/RSP-26666
Solution for 10.3.x, as you already mentioned, is to define and use local variable instead of using inline variable.
Another solution would be upgrading to 10.4, where capturing inline variables with anonymous methods has been corrected and now it correctly captures inline variable, so you would be able to use your original code.
However, there is additional bug in compiler in 10.4 related to capturing inline variables. If the variable is managed type (interface reference, string...), it is never automatically released and it leaks memory.
Such variables must be explicitly released at the end of anonymous method that captured them. For instance, assigning nil to interface reference or assigning empty string to string variable...
Memory leak: inline Interfaces captured by anonymous methods are not released
https://quality.embarcadero.com/browse/RSP-29564

Related

Why do I need Sharemem in my Delphi dll which only exposes a function with WideString parameters?

I have a dll and a test application written in Delphi. The test application uses multiple threads to call the function exported by the dll. The exported function has a trivial thread safe implementation. When running the test application various errors (access violation, invalid pointer operation, stack overflow etc) happens or the application freezes. In some cases the application finishes without errors.
Note that these errors only happen (surface) when using multiple threads. When calling the function from the main thread only then everything works fine.
I have found that adding ShareMem to both the dll and the application stops all these kind of errors. But I don't understand why. To my knowledge ShareMem is only needed when passing long strings between the dll and the application. As far as I know WideString is not a long string.
Also according to this post ShareMem should not be required:
Why can Delphi DLLs use WideString without using ShareMem?
Here is the source of the dll:
library External;
uses
Winapi.Windows;
type
TMyType = class
private
FText: string;
end;
function DoSomething(input: WideString; out output: WideString): Bool; stdcall;
var
x: TObject;
begin
x := TMyType.Create;
try
output := x.ClassName;
finally
x.Free;
end;
Result := True;
end;
exports
DoSomething;
begin
end.
Here is the test application:
program ConsoleTest;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Winapi.Windows,
OtlParallel;
function DoSomething(input: WideString; out output: WideString): Bool; stdcall; external 'External.dll' name 'DoSomething';
var
sResult: WideString;
begin
try
Parallel.&For(0, 500).Execute(procedure(value: Integer)
var
sResult: WideString;
begin
DoSomething('hhh', sResult);
end);
WriteLn('Done');
ReadLn;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Why ShareMem makes the bugs go away and is there another way to fix these bugs?
I am using Delphi XE2 and OmniThread 3.07.5.
Update
- Same issue when run from a VCL application's button's on click event handler
- If DoSomething uses a critical section inside then runs fine
- If FText field is removed from TMyClass then no errors are reported but the application randomly freezes
For the standard memory manager (FastMM) to support multi threading, you need to set the IsMultiThread flag.
When you use RTL for threading, this flag is automatically set. As revealed in the comments to the question, OTL also use RTL to start its threads. So the memory manager in your executable is aware of threading, but the distinct memory manager in the dll causes errors. When you use "sharemem", there is only one memory manager which is aware of threading because of OTL, so you encounter no errors.
An alternative solution, apart from using a shared memory manager, would be to set the flag also for the memory manager in the dll.

Delphi 7 with..do statement doesen't work with variant variable

I'm working with Microsoft Excel via Delphi 7. It works fine but while formatting rows and ranges I have to write such long strings.
XLApp.Workbooks[1].WorkSheets[NameDoc].Range['A19:L19'].Font.Bold := true;
So I want to get rid of hard work and do it via "with..do" statement like this
with XLApp.Workbooks[1].WorkSheets[NameDoc] do
begin
Range['A19:L19'].Font.Bold := true;
end;
But at compilation stage I see this error
Record, object or class type required
on string - "with..do".
I creating Excel object this way
XLApp: Variant;
XLApp := CreateOleObject('Excel.Application');
I consider that with..do statement doesen't works with variant type variable, but I want to know whether I'm right or not? And if I'm right is there any workaround to make it work?
Variant can be anything or nothing at all - compiler doesn't know it and cannot know: it is so called "dynamically typed value". Since it does not know - it does not know if there would be any members (properties, methods) and if there would - what names would they have.
To get the benefits of strong compile-time typing - including using of with but not only - you have to use interface variables, those that are provided by TExcelApplication component and underlying unit having those values "statically typerd" - thus providing for Delphi compiler to know value types when compiling, in before running. There are plenty of types like iWorsksheet, iRange and others in that unit.
Borland Delphi 7 TExcelApplication.Connect works on office machines but not at client
http://www.delphipages.com/forum/showthread.php?t=157889
http://delphikingdom.ru/asp/viewitem.asp?catalogid=1270
However, since that is about reference-counting and lifetime I'd suggest you go with explicit use of temp variables rather than using with with and implicit invisible variables. Since you cannot control their lifespan and their clearance you might hit the wall in some unexpected place later. I did.
var tmpR: iRange; // assuming we have statically-typed API
// for example - providing we using ExcelXP or Excel2000 unit
tmpR := XLApp.Workbooks[1].WorkSheets[NameDoc];
tmpR.Range['A19:L19'].Font.Bold := true; // instead of with
with tmpR do // also possible but gives little benefit now
begin // when we made a dedicated temp var
Range['A19:L19'].Font.Bold := true;
end;
tmpR := nil; // crucial unless the most short and simplistic functions
// just release hold on Excel's object - let it manage its memory freely,
// by letting Excel know your program no more uses that object.
Also read
https://en.wikipedia.org/wiki/Automatic_Reference_Counting
https://en.wikipedia.org/wiki/Component_Object_Model
Can with be used with a Variant?
No.
You can use with for types whose members are known at compile time. But variants, for which the . operator is evaluated at run time, do not fall into this category. Hence with is not available for variants.
The documentation says, with my emphasis:
A with statement is a shorthand for referencing the fields of a record
or the fields, properties, and methods of an object. The syntax of a
with statement is:
with obj do statement
or:
with obj1, ..., objn do statement
where obj is an expression yielding a reference to a record, object
instance, class instance, interface or class type (metaclass)
instance, and statement is any simple or structured statement.

Delphi pass string to procedure gets an access violation error

I am currently trying to create a test procedure and pass it a string. And the function checks the string for equivalence. The problem is that when the test runs, I get a access violation error of EAccessViolation on the string that I pass inside the Procedure. I understand that this is a memory declaration problem but I am not sure how to fix it.
Here is my code:
Declare
TestTForm1 = class(TTestCase)
strict private
FForm1: TForm1;
public
procedure SetUp; override;
procedure TearDown; override;
published
procedure TestCompareListBoxToFile(Method : String);
end;
Call
TestCompareListBoxToFile('Save');
Procedure
procedure TestTForm1.TestCompareListBoxToFile(Method : String);
begin
Check('Save' = Method,'they dont match');
end;
I am new to delphi so if there is anything I am missing, please let me know. please be specific in the response. Thank you.
That code is called by the unit test runner. It uses RTTI to find published methods whose names begin with 'Test'. On the face of it, this appears to be a DUnit test case.
The runner expects a procedure that accepts no parameters, and calls the method as such. You on the other hand, provide a method that does require a parameter. A parameter that is not provided. Hence the runtime error.
Now, somewhere in your code you say that you are calling the method like this:
TestCompareListBoxToFile('Save');
But that's just not how tests are invoked. Tests are invoked by the runner which uses RTTI to do so. If you attempt to call the function as well, that's just wrong.
You must declare your method like this:
procedure TestCompareListBoxToFile;
You probably need to go back to the documentation and examples for the unit test framework and learn how to design your test case to be able to accept parameters. In fact, before even doing that, I suggest you go right back to basics and make sure you fully understand how the runner discovers your tests and then runs them.

Why doesn't TPageProducer remove quotation marks from strings?

I'm trying to debug behaviour that has only appeared when my large app - working fine in XE3 - is run after compiling with XE4. The issue seems to cause some quoted strings (eg "MyString") to retain their quotes even after having been 'de-quoted' by TPageProducer in Web.HTTPProd. For example, consider the code below which is small extract from this Delphi source unit Web.HTTPApp:
procedure ExtractHeaderFields(Separators, _WhiteSpace: TSysCharSet; Content: PChar;
Strings: TStrings; Decode: Boolean; StripQuotes: Boolean = False);
{$ENDIF NEXTGEN}
var
Head, Tail: PChar;
EOS, InQuote, LeadQuote: Boolean;
QuoteChar: Char;
ExtractedField: string;
{$IFNDEF NEXTGEN}
WhiteSpaceWithCRLF: TSysCharSet;
SeparatorsWithCRLF: TSysCharSet;
{$ENDIF !NEXTGEN}
function DoStripQuotes(const S: string): string;
var
I: Integer;
InStripQuote: Boolean;
StripQuoteChar: Char;
begin
Result := S;
InStripQuote := False;
StripQuoteChar := #0;
if StripQuotes then
begin
for I := Result.Length - 1 downto 0 do
if Result.Chars[I].IsInArray(['''', '"']) then
if InStripQuote and (StripQuoteChar = Result.Chars[I]) then
begin
Result.Remove(I, 1);
InStripQuote := False;
end
else if not InStripQuote then
begin
StripQuoteChar := Result.Chars[I];
InStripQuote := True;
Result.Remove(I, 1);
end
end;
end;
I see this called when I use TPageProducer and I can see my good source string go into the ExtractHeaderFields routine above and then into the 'DoStripQuotes' function. Stepping into DoStripQuotes and watching 'Result' shows that it does not change, even when Result.Remove is called (to strip the quote). When I take this 'DoStripQuotes' routine out to a simple test app, it wont compile, telling me that 'Result.anything' is not allowed. I assume then that Result, although it is defined as 'string' must be another type of string in the context of Web.HTTPProd.
So I get to thinking maybe this is something to do with the 'Immutable strings' that I've heard about. I read this SO question about that and although I get the gist, I could do with more practical advice.
Specifically, I would like answers to the following questions:
What type of 'string' is 'Result' if the notation Result.Length is allowed?
Is there a way in which I can tell the compiler to use 'XE3' compatibility for a unit? (THis might allow me to see where the problem is originiating). Ive ttried {$ZEROBASEDSTRINGS ON} / OFF but this seems to cause even more chaos and I don't know what I'm doing!
Thanks for any help.
LATER EDIT: As noted in the accepted answer below this is a bug in the VCL unit Web.HTTPApp.pas which should read "Result := Result.Remove(I,1)" in two places around line 2645 and not "Result.Remove(I,1)"
What type of 'string' is 'Result' if the notation Result.Length is allowed?
It's just the same old string, aliased to UnicodeString, that you've been using since Delphi 2009. The difference is that this code uses the new record helper (specifically SysUtils.TStringHelper). That's what lets you use . notation on a string variable.
Is there a way in which I can tell the compiler to use 'XE3' compatibility for a unit?
No. The code in question is a library unit and it is designed to be compiled in a particular mode. What's more, you can't readily re-compile it unless you take on compiling the RTL/VCL yourself. Even if there was such a mode, it would not help since the code is simply wrong (see below). No amount of mode switching can fix this particular piece of code.
I get to thinking maybe this is something to do with the Immutable strings that I've heard about.
It's not. None of the Delphi compilers have immutable strings yet. The concept of immutable strings is just something that has been floated as a future change. And if the change is made, expect it to be made in the mobile compilers first.
The problem is in fact just a rather simple bug in the code that you posted which has clearly had no testing at all. The use of Remove is wrong. That method does not modify the string in-place. Instead it returns a new string that has the character removed. The code should read:
Result := Result.Remove(I, 1);
The reason that the developer who coded ExtractHeaderFields has made this mistake is that whoever designed the string helper code named the Remove method incorrectly. Since Remove is a verb you would expect it to operate in-place. A method that does not modify the subject, and returns a new instance, as this method does, should be given a name that is a noun. So this method should be named something like Remnants. It looks to me as though the RTL designers copied the .net naming where the same flaw also exists.
You should submit a QC report, if one does not already exist. I know that XE4 update 1 has just been released. It's plausible that it contains a fix.
Your other options, as I see them, are:
Stick with XE3 until XE4 is sufficiently debugged.
Include a copy of the Web.HTTPApp unit in your project and fix the bugs yourself.

Named threads in Delphi - what is that for?

When you create a TThread descendant using the tool palette in your BDS, you can provide a name for the thread. Here's the auto-generated code. You just call the SetName() function in the Execute method and the thread calling this method is given a name in a kind of weird way...
{$IFDEF MSWINDOWS}
type
TThreadNameInfo = record
FType: LongWord; // must be 0x1000
FName: PChar; // pointer to name (in user address space)
FThreadID: LongWord; // thread ID (-1 indicates caller thread)
FFlags: LongWord; // reserved for future use, must be zero
end;
{$ENDIF}
{ TTestThread }
procedure TTestThread.SetName;
{$IFDEF MSWINDOWS}
var
ThreadNameInfo: TThreadNameInfo;
{$ENDIF}
begin
{$IFDEF MSWINDOWS}
ThreadNameInfo.FType := $1000;
ThreadNameInfo.FName := 'ThreadName';
ThreadNameInfo.FThreadID := $FFFFFFFF;
ThreadNameInfo.FFlags := 0;
try
RaiseException( $406D1388, 0, sizeof(ThreadNameInfo) div sizeof(LongWord), #ThreadNameInfo );
except
end;
{$ENDIF}
end;
I find it really useful during debugging for you can see not only TIDs, but also thread names assigned that way. You know which thread is which thanks to that.
Please tell me, however, if the name assigned can be accessed in any way. Can it be read based on a thread's handle? Or can it be read even from 'outside' the process by another process? You know, there are applications which list your processes and the threads working in them. Will this name be accessible to apps like that?
Thanks!
Actually, thread names are just used for debugging purposes and nothing else, really. In your code, you could just identify threads by using the ThreadID. And if you want to keep a name with those thread ID's, keep a separate (dictionary) list which maps each thread ID to whatever name you like.
The hack that you see does a nasty trick. The exception that is raised is captured by the debugger, which just handles it as a special exception and will just continue execution. The exception flag just tells the system to continue after the exception is raised, since the code will handle it. The empty except-clause is handling the exception within your code. It's just a dirty trick to communicate with the debugger, which will zee the exception and remember the name you've just passed to it...
It's entirely a debugging feature. In fact, the thread object doesn't even keep track of its own name. It sends it directly to the debugger, but doesn't store a copy of the name for itself. It's not accessible from within your own program or from anywhere else, except the debugger.

Resources