Ada select multiple entries - task

I'm looking for a way to select on multiple entries. I've got the following task and select block. The intention is to run multiple (2) tasks elsewhere, run until one completes, or timeout after some amount of time
task type t_startup_task is
entry start;
entry started;
end t_startup_task;
task body t_startup_task is
startup_sig : Boolean := False;
begin
accept start;
busy : loop -- wait for external flag to become true
status.read_signal (startup_sig);
if startup_sig then
exit busy;
end if;
delay 0.1;
end loop busy;
accept started;
end t_startup_task;
<...>
startup_task.start;
select
startup_task.started;
or
state.wait_done; -- other entry
abort startup_task;
return False;
or
delay 4.0;
end select;
However, this results in the following compile error:
only allowed alternative in timed entry call is delay
"or" not allowed here
What's the best way of actually doing this?

Unfortunately you can't use a select statement for deciding between multiple entry calls. You can however use them to decide between accepting entries, so an implementation with three tasks should work.
You can still have your return value by using an out parameter on the final entry call.
task startup_task is
entry start;
entry wait_done;
entry signalled;
entry started (success : out boolean);
end startup_task;
-- This task waits upon your other entry, then calls the startup_task
-- entry once it has completed
task startup_wait is
entry start;
end startup_wait;
task body startup_wait is
begin
accept start;
state.wait_done;
startup_task.wait_done;
end startup_wait;
-- This task contains your busy loop and calls the startup_task
-- entry once it has completed
task startup_signal is
entry start;
end startup_signal;
task body startup_signal is
begin
accept start;
busy : loop -- wait for external flag to become true
status.read_signal (startup_sig);
if startup_sig then
exit busy;
end if;
delay 0.1;
end loop busy;
startup_task.signalled;
end startup_signal;
-- This task provides the functionality of your invalid select statement,
task body startup_task is
success : boolean := False;
begin
-- These start signals ensure that the subtasks wait for synchronisation
accept start;
startup_wait.start;
startup_signal.start;
select
accept signalled;
abort startup_wait;
success := True;
or
accept wait_done;
abort startup_signal;
or
delay 4.0
abort startup_wait;
abort startup_signal;
end select;
accept started (success);
end startup_task;
<...>
result : boolean;
begin
-- this block replaces your invalid select statement
startup_task.start;
startup_task.started(result);
return result;
end;
Note, I have not tested or compiled this code, but it should give an idea towards a solution.

Technically, the language allows this:
select
delay 4.0;
...
then abort
select
Entry1;
...
then abort
Entry2;
...
end select;
end select;
which might do what you want. However, the best way is probably to have the tasks check in with a protected object, and wait on an entry of the PO:
protected Multi_Wait is
procedure Task1_Ready;
procedure Task2_Ready;
entry Wait_For_Either (Task_1 : out Boolean; Task_2 : out Boolean);
private -- Multi_Wait
Task1 : Boolean := False;
Task2 : Boolean := False;
end Multi_Wait;
Then your code can do
select
Multi_Wait.Wait_For_Either (Task_1 => Task_1, Task_2 => Task_2);
if not Task_1 and Task_2 then
abort T1;
return False;
end if;
or
delay 4.0;
end select;
Your tasks call the appropriate procedure instead of waiting for a 2nd entry call.

Related

Tasking: Very slow response

My actual program creates a task which I does not react to control messages as it should. As it has grown pretty large, I present a short test program with the same control logic. It creates a background task which repeates a loop every 0.1s. Depending on a protected flag "running" it prints out an 'a', or does nothing. When I set "running", the program goes off immediately, printing the 'a's. But when I set "stopreq", it takes seconds, sometimes well over 10, until it stops. I would expect a response time of 0.1 or 0.2s.
Anybody has an explanation and a solution?
My main program opens a window with 3 buttons: "Start", which calls the subprogram Start below, "Stop", which calls Request_Stop, and "Quit" calling Request_Quit. I am working on a PC running Linux.
Here comes the body of my Tasking package. If you need more, just tell me and I post the other parts.
with Ada.Text_IO;
with Ada.Calendar;
package body Tasking is
t_step: constant Duration:= 0.1;
dti: Duration;
protected Sync is
procedure Start; -- sim. shall start
procedure Request_Stop; -- sim. shall stop
procedure Stop_If_Req;
function Is_Running return Boolean; -- sim. is running
procedure Request_Quit; -- sim.task shall exit
function Quit_Requested return Boolean; -- quit has been requested
procedure Reset_Time;
procedure Increment_Time (dt: Duration);
procedure Delay_Until;
private
running: Boolean:= false;
stopreq: Boolean:= false;
quitreq: Boolean:= false;
ti: Ada.Calendar.Time;
end Sync;
protected body Sync is
procedure Start is begin running:= true; end Start;
procedure Request_Stop is
begin
if running then stopreq:= true; end if;
end Request_Stop;
procedure Stop_If_Req is
begin
if stopreq then
running:= false;
stopreq:= false;
end if;
end Stop_If_Req;
function Is_Running return Boolean is begin return running; end Is_Running;
procedure Request_Quit is begin quitreq:= true; end Request_Quit;
function Quit_Requested return Boolean
is begin return quitreq; end Quit_Requested;
procedure Reset_Time is begin ti:= Ada.Calendar.Clock; end Reset_Time;
procedure Increment_Time (dt: Duration) is
begin
ti:= Ada.Calendar."+"(ti, dt);
dti:= dt;
end Increment_Time;
procedure Delay_Until is
use type Ada.Calendar.Time;
now: Ada.Calendar.Time;
begin
now:= Ada.Calendar.Clock;
while ti < now loop -- while time over
ti:= ti + dti;
end loop;
delay until ti;
end Delay_Until;
end Sync;
task body Thread is
begin
Ada.Text_IO.Put_Line("starting task");
while not Sync.Quit_Requested loop
if sync.Is_Running then
sync.Increment_Time (t_step);
sync.Delay_Until;
Ada.Text_IO.Put("a");
sync.Stop_If_Req;
else
delay t_step;
sync.Reset_Time;
end if;
end loop;
end Thread;
procedure Start is
begin
Sync.Start;
end Start;
function Is_Running return Boolean is
begin
return Sync.Is_Running;
end Is_Running;
procedure Request_Stop is
begin
Ada.Text_IO.Put_Line("");
Sync.Request_Stop;
end Request_Stop;
procedure Request_Quit is
begin
Sync.Request_Quit;
end Request_Quit;
end Tasking;
Your code is too poorly described and commented for me to understand what you want it to do.
But using a "delay until" statement in a protected operation (Sync.Delay_Until) is not correct -- it is a "bounded error". If it works, it probably blocks all other calls to that protected object until the delay has expired. I suggest you should start there when you try to correct the code.
Thanks to your comments, the program is working correctly now.
To Jesper Quorning:
"Time in past" was not a problem here, but could be so in my real software project. I included a remedy nevertheless in the corrected version below, so it could serve as a pattern for others. Thanks for the hint.
To Niklas Holsti:
I shifted "delay until" out of the protected operation and the program now works as expected, thanks for the explanation. (Sometimes a program which works badly is worse than one which doesn't work at all, because in the latter case you take compiler warnings more seriously.)
What I want to do is:
Execute an application at precisely defined time intervals
Being able to enable/disable it anytime
Giving it time to stop only when in a well defined state
Knowing, when it really has stopped
Controlled termination of the whole thing.
My idea of realization was:
Call the application in an endless loop as separate task
Check whether exit has been requested at the top of the loop
An if statement which executes the application if enabled
Simple delay otherwise.
Control from other packages is done with the public subprograms
Start, Is_Running, Request_Stop, Request_Quit.
Besides the corrections mentioned above, I renamed some items to make their role clearer.
If there is a standard solution, different from mine, I would like to see it.
Here comes my corrected program:
with Ada.Text_IO;
with Ada.Calendar;
package body Tasking is
t_step: constant Duration:= 0.1;
protected Sync is
procedure Enable_App; -- enable application
procedure Request_Disable; -- request disabling of app.
procedure Disable_If_Req; -- disable app. if requested
function Enabled return Boolean; -- application is enabled
procedure Request_Quit; -- task shall terminate
function Quit_Requested return Boolean; -- task termination requested
procedure Set_Time (t: Ada.Calendar.Time); -- set execution time
function Get_Time return Ada.Calendar.Time; -- get execution time
private
running: Boolean:= false; -- application is enabled
stopreq: Boolean:= false; -- disabling has been requested
quitreq: Boolean:= false; -- task termination requested
ti: Ada.Calendar.Time; -- time of next app. execution
end Sync;
protected body Sync is
procedure Enable_App is begin running:= true; end Enable_App;
procedure Request_Disable is
begin
if running then stopreq:= true; end if;
end Request_Disable;
procedure Disable_If_Req is
begin
if stopreq then
running:= false;
stopreq:= false;
end if;
end Disable_If_Req;
function Enabled return Boolean is
begin return running; end Enabled;
procedure Request_Quit is
begin quitreq:= true; end Request_Quit;
function Quit_Requested return Boolean
is begin return quitreq; end Quit_Requested;
procedure Set_Time (t: Ada.Calendar.Time) is
begin ti:= t; end Set_Time;
function Get_Time return Ada.Calendar.Time is
begin return ti; end Get_Time;
end Sync;
task body Thread is
use type Ada.Calendar.Time;
now: Ada.Calendar.Time;
begin
Ada.Text_IO.Put_Line("starting task");
while not Sync.Quit_Requested loop
if sync.Enabled then
-- increment time if it is too late
now:= Ada.Calendar.Clock;
while sync.Get_Time <= now loop
sync.Set_Time (sync.Get_Time + t_step);
end loop;
-- wait until next execution time
delay until sync.Get_Time;
-- execute application and set time for next execution
Ada.Text_IO.Put(".");
sync.Set_Time (sync.Get_Time + t_step);
-- disable application if this has been requested
sync.Disable_If_Req;
else
-- wait for enabling and set time for next execution
delay t_step;
sync.Set_Time (Ada.Calendar.Clock + t_Step);
end if;
end loop;
end Thread;
procedure Start is
begin
Sync.Enable_App;
end Start;
function Is_Running return Boolean is
begin
return Sync.Enabled;
end Is_Running;
procedure Request_Stop is
begin
Ada.Text_IO.Put_Line("");
Sync.Request_Disable;
end Request_Stop;
procedure Request_Quit is
begin
Sync.Request_Quit;
end Request_Quit;
end Tasking;
Some general ideas about Ada tasking:
Conceptually, an Ada task has its own processor that no other task runs on. Scheduling, such as determining when the task should next do something, is usually the task's responsibility.
When the task has to wait for an event, it is usually best for the task to block than to poll.
Ada.Calendar is typically local time, and can even go backward. Tasks should schedule themselves with Ada.Real_Time if possible.
So I would do something like
protected Control is
procedure Set_Running (Running : in Boolean := True);
-- Set whether the task should run or not
-- Initially Running is False
entry Wait_Until_Running;
-- Blocks the caller until Running is set to True
function Running return Boolean;
-- Returns the current value of Running
procedure Quit_Now;
-- Tell the task to quit
function Quitting return Boolean;
-- Returns True when Quit_Now has been called; False otherwise
private -- Control
Run : Boolean := False;
Quit : Boolean := False;
end Control;
task body T is
Step : constant Ada.Real_Time.Time_Span := Ada.Real_Time.Milliseconds (100);
Next : Ada.Real_Time.Time;
begin -- T
Forever : loop
Control.Wait_Until_Running;
Next := Ada.Real_Time.Clock;
Run : while Control.Running loop
exit Forever when Control.Quitting;
delay until Next;
Next := Next + Step;
-- The task does its thing here
Ada.Text_IO.Put (Item => 'a');
end loop Run;
end loop Forever;
end T;
protected body Control is
procedure Set_Running (Running : in Boolean := True) is
begin
Run := Running;
end Set_Running;
entry Wait_Until_Running when Run is
begin
null;
end Wait_Until_Running;
function Running return Boolean is (Run);
procedure Quit_Now is
begin
Run := True; -- Release the task if it is blocked on Wait_Until_Running
Quit := True;
end Quit_Now;
function Quitting return Boolean is (Quit);
end Control;
Thanks for your new description of the goals, #hreba, and good to hear that your program is working as expected now.
I agree with #Anh Vo that the application task could be controlled with task entries, but I think that using a protected object (PO) is mostly preferable because it decouples the caller (the main task) from the application task -- it does not make the caller wait until the application task is ready to accept the entry call. However, I would reduce the PO to handle the inter-task communication. In the #hreba code, I see no reason for the protected operations Set_Time and Get_Time, as the timing seems completely controlled by the application task anyway.
Here is how I would program this. First, instead of a number of Booleans I would define an enumeration for the state of the application task:
type App_State is (Enabled, Disabled, Stopping, Stopped);
In the #hreba code, it seems the application task is initially "not running", so
Initial_State : constant App_State := Disabled;
The PO would then be declared as (I leave out the comments because I will comment in prose):
protected App_Control is
procedure Enable;
procedure Disable;
procedure Stop;
entry Get_New_State (New_State : out App_State);
entry Wait_Until_Stopped;
private
State : App_State := Initial_State;
Old_State : App_State := Initial_State;
end App_Control;
The PO operations Enable, Disable, and Stop do the evident things:
procedure Enable
is
begin
State := Enabled;
end Enable;
and analogously for Disable and Stop. Stop sets State to Stopping.
The entry Get_New_State will be called in the application task to receive the new state. It is an entry, instead of a function or a procedure, so that it can be made callable only when the state has changed:
entry Get_New_State (New_State : out App_State)
when State /= Old_State
is
begin
New_State := State;
Old_State := State;
if State = Stopping then
State := Stopped;
end if;
end Get_New_State;
Here I made an automatic transition from Stopping to Stopped as soon as the application task has been given New_State = Stopping. If the application task has to do some clean-up before it can be considered really stopped, that transition should be moved to a separate operation, for example procedure App_Control.Has_Stopped, to be called from the application task after that clean-up.
The entry Wait_Until_Stopped can be called in the main task to wait until the application task has stopped. It is very simple:
entry Wait_Until_Stopped
when State = Stopped
is
begin
null;
end Wait_Until_Stopped;
Now for the application task. My design differs in one main point from the original: to make the task more responsive to "stop" commands, I use a timed entry call of Get_New_State to let that call take place while the application task is waiting for its next activation time. This may not matter much if the task is running every 0.1 s, but in some other scenario the task might be running every 10 s, or every 10 minutes, and then it could matter a great deal. So here is the task body:
task body App_Thread
is
use Ada.Calendar;
Period : constant Duration := 0.1;
State : App_State := Initial_State;
Next_Time : Time := Clock + Period;
begin
loop
select
App_Control.Get_New_State (New_State => State);
exit when State = Stopping;
or
delay until Next_Time;
if State = Enabled then
Execute_The_Application;
end if;
Next_Time := Next_Time + Period;
end select;
end loop;
end App_Thread;
To uncouple between calling task and called task, do not use accept {entry name} do statement. Just break this into two separate statements as shown below with entry Start_Work for example
--coupling case
accept Start_Work do
Execute_The_Application
end Start_Work;
--uncoupling case
accept Start_Work;
Execute_The_Application;
Here is my implementation of #hreba's requirement.
package Tasking is
-- more codes
task Thread2 is
entry Start_Work;
entry Stop_Work;
entry Quitting_Time;
end Thread2;
task body Thread2 is
use type Ada.Real_Time.Time;
T_Step : constant Ada.Real_Time.Time_Span := Ada.Real_Time.Milliseconds (100);
Next_Time : Ada.Real_Time.Time := Ada.Real_Time.Clock;
begin
Ada.Text_IO.Put_Line("task Thread2 elaborates and activates");
Forever: loop
select
accept Quitting_Time;
Ada.Text_IO.Put_Line ("task Thread2 terminates. Later alligator");
exit Forever; -- later alligator
or
accept Start_Work;
Working: loop
Ada.Text_IO.Put_Line ("Ada");
Next_Time := Next_Time + T_Step;
select
accept Stop_Work;
exit Working;
or
delay until Next_Time;
end select;
end loop Working;
or
Terminate;
end select;
end loop Forever;
end Thread2;
-- more codes
end Tasking;

Return log of activities inside a Stored Procedure in Teradata

The requirement is to get the log of all the DML statements executed inside a Stored Procedure in teradata. This Stored procedure is being called inside a shell script using BTEQ. The ask is to capture the log of all activities performed inside the shell into a log file in unix. If any error occurs, that also needs to be captured.
The Stored Procedure contains 20 Merge SQLs and i want to see how many rows got affected and how long each statement took. I tried calling the individual merges through BTEQ(instead of calling it in a Stored Procedure), but there is significant time gain if its called inside an SP. Right now, all i can see is that the Stored procedure has been completed successfully.
replace procedure SP_Test()
BEGIN
MERGE INTO T1 using T2 on T1.C1 = T2.C1
WHEN NOT MATCHED THEN INSERT (C1,C2,C3) VALUES (T2.C1,T2.C2,T3.C3)
WHEN MATCHED
THEN UPDATE
SET
C1 = CASE statement 1
C2 = CASE statement 2
c3 = CASE statement 3 ;
END;
inside the BTEQ block of test.sh shell script,
bteq << EOF >>LOgFILE.txt 2>&1
.LOGON source/user,password;
.COMPILE FILE = sp_merge.txt;
.IF ERRORCODE <> 0 THEN .GOTO QUITNOTOK
call SP_Test();
.IF ERRORCODE <> 0 THEN .GOTO QUITNOTOK
.LABEL QUITOK
.LOGOFF
.QUIT 0
.LABEL QUITNOTOK
.QUIT 2
EOF
Log File currently
**** Procedure has been executed.
**** Total elapsed time was 2 minutes and 47 seconds.
Expected Output
**** Merge completed. 5641191 rows affected.
5641191 rows inserted, no rows updated, no rows deleted.
**** Total elapsed time was 2 minutes and 45 seconds.
You can do one thing.
Write another procedure and keep an insert statement like this below :
REPLACE PROCEDURE <DB_USER>. log_process_run_message ( IN in_n_process_run_id DECIMAL(18,0) ,
IN in_v_process_name VARCHAR(30) ,
IN in_v_message_txt VARCHAR(50000) ,
IN in_d_message_ts TIMESTAMP(6)
)
BEGIN
BT; -- Begin Transaction
INSERT INTO <SCHEMA_NAME>.cop_process_run_message_op
(
process_name ,
message_txt ,
message_ts
)
VALUES (
in_v_process_name ,
in_v_message_txt ,
in_d_message_ts
);
ET; -- End Transaction
END;
In your main procedure include an exit handler;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET lv_sql_state = SQLSTATE;
SET ln_sql_code = SQLCODE;
SET out_status = 'FAILED';
ROLLBACK;
SET lv_my_mesg = 'Error in : ' || lv_my_mesg || ' - SQLSTATE: ' || lv_sql_state || ', SQLCODE: ' || CAST(ln_sql_code AS CHAR);
CALL <DB_USER>.log_process_run_message (ln_run_id,lv_err_component,lv_my_mesg,CURRENT_TIMESTAMP(6));
END;
Also define the below variable in your main procedure
SET lv_err_component='ur proc name'
Now call this procedure inside your main procedure after every merge statement:
SET lv_my_mesg ='Merge 1 completed '||CAST(CURRENT_TIMESTAMP(0) AS CHAR(19));
CALL <DB_USER>.log_process_run_message (lv_err_component,lv_my_mesg,CURRENT_TIMESTAMP(6));
What this will do is, it will insert a record after successful execution of your merge statements and in case there is an error it will
execute the call statement in the exit handler and will document your error message as well in case there is any.Let me know if this helps.

Ada 95 Tasking: How can I send a rendezvous from one instantiated task to another that is in a separate instantiated Task?

I have created defined 2 Task Types, and instantiated them in the main program.
How do I send rendezvous between the separate tasks?
Procedure Task_Prog is
task type Task_one is
entry Redezvous_One;
end Task_one;
task type Task_Two is
entry Redezvous_Two;
end Task_Two;
task body Task_one is
Finished : Boolean := False;
begin
while not Finished loop
accept Redezvous_One do
finished := True;
Task_Instantiation_B.Redezvous_Two;
end Redezvous_One;
end loop;
end Task_one;
task body Task_Two is
Finished : Boolean := False;
begin
while not Finished loop
accept Redezvous_Two do
finished := True;
end Redezvous_Two;
end loop;
end Task_Two;
Task_Instantiation_A : Task_one;
Task_Instantiation_B: Task_Two;
begin
Task_Instantiation_A.Redezvous_One;
end Task_Prog;
Compilation Error is:
task_prog.ada: Error: line 17 col 4 LRM:4.1(3), Direct name, Task_Instantiation_B, is not visible, Ignoring future references
I'm looking for one task to be able to pipe commands/rendezvous to other tasks that have been instantiated.
Any help will be greatly appreciated.
Simply moving the task instantiations above the task body declarations will make them visible to the task bodies, and with this change, your example compiles successfully.
That is...
Procedure Task_Prog is
task type Task_one is
entry Rendezvous_One;
end Task_one;
task type Task_Two is
entry Rendezvous_Two;
end Task_Two;
Task_Instantiation_A : Task_one;
Task_Instantiation_B : Task_Two;
task body Task_one is
... etc
This may not be compatible with your larger problem. If not, then add further details or constraints to the question.
First off, you need to realize that everything in Accept/do...end is the rendezvous, so calling Task_Instantiation_B.Redezvous_Two inside Redezvous_One is likely to deadlock. (Given it's a task type, you could have a different task than the one calling in Redezvous_One, which would solve that problem.)
So, change this:
while not Finished loop
accept Redezvous_One do
finished := True;
Task_Instantiation_B.Redezvous_Two;
end Redezvous_One;
end loop;
to this:
while not Finished loop
accept Redezvous_One do
finished := True;
end Redezvous_One;
end loop;
Task_Instantiation_B.Redezvous_Two;
Secondly you need to reorder your task-spec and bodies:
Task type Task_one is --...
Task type Task_two is --...
Task body Task_one is --...
Task body Task_two is --...
should do it. -- Now, if you're not going to have multiples change the task type to just plain task and you should be good.

Procedure Send Mail Alert

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.

implementing a timeout when reading a file with Delphi

I have an app written in Delphi 2006 that regularly reads from a disk file located elsewhere on a network (100Mb ethernet). Occasionally the read over the network takes a very long time (like 20 secs) and the app freezes, as the read is done from an idle handler in the main thread.
OK, I could put the read operation into it's own thread, but what I would like to know is whether it is possible to specify a timeout for a file operation, so that you can give up and go and do something else, or report the fact that the read has snagged a bit earlier than 20 seconds later.
function ReadWithTimeout (var Buffer ;
N : integer ;
Timeout : integer) : boolean ;
begin
Result := false
try
SetReadTimeout (Timeout) ; // <==========================???
FileStream.Read (Buffer, N) ;
Result := true ;
except
...
end ;
end ;
Open the file for asynchronous access by including the File_Flag_Overlapped flag when you call CreateFile. Pass in a TOverlapped record when you call ReadFile, and if the read doesn't complete immediately, the function will return early. You can control how long you wait for the read to complete by calling WaitForSingleObject on the event you store in the TOverlapped structure. You can even use MsgWaitForMultipleObjects to wait; then you can be notified as soon as the read completes or a message arrives, whichever comes first, so your program doesn't need to hang at all. After you finish processing messages, you can check again whether the I/O is complete with GetOverlappedResult, resume waiting, or give up on the I/O by calling CancelIo. Make sure you read the documentation for all those functions carefully; asynchronous I/O isn't trivial.
After you've moved the read operation to a thread, you could store the value returned by timeGetTime before reading:
isReading := true;
try
startedAt := timeGetTime;
FileStream.Read (Buffer, N);
...
finally
isReading := false;
end;
and check in the idle handler if it's taken too long.
eg:
function ticksElapsed( FromTicks, ToTicks : cardinal ) : cardinal;
begin
if FromTicks < ToTicks
then Result := ToTicks - FromTicks
else Result := ( high(cardinal) - FromTicks ) + ToTicks; // There was a wraparound
end;
...
if isReading and ( ticksElapsed( startedAt, timeGetTime ) > 10 * 1000 ) // Taken too long? ~10s
then // Do something

Resources