Procedure Send Mail Alert - stored-procedures

I have a procedure which sends emails and this procedure is being called from other functions and procedures (primarily used for sending alerts and notifications).
One issue I face is if our mail server is down, then calling function or procedure stops execution, I mean they do not do any functionality which they are supposed to do. How can I make sure that calling function or procedure or for that matter any client which calls
MailProcedure should do its functionality even when mail server is down.
How can I achieve this?
Any help is highly appreciable.
Mail Procedure
CREATE OR REPLACE PROCEDURE MailProcedure(frm_id IN VARCHAR2, to_id IN VARCHAR2, subject IN VARCHAR2, body_text IN VARCHAR2)
AS
c utl_tcp.connection;
rc integer;
BEGIN
c := utl_tcp.open_connection('email_server', 25);
rc := utl_tcp.write_line(c, 'string');
rc := utl_tcp.write_line(c, 'from address');
rc := utl_tcp.write_line(c, 'to address');
rc := utl_tcp.write_line(c, 'Subject');
rc := utl_tcp.write_line(c, 'body');
utl_tcp.close_connection(c);
EXCEPTION
WHEN OTHERS
THEN
null;
END;
/

Since you want to queue the email for later delivery, the simplest option is to send all email messages asynchronously. Your other procedures would call a QueueMail procedure that inserts a row into the new mail_queue table
CREATE OR REPLACE PROCEDURE QueueMail(p_from IN VARCHAR2,
p_to IN VARCHAR2,
p_subject IN VARCHAR2,
p_body IN VARCHAR2)
AS
BEGIN
INSERT INTO mail_queue( mail_queue_id. from, to, subject, body )
VALUES( mail_queue_seq.nextval, p_from, p_to, p_subject, p_body );
END;
You would then have a separate procedure that runs in a separate thread that actually sense the emails and removes the messages from the queue. Something like
CREATE OR REPLACE PROCEDURE SendQueuedMessages
AS
BEGIN
FOR msg IN (SELECT * FROM mail_queue )
LOOP
sendMessage( msg.from, msg.to, msg.subject, msg.body );
DELETE FROM mail_queue
WHERE mail_queue_id = msg.mail_queue_id;
commit;
END LOOP;
END;
where sendMessage implements the actual logic for sending an email. I would think that you would want to use either the utl_mail or the utl_smtp package to send email rather than using utl_tcp but, of course, you can use utl_tcp. You would then schedule the SendQueuedMessages procedure using either the dbms_job or the dbms_scheduler package. Something like this
DECLARE
l_jobno PLS_INTEGER;
BEGIN
dbms_job.submit( l_jobno,
'BEGIN SendQueuedMessages; END;',
sysdate + interval '1' minute,
'sysdate + interval ''1'' minute' );
commit;
END;
will create a job that runs the SendQueuedMessages procedure every minute. If the mail server is down, the SendQueuedMessage procedure fails and the job is automatically rescheduled to run later. After the first failure, the job runs again 1 minute later. After the second failure, it runs 2 minutes later, then 4 minutes, 8 minutes, etc. until it fails 16 consecutive times. You can choose something other than the default behavior if you want to catch the exceptions in the SendQueuedMessages procedure. Since job failures cause the failure to be written to the alert log, your DBA may ask you to handle the exceptions and to handle rescheduling the job to avoid unnecessary data being written to the alert log.

Related

Indy TIdIcmpClient - how to detect a timeout?

(Using Delphi 2009) I'm trying to write a simple network connection monitor using Indy TidIcmpClient. The idea is to ping an address, then inside an attached TidIcmpClient.OnReply handler test how much data was returned. If the reply contains > 0 bytes then I know the connection succeeded but if either the TidIcmpClient timed out or the reply contains 0 bytes I will assume the link is down
I'm having difficulty understanding the logic of TidIcmpClient as there is no 'OnTimeout' event.
Two sub questions...
Does TidIcmpClient.OnReply get called anyway, either (a) when data is received OR (b) when the timeout is reached, whichever comes first?
How can I distinguish a zero byte reply because of a timeout from a real reply within the timeout period that happens to contain zero bytes (or can't this happen)?
In other words is this sort of code OK or do I need to do something else to tell if it timed out or not
procedure TForm1.IdIcmpClient1Reply(ASender: TComponent; const AReplyStatus: TReplyStatus);
begin
if IdIcmpClient1.ReplyStatus.BytesReceived = 0 then
//we must have timed out, link is down
else
//got some data, connection is up
end;
procedure DoPing;
begin
IdIcmpClient1.ReceiveTimeout := 200;
IdIcmpClient1.Host := '8.8.8.8';
IdIcmpClient1.Ping;
end;
When Ping() exits, the ReplyStatus property contains the same information that is passed to the AReplyStatus parameter of the OnReply event (you are ignoring that parameter). Ping() simply calls the OnReply handler right before exiting, passing it the ReplyStatus property, so you don't actually need to use the OnReply event in your example. All that is doing is breaking up your code unnecessarily.
procedure DoPing;
begin
IdIcmpClient1.ReceiveTimeout := 200;
IdIcmpClient1.Host := '8.8.8.8';
IdIcmpClient1.Ping;
// process IdIcmpClient1.ReplyStatus here as needed...
end;
That being said, you are not processing the ReplyStatus data correctly. The BytesReceived field can be greater than 0 even if the ping fails. As its name implies, it simply reports how many bytes were actually received for the ICMP response. ICMP defines many different kinds of responses. The ReplyStatusType field will be set to the type of response actually received. There are 20 values defined:
type
TReplyStatusTypes = (rsEcho,
rsError, rsTimeOut, rsErrorUnreachable,
rsErrorTTLExceeded,rsErrorPacketTooBig,
rsErrorParameter,
rsErrorDatagramConversion,
rsErrorSecurityFailure,
rsSourceQuench,
rsRedirect,
rsTimeStamp,
rsInfoRequest,
rsAddressMaskRequest,
rsTraceRoute,
rsMobileHostReg,
rsMobileHostRedir,
rsIPv6WhereAreYou,
rsIPv6IAmHere,
rsSKIP);
If the ping is successful, the ReplyStatusType will be rsEcho, and the ReplyData field will contain the (optional) data that was passed to the ABuffer parameter of Ping(). You might also want to pay attention to the FromIpAddress and ToIpAddress fields as well, to make sure the response is actually coming from the expected target machine.
If a timeout occurs, the ReplyStatusType will be rsTimeOut instead.
Try this:
procedure DoPing;
begin
IdIcmpClient1.ReceiveTimeout := 200;
IdIcmpClient1.Host := '8.8.8.8';
IdIcmpClient1.Ping;
if IdIcmpClient1.ReplyStatus.ReplyStatusType = rsEcho then
begin
// got some data, connection is up
end
else if IdIcmpClient1.ReplyStatus.ReplyStatusType = rsTimeout then
begin
// have a timeout, link is down
end
else
begin
// some other response, do something else...
end;
end;
IdIcmpClient companent has OnReply event. This event has AReplyStatus param as type TReplyStatus. TReplyStatus has ReplyStatusType property. This property's type is TReplyStatusTypes. TReplyStatusTypes has rsTimeOut value. So add code to OnReply event and check timeout or other error.
procedure TForm1.IdIcmpClient1Reply(ASender: TComponent;
const AReplyStatus: TReplyStatus);
begin
if AReplyStatus.ReplyStatusType = rsTimeOut then
begin
//do someting on timeout.
end;
end;

IP*Works! SearchMailbox for IMAPS returns all available emails, even unmatching

I am using IP*Works! V9. I try to restrict the returned emails to only the one matching a restriction using SearchMailbox. My code looks like this:
lIMap.Mailbox := 'INBOX';
lIMap.SelectMailbox;
lIMap.CheckMailbox;
lIMap.Config('FETCHAFTERSEARCH=True');
lIMap.SearchMailbox('SUBJECT Diessenhofen UNSEEN');
if (lIMap.MessageCount > 0) then
begin
...
end;
MessageCount always reflects the total number of emails instead of one (there is one match in my inbox).
The IMAP server is Kereo
The documentation says it doesn't work like that. SearchMailbox doesn't restrict what's available to you, instead it calls a user-supplied function and fires an even once for each message in the search result.
Thanks to the answer of #arnt, I figured out a solution that works for me.
Yes, for every Message that corresponds to the search criteria, the event OnMessageInfo is fired.
Since I need to go through all messages in a loop, I ended up doing this:
procedure TReadIMapObjectsFavFktProperty.MessageInfo(Sender: TObject;
const MessageId, Subject, MessageDate, From, Flags: String;
Size:Int64);
begin
if (MessageList.IndexOf(MessageId) < 0) then
begin
MessageList.Add(MessageId);
end;
end;
where MessageList is a TStringList with delimiter ',';
I can then get all messages using either
lIMap.MessageSet := MessageList.Text;
again firing the same event or loop through them using the size of the MessageList like this:
for aa := 0 to MessageList.Count - 1 do
begin
lIMap.MessageSet := MessageList.Strings[aa];
lIMap.FetchMessageInfo;
...
end;

TraceCallBackEvent usage for detecting idle SQL connections

Using Delphi 2006; My aim is to check wether a TSQLConnection instance is idle or not. Therefore, i am setting a Datetime "m_dLastActivity" to "now" each time activity is seen.
As TSQLMonitor is buggy in its trace handling and causes memory problems (see http://qc.embarcadero.com/wc/qcmain.aspx?d=89216), i try to register a trace callback of my own using SetTraceCallbackEvent:
procedure TConnectionGuard.SetSQLConnection(const Value: TSQLConnection);
begin
...
if Assigned ( Value )
and not ( csDesigning in ComponentState ) then begin
...
m_SQLConnection.SetTraceCallbackEvent(U_ConnectionGuard.OnTraceCallBack, integer(self));
...
end;
end;
The callback is just returning the data to the TConnectionGuard object that registered it:
function OnTraceCallBack( CallType: TRACECat; CBInfo: Pointer): CBRType; stdcall;
var Desc: pSQLTraceDesc;
begin
Desc := pSQLTraceDesc(CBInfo);
Result := TConnectionGuard(Desc.ClientData).OnTraceCallBack(CallType, CBInfo);
end;
The event itself:
function TConnectionGuard.OnTraceCallBack(CallType: TRACECat; Desc: pSQLTraceDesc): CBRType;
begin
m_dLastActivity := now;
Result := cbrUSEDEF;
end;
So far, so good, it works. But i am quite uncomfortable with the fact that i have no idea what i have to pass back as CBRType result (defined in DBCommonTypes.pas) to have a minimum performance impact. In fact, i have no idea what i am answering, as the given parameter CallCAT provides no hint how to read / handle it.
Does anyone know if cbrUSEDEF is the right thing to have tracing at a minimum?
EDIT: I realized through the source code of TSQLMonitor that the CBInfo pointer given is not the client info i registered, but a psQLTraceDesc that contains the client info (in this case, the pointer to my Guard). I have adapted the methods to that fact...

Indy, Acces Violatio when too many connections

I have three questions:
is it possible to destroy IdTCPServer by to many connection?
I tried to test my application and when I have several connections - it works very good (even several days) but when sometimes number of connection increases application gives acess violation. I wrote application similates 50 clients sending data constantly (with only sleep(200)). And in this situation IdTCPServer gives exceptions?
My application reseives information from clients by onExecute event and modyfies databases table using
TidNotify and TIdSync classes. I believe it protects crosses connections threads?
Sending information to clients is doing by TTimer (it is only now, I'll change it to other thread).
Have I use in this situation special protection or something like that is enough:
type
PClient = ^TClient;
TClient = record
Activity_time:TDateTime;
AContext: TIdContext;
end;
...
list := server.Contexts.LockList;
try
for i := 0 to list.Count - 1 do
with TIdContext(list[i]) do
begin
if SecondsBetween(now(), PClient(data)^.activity_time) > 6 then
begin
Connection.IOHandler.Close;
Continue;
end;
try
Connection.IOHandler.writeln('E:');
Except
Connection.IOHandler.Close;
end;
end;
finally
server.Contexts.UnlockList;
end;
2.Is a simple way to refuse connection when server is to busy (I think my database isn't complicated (100 rows, only one row is modyfied by one connection) but maybe here is a way to keep stability of server?
3.I know that this question was repeating many times but I didn't find satisfying answer: how to protect application to avoid message exception: "Connection closed gracefully" and "Connection reset by peer"?
Thank You for all advices
is it possible to destroy IdTCPServer by to many connection?
You are asking the wrong question, because you are not actually destroying TIdTCPServer itself, you are simply closing idle connections from an outside thread. That kind of logic can be (and should be) handled inside of the OnExecute event instead, where it is safest to access the connection, eg:
type
PClient = ^TClient;
TClient = record
Activity_time: TDateTime;
Heartbeat_time: TDateTime;
AContext: TIdContext;
end;
procedure TForm1.serverConnect(AContext: TIdContext);
var
Client: PClient;
begin
New(Client);
Client^.Activity_time := Now();
Client^.Heartbeat_time := Client^.Activity_time;
AContext.Data := TObject(Client);
end;
procedure TForm1.serverDisconnect(AContext: TIdContext);
var
Client: PClient;
begin
Client := PClient(AContext.Data);
AContext.Data := nil;
if Client <> nil then Dispose(Client);
end;
procedure TForm1.serverExecute(AContext: TIdContext);
var
Client: PClient;
dtNow: TDateTime;
begin
Client := PClient(AContext.Data);
dtNow := Now();
if SecondsBetween(dtNow, Client^.Activity_time) > 6 then
begin
AContext.Connection.Disconnect;
Exit;
end;
if SecondsBetween(dtNow, Client^.Heartbeat_time) > 2 then
begin
AContext.Connection.IOHandler.WriteLn('E:');
Client^.Heartbeat_time := dtNow;
end;
if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
if not AContext.Connection.IOHandler.CheckForDataOnSource(100) then
Exit;
end;
// process incoming data as needed ...
Client^.Activity_time := Now();
end;
Is a simple way to refuse connection when server is to busy (I think my database isn't complicated (100 rows, only one row is modyfied by one connection) but maybe here is a way to keep stability of server?
The current architecture does not allow for refusing connections from being accepted. You can let the server accept connections normally and then close accepted connections when needed. You can do that in the OnConnect event, or you can set the server's MaxConnection property to a low non-zero number to allow the server to auto-disconnect new connections for you without wasting resources creating new TIdContext objects and threads for them.
Another option is to call the server's StopListening() method when the server is busy, so that new connections cannot reach the server anymore, and then call the server's StartListening() method when you are ready to accept new clients again. Existing clients who are already connected should not be affected, though I have not actually tried that myself yet.
I know that this question was repeating many times but I didn't find satisfying answer: how to protect application to avoid message exception: "Connection closed gracefully" and "Connection reset by peer"?
You should not avoid them. Let them happen, they are normal errors. If they are happening inside of the server's events, just let the server handle them normally for you. That is how TIdTCServer is designed to be used. If they are happening outside of the server's events, such as in your timer, then just wrap the socket operation(s) in a try/except block and move on.

using LocalAsyncVclCall in Delphi

Actually i am using the AsyncCalls library to execute an Query asynchronously in this way.
while AsyncMultiSync([RunQuery], True, 10) = WAIT_TIMEOUT do
begin
FrmProgress.refresh; //Update the ellapsed time in a popup form
Application.ProcessMessages;
end;
and everything works ok.
Now i want to do the same for load the query in a grid.
so i tried this
while LocalAsyncVclCall(#InitGrid, 10) = WAIT_TIMEOUT do
begin
FrmProgress.refresh;
Application.ProcessMessages;
end;
but obviously not compile because the type returned by LocalAsyncVclCall is IAsyncCall and not a Cardinal.
also i tried this, but not works because the initgrid procedure is executed but not asynchronously.
while not LocalAsyncVclCall(#InitGrid, 10).Finished do
begin
FrmProgress.refresh;
//Application.ProcessMessages;
end;
How i can use LocalAsyncVclCall or another function to execute an VCL code asynchronously .
i want something like this.
while ExecuteMyVCLProcedure(#InitGrid) = WAIT_TIMEOUT do
begin
FrmProgress.refresh;
//Application.ProcessMessages;
end;
UPDATE
The InitGrid procedure goes here, the TcxGrid does not provide any event to show the progress of the data load. because that i want to execute this procedure asynchronously.
procedure InitGrid;
begin
cxGrid1DBTableView1.BeginUpdate;
try
cxGrid1DBTableView1.DataController.DataSource:=DataSource1;//in this point assign the query previously executed to the grid.
cxGrid1DBTableView1.ClearItems;
cxGrid1DBTableView1.DataController.CreateAllItems;
for ColIndex:=0 to cxGrid1DBTableView1.ColumnCount-1 do
cxGrid1DBTableView1.Columns[ColIndex].Options.Editing:=False;
finally
cxGrid1DBTableView1.EndUpdate;
end;
end;
Thanks in advance.
UPDATE The InitGrid procedure goes
here, the TcxGrid does not provide any
event to show the progress of the data
load. because that i want to execute
this procedure asynchronously
You can't populate the cxGrid in a different VCL thread because there is only one VCL Thread, the MainThread. Calling LocalAsyncVclCall from the MainThread does nothing else than executing the given function in the thread that calls LocalAsyncVclCall. So it wouldn't do anything different than calling the InitGrid() function in the same thread where your ProcessMessages() call is. Hence ProcessMessages() would be called after the data was loaded into the cxGird what is not what you want.
All the *VclCall() functions are intended to be executed from a different thread than the MainThread.
Without changing the cxGrid's code to support a "progress event" you are out of luck with your attempt.
Use PostMessage to send a custom windows message to FrmProgress.
e.g
WM_PopulateGrid = WM_USER + 1;
procedure WMPopulateGrid(var msg: TMessage); message WM_PopulateGrid;
begin
//load records into the grid.
//You maybe can use a global variable to pass the records from RunQuery
....
end;

Resources