How to use RB, DADE and RAP in a threaded application (webserver, etc) - An answer!
Hi,
I promised a 'pay it forward', so instead of asking a question, I am
going to post some answers. On this newsgroup you will see clearly that in
order to use RB in a multithreaded environment you must:
1) Have RB 6.0
2) Not use DADE
3) Not use RAP
I recently built an ISAPI extension and it runs reports that are loaded from
a database. These reports have the data defined with DADE (which is really
nice, just add a new report all setup and no other coding!) and might have
RAP in them as well as TeeCharts, etc.
Here is how I made it work - I made part of my webserver dll single
threaded. In my example, I call my report like this: (This is a
simplified example, not actual parameters I use)
http://localhost/somefolder/ReportServer.dll?ReportName=SomeReport&Isolate=1
&Param1=SomeValue&Param1Value=SomeValue....
So, as you can see I pass in the report name, some parameters for the report
and a special Isolate parameter.
In my ISAPI dll, on the WebModule OnCreate event, I create/open a handle to
a Mutex (where hMutex is a private variable in webmodule):
procedure TWebModule1.WebModuleCreate(Sender: TObject);
begin
hMutex:=CreateMutex(nil, False, 'WebReportServer');
end;
In the OnDestroy event, be sure and close your Mutex, note it will not
actually close until all handles to it are closed.
procedure TWebModule1.WebModuleDestroy(Sender: TObject);
begin
CloseHandle(hMutex);
end;
Now, in the function where I actually process the request (the webaction), I
do the following:
1) Execute all code that is known to be threadsafe.
2) Establish an exclusive lock between all instances of the dll for the
section of the code not threadsafe.
3) Run unsafe code. Note, this is NOT processor intensive code, so the lock
time should be minimal.
4) Release lock.
3) Optionally exclusively lock the 'PrintToDevices' function of RB, based
on the Isolate parameter above. This is only set to 1 in the database
where RAP is used, since RAP is not necessarily threadsafe. I have not
tried to use RAP in this manner, so I cannot say if it is safe or not. I
can say that DADE is NOT safe. Locking here would be processor intensive
and cause an application with LOTS of requests to have a huge backup. If
you are going to have LOTS of requests, you should consider one of two
things. a) Not allowing RAP, therefore not having to worry about it. or
b) Create a seperate instance of the dll for running rap. That way, normal
requests will not have to wait on any long running rap report. So, you
might have (consider hardcoding the Isolate option in this case):
http://localhost/nonrap/ReportServer.dll?ReportName=SomeReport&Isolate=0...
and
http://localhost/rap/ReportServer.dll?ReportName=SomeReport&Isolate=1...
So, In the end, I have code similar to this.
procedure TWebModule1.WebModule1DisplayReportAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
Report: TppReport;
HD: TPDFDevice;
MS: TMemoryStream;
begin
MS:= TMemoryStream.Create;
try
Report:=LoadReport(Request.QueryFields.Values['ReportName']); //this is
a custom function that loads the report from database.
HD:= TPDFDevice.Create(Self);
try
//Make sure we are the ONLY instance of this DLL running through this
next DADE code. This next line causes the program to sit here and wait
until no other dll is executing this code. Sort of like locking a resource.
WaitForSingleObject(hMutex, INFINITE);
try
//set autosearchcriteria with params that came in. Note, I actually
pass in pipeline.fieldname as the Param1 query string.
Report.AutoSearchCriteriaByName('SomePipeline',
Request.QueryFields.Values['Param1']).SearchExpression:=Request.QueryFields.
Values['Param1Value'];
Report.ShowAutoSearchDialog:=False;
HD.PrintToStream := True;
HD.Publisher := Report.Publisher;
finally
//release our lock. This allows other waiting DLL's to get on
through
ReleaseMutex(hMutex);
end;
//Go ahead and print out report to the PDF file. Note, this is where
MOST of the processing time takes place and it is OUTSIDE of the exclusive
region, if Isoloate=0. In my database, if the report does NOT have RAP, I
can set isolate=0 and make this dll able to process more requests. If I
don't know if RAP is in it, better safe than sorry...
if Request.QueryFields.Values['Isolate']='1' then begin
WaitForSingleObject(hMutex, INFINITE);
try
Report.PrintToDevices;
finally
ReleaseMutex(hMutex);
end;
end else
Report.PrintToDevices;
Response.ContentType := 'application/pdf';
MS.LoadFromStream(HD.Reportstream);
Response.ContentStream := MS;
finally
HD.Free;
// MS.Free; DO NOT FREE. Response object will free when done!
Report.Free;
end;
except on E: Exception do begin
MS.Free; //free allocated MS on except only...
Response.Content:=''+E.Message+'';
end;
end;
end;
Comments? Questions? Thoughts?
Debt Paid! :-)
Wayne Brantley
I promised a 'pay it forward', so instead of asking a question, I am
going to post some answers. On this newsgroup you will see clearly that in
order to use RB in a multithreaded environment you must:
1) Have RB 6.0
2) Not use DADE
3) Not use RAP
I recently built an ISAPI extension and it runs reports that are loaded from
a database. These reports have the data defined with DADE (which is really
nice, just add a new report all setup and no other coding!) and might have
RAP in them as well as TeeCharts, etc.
Here is how I made it work - I made part of my webserver dll single
threaded. In my example, I call my report like this: (This is a
simplified example, not actual parameters I use)
http://localhost/somefolder/ReportServer.dll?ReportName=SomeReport&Isolate=1
&Param1=SomeValue&Param1Value=SomeValue....
So, as you can see I pass in the report name, some parameters for the report
and a special Isolate parameter.
In my ISAPI dll, on the WebModule OnCreate event, I create/open a handle to
a Mutex (where hMutex is a private variable in webmodule):
procedure TWebModule1.WebModuleCreate(Sender: TObject);
begin
hMutex:=CreateMutex(nil, False, 'WebReportServer');
end;
In the OnDestroy event, be sure and close your Mutex, note it will not
actually close until all handles to it are closed.
procedure TWebModule1.WebModuleDestroy(Sender: TObject);
begin
CloseHandle(hMutex);
end;
Now, in the function where I actually process the request (the webaction), I
do the following:
1) Execute all code that is known to be threadsafe.
2) Establish an exclusive lock between all instances of the dll for the
section of the code not threadsafe.
3) Run unsafe code. Note, this is NOT processor intensive code, so the lock
time should be minimal.
4) Release lock.
3) Optionally exclusively lock the 'PrintToDevices' function of RB, based
on the Isolate parameter above. This is only set to 1 in the database
where RAP is used, since RAP is not necessarily threadsafe. I have not
tried to use RAP in this manner, so I cannot say if it is safe or not. I
can say that DADE is NOT safe. Locking here would be processor intensive
and cause an application with LOTS of requests to have a huge backup. If
you are going to have LOTS of requests, you should consider one of two
things. a) Not allowing RAP, therefore not having to worry about it. or
b) Create a seperate instance of the dll for running rap. That way, normal
requests will not have to wait on any long running rap report. So, you
might have (consider hardcoding the Isolate option in this case):
http://localhost/nonrap/ReportServer.dll?ReportName=SomeReport&Isolate=0...
and
http://localhost/rap/ReportServer.dll?ReportName=SomeReport&Isolate=1...
So, In the end, I have code similar to this.
procedure TWebModule1.WebModule1DisplayReportAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
Report: TppReport;
HD: TPDFDevice;
MS: TMemoryStream;
begin
MS:= TMemoryStream.Create;
try
Report:=LoadReport(Request.QueryFields.Values['ReportName']); //this is
a custom function that loads the report from database.
HD:= TPDFDevice.Create(Self);
try
//Make sure we are the ONLY instance of this DLL running through this
next DADE code. This next line causes the program to sit here and wait
until no other dll is executing this code. Sort of like locking a resource.
WaitForSingleObject(hMutex, INFINITE);
try
//set autosearchcriteria with params that came in. Note, I actually
pass in pipeline.fieldname as the Param1 query string.
Report.AutoSearchCriteriaByName('SomePipeline',
Request.QueryFields.Values['Param1']).SearchExpression:=Request.QueryFields.
Values['Param1Value'];
Report.ShowAutoSearchDialog:=False;
HD.PrintToStream := True;
HD.Publisher := Report.Publisher;
finally
//release our lock. This allows other waiting DLL's to get on
through
ReleaseMutex(hMutex);
end;
//Go ahead and print out report to the PDF file. Note, this is where
MOST of the processing time takes place and it is OUTSIDE of the exclusive
region, if Isoloate=0. In my database, if the report does NOT have RAP, I
can set isolate=0 and make this dll able to process more requests. If I
don't know if RAP is in it, better safe than sorry...
if Request.QueryFields.Values['Isolate']='1' then begin
WaitForSingleObject(hMutex, INFINITE);
try
Report.PrintToDevices;
finally
ReleaseMutex(hMutex);
end;
end else
Report.PrintToDevices;
Response.ContentType := 'application/pdf';
MS.LoadFromStream(HD.Reportstream);
Response.ContentStream := MS;
finally
HD.Free;
// MS.Free; DO NOT FREE. Response object will free when done!
Report.Free;
end;
except on E: Exception do begin
MS.Free; //free allocated MS on except only...
Response.Content:=''+E.Message+'';
end;
end;
end;
Comments? Questions? Thoughts?
Debt Paid! :-)
Wayne Brantley
This discussion has been closed.
Comments
threadsafe, we haven't done enough multithreaded testing with it.
Cheers,
Jim Bennett
Digital Metaphors
http://www.digital-metaphors.com
info@digital-metaphors.com
exclusive lock around this code:
Report.AutoSearchCriteriaByName('SomePipeline',
equest.QueryFields.Values['Param1']).SearchExpression:=Request.QueryFields.
Values['Param1Value'];
Other than that, I did not see any problems with DADE, from a testing point
of view. However, I did not review the code.
I am using DADE, but not RAP (yet). I have successfully done multithread
testing on a multiprocessor server using the code I submitted in the
previous thread, WITHOUT using the Isolate flag. This means that the dll
is only 'exclusively locked' for a VERY short time frame (while parameters
are being set). This should cause very little contention even at high
loads. I also considered creating some load balancing DLL that uses my
existing ReportServer.dll to do the work. The proxy dll could then load the
same dll, but located in different places on the ISAPI server. Each load of
a different DLL would essentially elimiate any threading issues between the
loads. Then, if I made the Mutex name something that could be set from
outside of the DLL, each instance of the dll loaded by the proxy could
handle multiple requests regardless of threading (where each instance of the
DLL is locking inside its own Mutex, which does not hold up the other
instances).
However, for now, this implementation (as long as ISOLATE=0) can handle
quite a load without bogging down. (Well, it bogs down, but that is due to
the time it takes to run the report.PrintToDevices command, not the time it
spent waiting on the Mutex to open up.)
Hopefully the DM guys will work on DADE and possibly RAP to get everything
thread safe.
Mike, I will try to hang around and be more active here. After all, this
truely is a great product.
Wayne