Why multithreaded memory allocate/deallocate intensive application does not scale with number of threads? - json

Notice:
Original post title
Why multithreaded JSON parser from DWScript does not scale with number of threads?
was changed because this problem is not related to processing JSON data with DWScript.
The problem is in default memory manager in Delphi XE2 to XE7 ( tested were XE2 and trial XE7 ), but problem appeared first in such type of application.
I have multithreaded Win32/Win64 vcl application which process JSON data in Delphi XE2.
Each thread parses JSON data using TdwsJSONValue.ParseString(sJSON) from DWScript, reads values using DWScript methods and stores result as records.
For testing purposes I process same JSON data in each thread.
Single thead run takes N seconds within thread to process data. Increasing number of threads to M lineary (approx. M * N) increases time within single thread necessary to process same data.
In result there is no speed improvment. Other parts of this applications ( JSON data delivery, storing results in target environment ) - scale as expected.
What could be a reason ? Any ideas appreciated.
Supplemental information:
Tested on Win7/32 and Win7/64, Win8/64 from 2-core to 12-core (w/w-out HT) systems
DWScript was choosen as fastest available (tested a bunch, among them: Superobject, build-in Delphi). SO behaves similar as JSON unit from DWS.
Below is complete console app illustrating the problem. To run it we need sample json data available here: https://www.dropbox.com/s/4iuv87ytpcdugk6/json1.zip?dl=0 This file contains data json1.dat for first thread. For threads up to 16 just copy json1.dat to json2.dat...json16.dat.
Program and data shoule be in the same folder. To run: convert.exe N, where N is number of threads.
Program writes time of execution in msecs to stout - spent in thread, time of parsing data and time of releasing (Destroy) TdwsJSONValue object.
Statement _dwsjvData.Destroy; does not scale.
program Convert;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Diagnostics,
System.Classes,
dwsJSON in 'dwsJSON.pas',
dwsStrings in 'dwsStrings.pas',
dwsUtils in 'dwsUtils.pas',
dwsXPlatform in 'dwsXPlatform.pas';
type
TWorkerThread = class (TThread)
private
_iUid: Integer;
_swWatch: TStopwatch;
_lRunning: Boolean;
_sFileJSonData: String;
_fJsonData: TextFile;
protected
constructor Create (AUid: Integer);
procedure Execute; override;
published
property Running: Boolean read _lRunning;
end;
TConverter = class (TObject)
private
_swWatch0, _swWatch1, _swWatch2: TStopwatch;
_dwsjvData: TdwsJSONValue;
protected
constructor Create;
destructor Destroy; override;
function Calculate (AUid: Integer; AJSonData: String; var AParse, ADestroy: Integer): Integer;
end;
const
MAX_THREADS = 16;
var
iHowMany: Integer;
athWorker: array [1..MAX_THREADS] of Pointer;
aiElapsed: array [1..MAX_THREADS] of Integer;
aiElapsedParse: array [1..MAX_THREADS] of Integer;
aiElapsedDestroy: array [1..MAX_THREADS] of Integer;
aiFares: array [1..MAX_THREADS] of Integer;
swWatchT, swWatchP: TStopwatch;
constructor TWorkerThread.Create (AUid: Integer);
begin
inherited Create (True);
_iUid := AUid;
_swWatch := TStopwatch.Create;
_sFileJSonData := ExtractFilePath (ParamStr (0)) + 'json' + Trim (IntToStr (_iUid)) + '.dat';
_lRunning := False;
Suspended := False;
end;
procedure TWorkerThread.Execute;
var
j: Integer;
sLine: String;
slLines: TStringList;
oS: TConverter;
begin
_lRunning := True;
oS := TConverter.Create;
slLines := TStringList.Create;
System.AssignFile (_fJsonData, _sFileJSonData);
System.Reset (_fJsonData);
j := 0;
repeat
System.Readln (_fJsonData, sLine);
slLines.Add (sLine);
Inc (j);
until (j = 50);
// until (System.Eof (_fJsonData));
System.Close (_fJsonData);
Sleep (1000);
_swWatch.Reset;
_swWatch.Start;
aiFares [_iUid] := 0;
aiElapsedParse [_iUid] := 0;
aiElapsedDestroy [_iUid] := 0;
for j := 1 to slLines.Count do
aiFares [_iUid] := aiFares [_iUid] + oS.Calculate (_iUid, slLines.Strings [j - 1], aiElapsedParse [_iUid], aiElapsedDestroy [_iUid]);
_swWatch.Stop;
slLines.Free;
os.Destroy;
aiElapsed [_iUid] := _swWatch.ElapsedMilliseconds;
_lRunning := False;
end;
constructor TConverter.Create;
begin
inherited Create;
_swWatch0 := TStopwatch.Create;
_swWatch1 := TStopwatch.Create;
_swWatch2 := TStopwatch.Create;
end;
destructor TConverter.Destroy;
begin
inherited;
end;
function TConverter.Calculate (AUid: Integer; AJSonData: String; var AParse, ADestroy: Integer): Integer;
var
jFare, jTotalFares, iElapsedParse, iElapsedDestroy, iElapsedTotal: Integer;
begin
_swWatch0.Reset;
_swWatch0.Start;
_swWatch1.Reset;
_swWatch1.Start;
_dwsjvData := TdwsJSONValue.ParseString (AJSonData);
_swWatch1.Stop;
iElapsedParse := _swWatch1.ElapsedMilliseconds;
if (_dwsjvData.ValueType = jvtArray) then
begin
_swWatch2.Reset;
_swWatch2.Start;
jTotalFares := _dwsjvData.ElementCount;
for jFare := 0 to (jTotalFares - 1) do
if (_dwsjvData.Elements [jFare].ValueType = jvtObject) then
begin
_swWatch1.Reset;
_swWatch1.Start;
_swWatch1.Stop;
end;
end;
_swWatch1.Reset;
_swWatch1.Start;
_dwsjvData.Destroy;
_swWatch1.Stop;
iElapsedDestroy := _swWatch1.ElapsedMilliseconds;
_swWatch0.Stop;
iElapsedTotal := _swWatch0.ElapsedMilliseconds;
Inc (AParse, iElapsedParse);
Inc (ADestroy, iElapsedDestroy);
result := jTotalFares;
end;
procedure MultithreadStart;
var
j: Integer;
begin
for j := 1 to iHowMany do
if (athWorker [j] = nil) then
begin
athWorker [j] := TWorkerThread.Create (j);
TWorkerThread (athWorker [j]).FreeOnTerminate := False;
TWorkerThread (athWorker [j]).Priority := tpNormal;
end;
end;
procedure MultithreadStop;
var
j: Integer;
begin
for j := 1 to MAX_THREADS do
if (athWorker [j] <> nil) then
begin
TWorkerThread (athWorker [j]).Terminate;
TWorkerThread (athWorker [j]).WaitFor;
TWorkerThread (athWorker [j]).Free;
athWorker [j] := nil;
end;
end;
procedure Prologue;
var
j: Integer;
begin
iHowMany := StrToInt (ParamStr (1));
for j := 1 to MAX_THREADS do
athWorker [j] := nil;
swWatchT := TStopwatch.Create;
swWatchT.Reset;
swWatchP := TStopwatch.Create;
swWatchP.Reset;
end;
procedure RunConvert;
function __IsRunning: Boolean;
var
j: Integer;
begin
result := False;
for j := 1 to MAX_THREADS do
result := result or ((athWorker [j] <> nil) and TWorkerThread (athWorker [j]).Running);
end;
begin
swWatchT.Start;
MultithreadStart;
Sleep (1000);
while (__isRunning) do
Sleep (500);
MultithreadStop;
swWatchT.Stop;
Writeln (#13#10, 'Total time:', swWatchT.ElapsedMilliseconds);
end;
procedure Epilogue;
var
j: Integer;
begin
for j := 1 to iHowMany do
Writeln ( #13#10, 'Thread # ', j, ' tot.time:', aiElapsed [j], ' fares:', aiFares [j], ' tot.parse:', aiElapsedParse [j], ' tot.destroy:', aiElapsedDestroy [j]);
Readln;
end;
begin
try
Prologue;
RunConvert;
Epilogue;
except
on E: Exception do
Writeln (E.ClassName, ': ', E.Message);
end;
end.

Have you tried my scaleable memory manager? Because Delphi (with fastmm internally) does not scale well with strings and other memory related stuff:
https://scalemm.googlecode.com/files/ScaleMM_v2_4_1.zip
And you could also try both profiler modes of my profiler to see which part is the bottleneck:
https://code.google.com/p/asmprofiler/

I did a (re)test of the FastCode MM Challenge, and the results were not that good for TBB (also out of memory exception in block downsize test).
In short: ScaleMM2 and Google TCmalloc are the fastest in this complex test, Fastmm and ScaleMM2 use the least memory.
Average Speed Performance: (Scaled so that the winner = 100%)
XE6 : 70,4
TCmalloc : 89,1
ScaleMem2 : 100,0
TBBMem : 77,8
Average Memory Performance: (Scaled so that the winner = 100%)
XE6 : 100,0
TCmalloc : 29,6
ScaleMem2 : 75,6
TBBMem : 38,4
FastCode Challenge: https://code.google.com/p/scalemm/source/browse/#svn%2Ftrunk%2FChallenge
TBB 4.3: https://www.threadingbuildingblocks.org/download

The solution is exchange default Delphi XE2 or XE7 memory manager with Intel® Threading Building Blocks memory manager. In example application it scales ca. lineary with number of threads up to 16 when app is 64 bits.
update: with assumption that number of threads running is less than number of cores
This was tested on machines from 2cores/4ht to 12cores/24ht running KVM virtualized Windows 7 with 124GB RAM
Interesting thing is virtualizing Win 7. memory allocation and deallocation is from 2 x faster as in native Win 7.
Conclusion: if you do a lot of memory allocation / deallocation operations of 10kB-10MB blocks in threads of multithreaded ( more than 4-8 threads) application - use only memory manager from Intel.
#André: thanks for tip pointing me to right direction!
Here is unit with TBB memory manager taken for tests, it has to appear as 1st on unit list in main project file .dpr
unit TBBMem;
interface
function ScalableGetMem (ASize: NativeInt): Pointer; cdecl; external 'tbbmalloc' name 'scalable_malloc';
procedure ScalableFreeMem (APtr: Pointer); cdecl; external 'tbbmalloc' name 'scalable_free';
function ScalableReAlloc (APtr: Pointer; Size: NativeInt): Pointer; cdecl; external 'tbbmalloc' name 'scalable_realloc';
implementation
Function TBBGetMem (ASize: Integer): Pointer;
begin
result := ScalableGetMem (ASize);
end;
Function TBBFreeMem (APtr: Pointer): Integer;
begin
ScalableFreeMem (APtr);
result := 0;
end;
Function TBBReAllocMem (APtr: Pointer; ASize: Integer): Pointer;
begin
result := ScalableRealloc (APtr, ASize);
end;
const
TBBMemoryManager: TMemoryManager = ( GetMem: TBBGetmem;
FreeMem: TBBFreeMem;
ReAllocMem: TBBReAllocMem; );
var
oldMemoryManager: TMemoryManager;
initialization
GetMemoryManager (oldMemoryManager);
SetMemoryManager (TBBMemoryManager);
finalization
SetMemoryManager (oldMemoryManager);
end.

Related

delphi/Lazarus/pascal , how to conver integer to byte array ?

I use Lazarus , but less document , I want convert integer to byte array , Distinguish between big-endian and small-endian.
for example
i is 1
I want convert to byte array
00,00,00,01 and 01,00,00,00
big and small .
Only for CPUs that are Little-Endian:
FUNCTION BigEndian(V : UInt32) : TBytes;
VAR
I : Int32;
BEGIN
SetLength(Result,4);
FOR I:=LOW(Result) TO HIGH(Result) DO BEGIN
Result[I]:=(V AND $FF000000) SHR 24;
V:=(V AND $00FFFFFF) SHL 8
END
END;
FUNCTION LittleEndian(V : UInt32) : TBytes;
VAR
I : Int32;
BEGIN
SetLength(Result,4);
FOR I:=LOW(Result) TO HIGH(Result) DO BEGIN
Result[I]:=V AND $000000FF;
V:=V SHR 8
END
END;
Another option for LittleEndian (again - on Little-Endian CPUs):
FUNCTION LittleEndian(V : UInt32) : TBytes;
BEGIN
SetLength(Result,4);
MOVE(V,Result[0],4)
END;
Don't know enough about Lazarus to know if it compiles unmodified there, but the above compiles fine in Delphi.
Endian-agnostic versions:
FUNCTION BigEndian(V : UInt32) : TBytes;
VAR
I : Int32;
BEGIN
SetLength(Result,4);
FOR I:=HIGH(Result) DOWNTO LOW(Result) DO BEGIN
Result[I]:=V MOD 256;
V:=V DIV 256
END
END;
FUNCTION SmallEndian(V : UInt32) : TBytes;
VAR
I : Int32;
BEGIN
SetLength(Result,4);
FOR I:=LOW(Result) TO HIGH(Result) DO BEGIN
Result[I]:=V MOD 256;
V:=V DIV 256
END
END;

FPGA output pins outputting wrong state

I am writing a LCD controller for an FPGA and am having a really weird (for me at least) problem. The state machine that's supposed to output the needed bits to the screen misbehaves and gets the output pins "stuck" in an old state, while it clearly has moved on to later states.
Here is the relevant parts of the state machine:
PROCESS (clk)
VARIABLE count: INTEGER RANGE 0 TO clk_divider; -- clk_divider is a generic positive.
BEGIN
IF (clk'EVENT AND clk = '1') THEN
count := count + 1;
IF (count = clk_divider) THEN
EAUX <= NOT EAUX;
count := 0;
END IF;
END IF;
END PROCESS;
....
PROCESS (EAUX)
BEGIN
IF (EAUX'EVENT AND EAUX = '1') THEN
pr_state <= nx_state;
END IF;
END PROCESS;
....
PROCESS (pr_state)
BEGIN
CASE pr_state IS
WHEN EntryMode => --6=1,7=Cursor increment/decrement, 8=Display shift on/off
RSs <='0';
DB(7 DOWNTO 0) := "00000110";
nx_state <= WriteData;
WHEN WriteData => --Write data to LCD:
RSs <='1';
YLED <= '1';
DB(7 DOWNTO 0) := "01011111";
i := i + 1;
IF (i < chars) THEN
nx_state <= WriteData;
ELSE
i := 0;
nx_state <= ReturnHome;
END IF;
WHEN ReturnHome => --Return cursor
RSs <='0';
YLED <= '1';
DB(7 DOWNTO 0) := "01011111";
nx_state <= WriteData;
END CASE;
END PROCESS;
Where the bits in the variable DB is assigned to the signal DBOUT:
DBOUT : OUT STD_LOGIC_VECTOR(7 DOWNTO 0) -- In entity
SHARED VARIABLE DB : STD_LOGIC_VECTOR(7 DOWNTO 0) := "00000000"; -- In Architecture
DBOUT <= DB;
DBOUT is outputted (in the .ucf-file) as:
NET "DBOUT(0)" LOC = P10;
NET "DBOUT(1)" LOC = P11;
NET "DBOUT(2)" LOC = P12;
NET "DBOUT(3)" LOC = P13;
NET "DBOUT(4)" LOC = P15;
NET "DBOUT(5)" LOC = P16;
NET "DBOUT(6)" LOC = P18;
NET "DBOUT(7)" LOC = P19;
Using an oscilloscope on the pins I can see that it is clearly stuck outputting the "EntryMode" bits and the "RSs" is set at low, while the YLED (the internal led on the FPGA) is on (it's off at all other states). The really weird thing is (and this took a real long time to find) is that if I change the EntryMode bits from
"00000110"
to
"00000100"
it successfully passes the state and outputs the correct bits. It might be true for other changes as well, but I don't really feel like testing that too much. Any help or tips would be highly appreciated!
UPDATE:
After popular request I explicitly put YLED to low in all the early states and switched (back) DB to be a signal. The result is that I can't reach the later states at all, or at least stay in them (even when fiddling with the magic bits, which I guess is a good thing) as the YLED only stays on for a split second after booting the FPGA.
There is a complete example, including theory, state machine, and VHDL code on pages 279-290 of "Finite State Machines in Hardware: Theory and Design...", by Volnei Pedroni, MIT Press, Dec. 2013.

Delphi formatting bytes to GB

I am using the following function to format Bytes to a more human readable format, but it is returning the incorrect information.
//Format file byte size
function FormatByteSize(const bytes: LongInt): string;
const
B = 1; //byte
KB = 1024 * B; //kilobyte
MB = 1024 * KB; //megabyte
GB = 1024 * MB; //gigabyte
begin
if bytes > GB then
result := FormatFloat('#.## GB', bytes / GB)
else
if bytes > MB then
result := FormatFloat('#.## MB', bytes / MB)
else
if bytes > KB then
result := FormatFloat('#.## KB', bytes / KB)
else
result := FormatFloat('#.## bytes', bytes) ;
end;
Example:
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(FormatByteSize(323889675684)); //Returns 1.65GB when it should be ~301GB
end;
Reference: http://delphi.about.com/od/delphitips2008/qt/format-bytes.htm (Author: Zarco Gajic)
Can anyone explain why it is returning the incorrect information and more importantly know how to fix it so it returns the correct information ?
The problem are arithmetic overflow. You can rewirte the the function like this:
uses
Math;
function ConvertBytes(Bytes: Int64): string;
const
Description: Array [0 .. 8] of string = ('Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
var
i: Integer;
begin
i := 0;
while Bytes > Power(1024, i + 1) do
Inc(i);
Result := FormatFloat('###0.##', Bytes / Power(1024, i)) + #32 + Description[i];
end;
Like said in the comments, your problem is that you are overflowing your 32-bit integer with a 64-bit value, thus it gets truncated to 32 bit (the top 32 bits are simply thrown away, so f.ex. a value of 5 Gb will be understood as 1 Gb). Also, since you are talking about sizes, you really shouldn't use integers, as you will then throw away half of your range on values that can't be valid in any case (a file, f.ex., can't have a size of -2048 bytes).
I have for some time used the following two functions. The one without a Decimals parameter will return up to 3 decimals, but only if necessary (ie. if the size is exactly 1 Gb, then it will return the string "1 Gb" and not "1,000 Gb" (if your decimal point is the comma)).
The one with a Decimals parameter will always return the value with that number of decimals.
Also note, that the calculation is done using the binary scale (1 Kb = 1024 bytes). If you want it changed to the decimal scale, you should change the 1024 values with 1000 and probably the SizeUnits array as well.
CONST
SizeUnits : ARRAY[0..8] OF PChar = ('bytes','Kb','Mb','Gb','Tb','Pb','Eb','Zb','Yb');
FUNCTION SizeStr(Size : UInt64) : String; OVERLOAD;
VAR
P : Integer;
BEGIN
Result:=SizeStr(Size,3);
IF Size>=1024 THEN BEGIN
P:=PRED(LastDelimiter(' ',Result));
WHILE COPY(Result,P,1)='0' DO BEGIN
DELETE(Result,P,1);
DEC(P)
END;
IF CharInSet(Result[P],['.',',']) THEN DELETE(Result,P,1)
END
END;
FUNCTION SizeStr(Size : UInt64 ; Decimals : BYTE) : String; OVERLOAD;
VAR
I : Cardinal;
S : Extended;
BEGIN
S:=Size;
FOR I:=LOW(SizeUnits) TO HIGH(SizeUnits) DO BEGIN
IF S<1024.0 THEN BEGIN
IF I=LOW(SizeUnits) THEN Decimals:=0;
Result:=Format('%.'+IntToStr(Decimals)+'f',[S]);
Result:=Result+' '+StrPas(SizeUnits[I]);
EXIT
END;
S:=S/1024.0
END
END;
If you are using a compiler version of Delphi that doesn't have the UInt64 type, you can use Int64 instead (you probably won't come acros files larger than 8 Eb = apprx. 8.000.000 TeraBytes in your lifetime :-), so Int64 should be sufficient in this case).
Also, the CharInSet function is one from the Unicode versions of Delphi. It can be implemneted as:
TYPE TCharacterSet = SET OF CHAR;
FUNCTION CharInSet(C : CHAR ; CONST Z : TCharacterSet) : BOOLEAN; INLINE;
BEGIN
Result:=(C IN Z)
END;
or replaced directly in the source, if you are using a pre-Unicode version of Delphi.

Delphi XE5 DataSnap Thread

I am trying to make a Android/Ios app that connects to MySQL through a DataSnap server.
I want to make this as a Thread. It works fine when I don't use a Thread.
In some articles it is mentioned that when using COM objects in a Thread it is importen to use CoInitialize and CoUninitialize. (But I don't get this to work)
Is this correct for FireMonkey app Android/Ios?
My Thread code:
Constructor TDMThread.Create(CreateSuspended: Boolean; ServerClassName, ProviderName:String; var ds:TclientDataset; n1:String=''; p1:String=''; n2:String=''; p2:String=''; n3:String='';p3:String='';n4:String='';p4:String='');
begin
Inherited Create(CreateSuspended);
FreeOnTerminate := False;
iServerClassName:=ServerClassName;
iProvName := ProviderName;
ip1 := p1;
in1 := n1;
ip2 := p2;
in2 := n2;
ip3 := p3;
in3 := n3;
ip4 := p4;
in4 := n4;
OutDS := ds;
end;
Destructor TDMThread.Destroy;
begin
inherited Destroy;
end;
procedure TDMThread.Execute;
var
par1,par2,par3,par4:Tparam;
begin
SQLConnection1 := TSQLConnection.Create(Nil);
SQLConnection1.DriverName := 'DataSnap';
SQLConnection1.Params.Values['HostName'] := 'localhost';
SQLConnection1.Params.Values['Port'] := '211';
SQLConnection1.Params.Values['DSAuthenticationPassword'] := '******';
SQLConnection1.Params.Values['DSAuthenticationUser'] := '*******';
SQLConnection1.Params.Values['DriverUnit'] := 'Data.DBXDataSnap';
SQLConnection1.Params.Values['CommunicationProtocol'] := 'tcp/ip';
SQLConnection1.Params.Values['DatasnapContext'] := 'datasnap/';
SQLConnection1.Params.Values['DriverAssemblyLoader'] := 'Borland.Data.TDBXClientDriverLoader,Borland.Data.DbxClientDriver,Version=19.0.0.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b';
DSProviderConnection1:=TDSProviderConnection.Create(NIL);
DSProviderConnection1.SQLConnection := SQLConnection1;
DSProviderConnection1.ServerClassName := iServerClassName;
SQLConnection1.Connected:=True;
ClientDataSet1 := TClientDataSet.Create(Nil);
ClientDataSet1.RemoteServer := DSProviderConnection1;
ClientDataSet1.ProviderName := iProvName;
ClientDataSet1.Close;
ClientDataSet1.Open;
ClientDataset1.FindFirst;
OutDS.CloneCursor(ClientDataSet1,False,True);
// Some more code ...
end;
Somebody have any thoughts? Examples that works?
I have XE5.1 and working on a Windows 8.1.
Update information...
It is running now.
I have made this change at the end:
procedure TDMThread.Execute;
var
par1,par2,par3,par4:Tparam;
begin
SQLConnection1 := TSQLConnection.Create(Nil);
SQLConnection1.DriverName := 'DataSnap';
SQLConnection1.Params.Values['HostName'] := 'localhost';
SQLConnection1.Params.Values['Port'] := '211';
SQLConnection1.Params.Values['DSAuthenticationPassword'] := '******';
SQLConnection1.Params.Values['DSAuthenticationUser'] := '*******';
SQLConnection1.Params.Values['DriverUnit'] := 'Data.DBXDataSnap';
SQLConnection1.Params.Values['CommunicationProtocol'] := 'tcp/ip';
SQLConnection1.Params.Values['DatasnapContext'] := 'datasnap/';
SQLConnection1.Params.Values['DriverAssemblyLoader'] := 'Borland.Data.TDBXClientDriverLoader,Borland.Data.DbxClientDriver,Version=19.0.0.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b';
DSProviderConnection1:=TDSProviderConnection.Create(NIL);
DSProviderConnection1.SQLConnection := SQLConnection1;
DSProviderConnection1.ServerClassName := iServerClassName;
SQLConnection1.Connected:=True;
ClientDataSet1 := TClientDataSet.Create(Nil);
ClientDataSet1.RemoteServer := DSProviderConnection1;
ClientDataSet1.ProviderName := iProvName;
ClientDataSet1.Close;
ClientDataSet1.Open;
ClientDataset1.FindFirst;
OutDS.CloneCursor(ClientDataSet1,False,True);
// This is new
while not terminated do
Begin
Sleep(100);
end;
//
// Some more code ...
end;
I found the solution here: XE5 Android TBitmap.LoadFromStream fail inside a thread
As you can see in:
XE5 Android TBitmap.LoadFromStream fail inside a thread
its bug in XE5 - the author's solution (sleep in loop) is not valid form of waiting for the thread...
dont use localhost as hostname. on mobile device it is wrong. you need to use actual IP in your local network of machine where server is running

TDataSet Descendant

I have a very good DirectMySQL unit, which is ready to be used and i want it to be a TDataset descendant so i can use it with QuickReport, i just want MySQL Query with DirectMySQL which descendant from TDataset.
Everything was ok until i tried to access a big table with 10.000 rows and more. It was unstable, the error was unpredictable and not always shown but it likely happened after you played with other tables.
It happened in GetFieldData(Field: TField; Buffer: Pointer): boolean; which used to get the field value from MySQL rows.
Here's the code,
function TMySQLQuery.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
var
I, CT: Integer;
Row: TMySQL_Row;
TBuf: PChar;
FD: PMySQL_FieldDef;
begin
UpdateCursorPos; ------------> This code is after i got the error but no result
Resync([]); ------------> This code is after i got the error but no result
Result := false;
Row := oRecordset.CurrentRow;
I := Field.FieldNo-1;
FD := oRecordset.FieldDef(I);
if Not Assigned(FD) then
FD := oRecordset.FieldDef(I);
TBuf := PP(Row)[i];
Try
CT := MySQLWriteFieldData(fd.field_type, fd.length, fd.decimals, TBuf, PChar(Buffer));
Result := Buffer <> nil;
Finally
Row := nil; ------------> This code is after i got the error but no result
FD := nil; ------------> This code is after i got the error but no result
TBuf := nil; ------------> This code is after i got the error but no result
Buffer := nil; ------------> This code is after i got the error but no result
End;
end;
{
These codes below are to translate the data type
from MySQL Data type to a TDataset data type
and move mysql row (TBuf) to TDataset buffer to display.
And error always comes up from this function
when moving mysql row to buffer.
}
function TMySQLQuery.MySQLWriteFieldData(AType: byte;
ASize: Integer; ADec: cardinal; Source, Dest: PChar): Integer;
var
VI: Integer;
VF: Double;
VD: TDateTime;
begin
Result := MySQLDataSize(AType, ASize, ADec);
case AType of
FIELD_TYPE_TINY, FIELD_TYPE_SHORT, FIELD_TYPE_LONG, FIELD_TYPE_LONGLONG,
FIELD_TYPE_INT24:
begin
if Source <> '' then
VI := StrToInt(Source)
else
VI := 0;
Move(VI, Dest^, Result);
end;
FIELD_TYPE_DECIMAL, FIELD_TYPE_NEWDECIMAL:
begin
if source <> '' then
VF := internalStrToCurr(Source)
else
VF := 0;
Move(VF, Dest^, Result);
end;
FIELD_TYPE_FLOAT, FIELD_TYPE_DOUBLE:
begin
if Source <> '' then
VF := InternalStrToFloat(Source)
else
VF := 0;
Move(VF, Dest^, Result);
end;
FIELD_TYPE_TIMESTAMP:
begin
if Source <> '' then
VD := InternalStrToTimeStamp(Source)
else
VD := 0;
Move(VD, Dest^, Result);
end;
FIELD_TYPE_DATETIME:
begin
if Source <> '' then
VD := InternalStrToDateTime(Source)
else
VD := 0;
Move(VD, Dest^, Result);
end;
FIELD_TYPE_DATE:
begin
if Source <> '' then
VD := InternalStrToDate(Source)
else
VD := 0;
Move(VD, Dest^, Result);
end;
FIELD_TYPE_TIME:
begin
if Source <> '' then
VD := InternalStrToTime(Source)
else
VD := 0;
Move(VD, Dest^, Result);
end;
FIELD_TYPE_STRING, FIELD_TYPE_VAR_STRING,
FIELD_TYPE_ENUM, FIELD_TYPE_SET:
begin
if Source = nil then
Dest^ := #0
else
Move(Source^, Dest^, Result);
end;
Else
Result := 0;
Raise EMySQLError.Create( 'Write field data - Unknown type field' );
end;
end;
My guess for now is it's memory related problem.
I am stacked. Anyone could help?
I also need TDataset documentation which list availlable descendant function and how to use it, or how to descendant from TDataset. anyone have them? I am lack of this kind of doumentation.
GetFieldData cannot have UpdateCursorPos and Resync calls. Otherwise you may get unpredicatable errors.
FD := oRecordset.FieldDef(I) ... FD := oRecordset.FieldDef(I); - looks strange. Second assigment is not needed.
finally ... end with local variables reset is not needed.
I have no idea what returns MySQLDataSize. For example, MySQLDataSize may return size in Delphi data type representation units, or may return length of data returned by MySQL. But depending on that MySQLWriteFieldData may be correct or may be not.
I dont know how DirectMySQL works. If it uses raw TCP/IP to talk to MySQL, then the problem may be there. For example, it incorrectly handles a sequence of packets.
And finally - what are the errors you are getting ? What is your Delphi version ? What is your MySQL client and server versions ?
And so on ....
IOW, that will be really hard to say, what is wrong. To do so, I for example, will need to get all sources, sit at Delphi IDE debugger and analyze many details of what is going on - sorry, no time :)
It's solved now by adding #0 at the end of the line... Thanks so much to all who replied to my problem.