I have an application that makes thousands of HTTP requests along the day.
For best performance, I decided to create only once the IdHTTP object and use this same object for all requests.
This is where the problem begins. While creating one IdHTTP for each request, everything went fine.
The code is pretty basic:
constructor HTTPThread.Create;
begin
inherited Create(false);
httpObject:= TIdHTTP.Create(Nil);
sslObject:= TIdSSLIOHandlerSocketOpenSSL.Create(Nil);
sslObject.SSLOptions.Method:= sslvTLSv1_2;
httpObject.IOHandler:= sslObject;
httpObject.Request.Accept:= frmHTTPRequests.Edit1.Text;
httpObject.Request.UserAgent:= frmHTTPRequests.Edit3.Text;
httpObject.ReadTimeout:= 15000;
httpObject.HandleRedirects:= true;
FreeOnTerminate:= true;
OnTerminate:= TerminateProc;
end;
procedure HTTPThread.DoRequests;
var
htmlSource: string;
begin
try
htmlSource:= httpObject.Get(Link);
//a bunch of other stuff with HTML source
except
on E : Exception do
Synchronize(procedure
begin
errorList.Add(E.Message);
errorList.SaveToFile('Error.txt');
end);
end;
end;
I created this except saving the Error.txt file to watch what is happening...
The code runs fine sometimes for the first 1k requests, sometimes for the first 2k, it varies. Suddently, it starts writing on the TXT file the same error:
Connection reset by peer. domain.com - Socket Error # 10054
I tried to disconnect the httpObject, tried httpObject.Request.Clear, nothing seems to work.
Are there any chance to make this work?
For some reasons Indy doesn't close socket when server respond with Connection reset by peer, so you need to do it manually.
procedure HTTPThread.DoRequests;
const
MAX_TRIES_COUNT = 5;
var
htmlSource: string;
TriesCount: Integer;
begin
TriesCount := 0;
repeat
try
htmlSource:= httpObject.Get(Link);
//a bunch of other stuff with HTML source
except
on E: Exception do
begin
if E.Message.Contains('by peer') then
begin
httpObject.Disconnect;
// Try to solve network connection issues
Continue;
end
else
begin
// Some other error handlers
end;
end;
inc(TriesCount);
end;
until (httpObject.ResponseCode = 200) or (TriesCount > MAX_TRIES_COUNT);
end;
P.S. I don't recommend you to use Synchronize() for threads synchronization. Try to use TCriticalSection or TMonitor instead.
Related
I am using TOmniBlockingCollection from a "Server Thread".
In this thread, I am hosting a Single-Threaded Apartment COM object to a DataProvider (CoInitialize() and CoUninitialize() are called).
Then additionally, I am using multiple worker threads to write to an ADO Dataset (TADOCommand), which also needs CoInitialize()/CoUninitialize().
Only when I catch an error from inside the WorkerFunction and try to terminate the "Server Thread", I am waiting (infinitely?) on the CoUninitialize() in the "Server Thread" (inherited from TThread) that started the WorkerFunction.
Callstack:
:774546bc ntdll.ZwWaitForAlertByThreadId + 0xc
.... some other system functions
:771e8069 combase.CoUninitialize + 0xf9
UTServerThread.TServerThread.Execute (calls CoUninitialize)
How do I avoid the wait in the Server Thread when I catch an error ... it seems to be something COM-related in multi-threading.
Pseudo Code:
procedure CreateIOmniWorkers;
begin
for Each IOmniWorker in OmniWorkerArry
begin
IOmniWorker := CreateTask("WorkerFunction" as TOmniTaskMethod, Description)
.SetParameter('Input', InputCollection)
.WithLock(TSynchroObject.Create) // The Used Logger inside Worker Funtion needs a Lock
.OnTerminated(ErrorHandler);
end;
end;
procedure ErrorHandler(const task: IOmniTaskControl);
var
eException: Exception;
begin
if Assigned(task.FatalException) then
begin
eException := task.DetachException;
try
Logger.Error(
'TOTLBlockingListWorker', eException.ClassName,
Format(rsScriptErrorDataBlock,[eException.Message, eException.StackTrace]));
finally
FreeAndNil(eException);
end;
end;
end;
procedure WorkerFunction(const task: IOmniTask; var SyncData: TSyncOutputValueHolder);
begin
CoInitialize(nil);
try
CreateConnection(SyncData);
// Somewhere here a Exception happens
WriteSQLStyle(task, SyncData);
finally
CoUninitialize; // is called
end;
end;
procedure UTServerThread.TServerThread.Execute
begin
CoInitialize(nil);
try
while not Terminated
begin
CreateIOmniWorkers;
with TOTLBlockingCollection do
while DataAvailable
TOTLBlockingCollection.Add(Datapackage);
TOTLBlockingCollection.CompleteAdding;
while not TOTLBlockingCollection.IsFinalized
Sleep(250);
end;
finally
CoUninitialize; // Here the (infinite?) Wait happens.
end;
end;
I had also some Other Problem with the BlockingList.
if a Exception occured Inside the TryTake until False Loop of the Blocking List Interfaced objects would not be freed.
So i did deperately update my OTL Source to the current Version (3.07.8 was 07.6).
The Problem with CoInitialize and Unitialize is gone... But... i must have still a bug in the Composition.
In place of the OnTerminadted Handler i attached A Monitor to my CreateIOmniWorkers, but i dont receive Events there. The OnTerminated Handler also does not Fire. So it works... But i am not confident that i did it right.
repeat
if not BlockingList.TryTake(Params) then
break; // simplified
InterfacedObject := TInferfacedObject.Create as ISomething
try
raise Exception while having a TInterfacedObject here ...
Finally
InterfaceObject := nil; // if this finally is here in case of Error the Interface would not be freed.
end;
until false
I wrote a communication class based on TThread, which would send some data and receive a reply.
I want the method to:
sent the data (this is a non blocking procedure)
wait for a reply or timeout
show the data received in a vcl control
give back control to the caller
Here is how I try to do,
procedure TForm1.Button1Click(Sender: TObject);
begin
for i := 1 to 5 do // send 5 commands
mycomm.SendCommand();
end;
procedure TMyComm.ShowData();
begin
Form1.Memo1.Lines.Add('Frame received');
end;
procedure TMyComm.SendCommand();
begin
//build frame and put it on interface here
//...
event.WaitFor(3000);
//show received frame if no timeout in VCL
//...
end;
procedure TMyComm.Execute();
begin
while not Terminated do
begin
if receive() then //blocks until frame is received
begin
Synchronize(ShowData); //hangs :-(
event.SetEvent;
end;
end,
end;
Of course this will result in a deadlock, but how can I achieve that my VCL is updated immediately after each received frame?
You can use a anonymous thread, this will only execute the rest of the code after the thread has finished, change it to suite your needs.
You can find the AnonThread Unit in :
C:\Users\Public\Documents\RAD Studio\12.0\Samples\Delphi\RTL\CrossPlatform Utils
uses
AnonThread
var
GetFrame :TAnonymousThread<Boolean>;
begin
GetFrame := TAnonymousThread<Boolean>.Create(function : Boolean
begin
// Start your execution
end,
procedure (AResult : Boolean)
begin
// Wil only execute after the thread has run its course, also safe to do UI updates
end,
procedure (AException : Exception)
begin
ShowMessage('Error : ' + AException.Message);
end);
Okay guys, I'm using TClientSocket class and Threads to work simultaneously with a list of hosts. It's all good but I started note that after sometime, all threads get stucked into a ReceiveBuf call... If I open 10 threads, to check 100 hosts for example, it will start good, but after sometime all threads will be stucked because for some reason some hosts don't answer fine for this ReceiveBuf call... I tried to do a ReceiveLength and check if receive > 0, to call the ReceiveBuf, but still not working.
I'll post the original code below:
function Threads.ReceiveProtocolVersion;
var
ProtocolVersion: array[0..11] of AnsiChar;
begin
try
MySocket.Socket.ReceiveBuf(ProtocolVersion, SizeOf(ProtocolVersion));
Version:= AnsiString(ProtocolVersion);
...//continues but doesn't matter because many threads get stucked in the ReceiveBuf call...
except
Terminate; //we terminate thread if raise some exception..
Ok, so after some researchs, I started tryin to do like this:
function Threads.ReceiveProtocolVersion;
var
ProtocolVersion: array[0..11] of AnsiChar;
SizeBuf: integer;
begin
try
SizeBuf:= MySocket.Socket.ReceiveLength;
if SizeBuf > 0 then
begin
MySocket.Socket.ReceiveBuf(ProtocolVersion, SizeOf(ProtocolVersion));
Version:= AnsiString(ProtocolVersion);
....
end;
except
Terminate; //we terminate thread if raise some exception..
Apparently, it solved the problem about threads getting stucked in ReceiveBuf call, but for some unknown reason, none (not even the ones that was working right) threads get inside the 'if SizeBuf > 0'.
Any help?
//Edit showing more of Thread code::
The Thread.Execute is like this:
procedure MyThread.Execute;
begin
while not(Terminated) do
begin
if SocketConnect then
begin
if ReceiveProtocolVersion then
begin
DoAuthentication;
end;
end;
MySocket.Close;
MySocket.Free;
end;
Terminate;
end;
The SocketConnect function is:
function MyThread.SocketConnect: bool;
begin
Result:= false;
MySocket:= TClientSocket.Create(Nil);
MySocket.Port:= StrToInt(Form1.Edit1.Text);
MySocket.ClientType:= ctBlocking;
MySocket.Host:= Host; //Host is a private variable for thread class
try
MySocket.Active:= true;
if (MySocket.Socket.Connected = true) then
Result:= true;
except
Terminate;
end;
end;
I fix the problem using TWinSocketStream. Something like this:
function MyThread.CheckProtocol: bool;
var
SockStream: TWinSocketStream;
ProtocolVersion: array[0..11] of AnsiChar;
begin
try
SockStream := TWinSocketStream.Create(MySocket.Socket, 3000);
SockStream.Read(ProtocolVersion, SizeOf(ProtocolVersion));
RFBVer:= AnsiString(ProtocolVersion);
....
I read that the correct way to work with blocking-mode sockets is by sending/receiving data over TWinSocketStream in:
DocWiki Embarcadero
Anyway, thank for the guys who tried to help!
I am using a thread to access a pop3 account and retrieve messages. It works fine, but it locks up my application until it is complete. Cant move the window, shut down, click buttons, nothing.
It runs fine and allows me to access the main application up until the spot i commented out (or after the IdPOP31.Connect();)
//Getting the number of the messages that server has
then it locks up
procedure TPopThread.Pop;
var
vName, vEmail, vServerIn, vServerOut, vUserId, vPassword: String;
vPop3Port, vSMTPPort, vSSL: String; vHTML: TStringList;
MsgCount : Integer;
i,j : Integer;
FMailMessage : TIdMessage;
begin
with frmMain do
begin
RzMemo1.Lines.Clear;
vHTML:= TStringList.Create;
GetAccount(lbxMain.SelectedItem,vName, vEmail, vServerIn, vServerOut, vUserId, vPassword,
vPop3Port, vSMTPPort, vSSL, vHTML);
IdPOP31.Host := vServerIn;
IdPOP31.Username := vUserId;
IdPOP31.Password := vPassword;
IdPOP31.Port := StrToInt(vPop3Port);
try
Prepare(IdPOP31);
IdPOP31.Connect();
// {
// //Getting the number of the messages that server has.
// MsgCount := IdPOP31.CheckMessages;
// for i:= 0 to Pred(MsgCount) do
// begin
// try
// FMailMessage := TIdMessage.Create(nil);
// IdPOP31.Retrieve(i,FMailMessage);
// RzMemo1.Lines.Add('=================================================');
// RzMemo1.Lines.Add(FMailMessage.From.Address);
// RzMemo1.Lines.Add(FMailMessage.Recipients.EMailAddresses);
// RzMemo1.Lines.Add(FMailMessage.Subject);
// RzMemo1.Lines.Add(FMailMessage.Sender.Address);
// RzMemo1.Lines.Add(FMailMessage.Body.Text);
//
// for J := 0 to Pred( FMailMessage.MessageParts.Count ) do
// begin
// // if the part is an attachment
// if ( FMailMessage.MessageParts.Items[ J ] is TIdAttachment) then
// begin
// RzMemo1.Lines.Add('Attachment: ' + TIdAttachment(FMailMessage.MessageParts.Items[J]).Filename);
// end;
// end;
// RzMemo1.Lines.Add('=================================================');
// finally
// FMailMessage.Free;
// end;
// RzMemo1.Clear;
// end;
// }
finally
IdPOP31.Disconnect;
vHTML.Free;
end;
end;
end;
It actually did this before I added the thread, so it has something to do with that portion that is commented out and not the thread
What did i do wrong or didn't do?
here is my Execute
procedure TPopThread.Execute;
begin
try
Synchronize(Pop);
except
on Ex: Exception do
fExceptionMessage := Ex.Message;
end;
end;
here is how i call it
PopThread := TPopThread.Create(lbxMain.SelectedItem, frmMain.DonePopping);
You are locking up the application yourself, because you're synchronizing the call to the pop method.
Synchronize causes the call specified by AMethod to be executed using the main thread, thereby avoiding multithread conflicts.
The current thread is passed in the AThread parameter.
If you are unsure whether a method call is thread-safe, call it from within the Synchronize method to ensure it executes in the main thread.
Execution of the current thread is suspended while the method executes in the main thread.
So, for practical purposes, you're like you have no extra thread, since all your code is executed in the main thread.
An example of when you would want to use Synchronize is when you want to interact with a VCL component
On the other hand, because you're directly accessing a number of visual controls from your method, and the VCL is not thread safe, you have to execute your method in the main thread.
The best you can do is to make your thread independent from the VCL by not accessing any VCL component from the thread, but rather collecting all the input and output values in memory and setting/reading it from the main thread before the thread starts and after the thread finishes.
Or, if for any reason you don't want to do that, you can dissect your method to separate the parts that need access to the VCL and synchronize only that parts, for example:
type
TPopThread = class
private
FMailMessage : TIdMessage; //now the message belongs to the class itself
...
public
//all the values are passed via constructor or the thread is
//created in suspended state, configured and then started
property Host: string read FHost write FHost;
property UserName: string read FUserName write FUserName;
property Password: string read ...;
property Port: Integer read ...;
end;
procedure TPopThread.CopyMailToGUI;
var
J: Integer;
begin
frmMain.RzMemo1.Lines.Add('=================================================');
frmMain.RzMemo1.Lines.Add(FMailMessage.From.Address);
frmMain.RzMemo1.Lines.Add(FMailMessage.Recipients.EMailAddresses);
frmMain.RzMemo1.Lines.Add(FMailMessage.Subject);
frmMain.RzMemo1.Lines.Add(FMailMessage.Sender.Address);
frmMain.RzMemo1.Lines.Add(FMailMessage.Body.Text);
for J := 0 to Pred( FMailMessage.MessageParts.Count ) do
begin
// if the part is an attachment
if ( FMailMessage.MessageParts.Items[ J ] is TIdAttachment) then
begin
frmMain.RzMemo1.Lines.Add('Attachment: ' + TIdAttachment(FMailMessage.MessageParts.Items[J]).Filename);
end;
end;
frmMain.RzMemo1.Lines.Add('=================================================');
end;
procedure TPopThread.Pop;
var
MsgCount : Integer;
i,j : Integer;
Pop: TIdPOP3;
begin
Pop := TIdPOP3.Create(nil);
try
Pop.Host := FHost;
Pop.Username := FUserName;
Pop.Password := FPassword;
Pop.Port := FPort;
Prepare(Pop);
Pop.Connect();
//Getting the number of the messages that server has.
MsgCount := Pop.CheckMessages;
for I := 0 to Pred(MsgCount) do
begin
try
FMailMessage := TIdMessage.Create(nil);
try
IdPOP31.Retrieve(i,FMailMessage);
Synchronize(CopyMailToGUI);
finally
FMailMessage.Free;
end;
end;
finally
Pop.Free;
end;
end;
procedure TPopThread.Execute;
begin
//no need of a try/except, if an exception occurs, it
//is stored in the FatalException property
Pop;
end;
Now, your thread will ask the main thread to copy just the processed message to the VCL. During that copy your thread will block and your application will not respond to messages because the main thread is busy, but that will be for very shorts intervals, so even if it is not the ideal case, I think it will work for what you want.
You put all your logic inside a Synchronize call. Synchronize runs its function in the main VCL thread, so you've essentially nullified any benefits you might have gained from using a separate thread in the first place.
Remove the call to Synchronize so that Pop runs in the thread you created for it.
If you still need some operations to execute in the main thread, then put them in subroutines so that you can run only them in Synchronize. The parts I see in that code are the places where you add lines to a memo control.
This is my first post here - so be gentle :-)
I want to build a client/server application that uses datasnap for data transport.
This is a fairly simple task - and there are lots of examples to learn from.
BUT - Having a Datasnap server (build from Delphi XE wizard) I find myself running into a problem, and I hope someone can guide me into the right direction.
Server and Client run on same PC (that is the design for now).
Server is running Session lifecycle.
Server and Client shares a class (posted below)..
The Server provides a simple method - GetServerObject which uses the GetNewObject method.
The Server itself is a VCL application - main form is fmServer.
OnCreate instatiates the Servers FormObject property (FormObject := TMyDataObject.Create);
function TServerMethods2.GetNewObject: TMyDataObject;
begin
Result := TMyDataObject.Create;
end;
function TServerMethods2.GetServerObject: TMyDataObject;
begin
Result := GetNewObject;
if not Result.Assign(fmServer.FormObject) then
raise Exception.Create('Server error : Assign failed!');
end;
All this is pretty trivial - and my problem only appears if I twist my Client application into a multithreaded monster :-) (read - more than 1 thread).
So here is the Thread code for the client.
TDataThread = class(TThread)
private
DSConn: TSQLConnection;
protected
procedure Execute; override;
public
constructor Create(aConn: TSQLConnection); overload;
end;
constructor TDataThread.Create(aConn: TSQLConnection);
begin
inherited Create(False);
DSConn := aConn.CloneConnection;
FreeOnTerminate := true;
end;
procedure TDataThread.Execute;
var
DSMethod: TServerMethods2Client;
aDataObject : TMyDataObject;
begin
NameThreadForDebugging('Data');
{ Place thread code here }
DSMethod := nil;
try
while not terminated do
begin
sleep(10);
if DSConn.Connected then
begin
try
if DSMethod = nil then
DSMethod := TServerMethods2Client.Create(DSConn.DBXConnection,false);
if DSMethod <> nil then
try
aDataObject := DSMethod.GetserverObject;
finally
freeandnil(aDataObject);
end;
except
freeandnil(DSMethod);
DSConn.Connected := False;
end
end
else
begin
// connect
try
sleep(100);
DSConn.Open;
except
;
end;
end;
end;
finally
freeandnil(DSMethod);
DSConn.Close;
freeandnil(DSConn);
end;
When I create more than 1 of these threads - eventually I will get an error (being "cannot instatiate ... " or some "remote dbx error ..." .. and so on.
I simply cannot get this to work - so that I can spawn hundreds of threads/connections to a datasnap server.
I know this question is tricky - but my hope is that someone is smarter than me :-)
If I try the same client thread code - but accessing a more simple server method (lets say echostring from sample) then I can run it with hundreds of threads.
Perhaps Im answering myself here - but Im too blind to realize it :-)
unit uDataObject;
interface
uses
SysUtils;
Type
TMyDataObject = class(TObject)
private
fString: String;
fInteger: Integer;
public
constructor Create; virtual;
destructor Destroy; override;
function Assign(aSource: TMyDataObject): boolean;
property aString: String read fString write fString;
property aInteger: Integer read fInteger write fInteger;
end;
implementation
{ TMyDataObject }
function TMyDataObject.Assign(aSource: TMyDataObject): boolean;
begin
if aSource <> nil then
begin
try
fString := aSource.aString;
fInteger := aSource.aInteger;
Result := True;
except
Result := false;
end;
end
else
Result := false;
end;
constructor TMyDataObject.Create;
begin
inherited;
Randomize;
fString := 'The time of creation is : ' + FormatDateTime('ddmmyyyy hh:nn:ss:zzz', Now);
fInteger := Random(100);
end;
destructor TMyDataObject.Destroy;
begin
inherited;
end;
end.
All help is appreciated
This has mostly been answered in the comments and the bug report, but... The problem you are seeing is caused by a multithreading issue in XE's marshaller code. If two threads (or two clients) call a server server method which takes in or return user defined types (any type which will use the marshaller/unmarshaller) at the same time, then an exception could happen.
I am unaware of a perfect workaround for XE, but if it is possible to not use user-defined types, then you shouldn't see multithreading issues.
Mat
When the simple server method is working, i think your problem has to be found i somethin the "real" code is doing or using.
It could be in the connection (try changing your simpler code to use the connection)
Your problem can also be then CloneConnection. The Cloned connection is freed, when the connection it is cloned from is freed. See
http://docwiki.embarcadero.com/VCL/en/SqlExpr.TSQLConnection.CloneConnection