RB threads and NexusDB database errors (v 15.05)
Hello
Our customers get occasional "System has been illegally re-entered"
errors when running reports, and we know that this is ONLY caused when
different threads are accessing the same session concurrently. We can
see in the error log that TppPageRequestThread is involved (we WANT its
functionality) and that its ExecuteReport method has a comment thus:
// enable background generation, without requiring a separate
database connection
ppUtils.ppBeginBackgroundPrinting;
Digging deeper we see that ppBeginBackgroundPrinting just increments the
global var "uBackgroundPrinting". Fine.
But it seems to us that the crucial test function "ppInMainThread" is
wrong (or at least it doesn't make sense to us):
if (uOutlinePreviewing = 0) and (uBackgroundPrinting = 0) then
Result := GetCurrentThreadID = MainThreadID
else
Result := True;
end; {function, ppInMainThread}
Surely if TppPageRequestThread has incremented uBackgroundPrinting then
the ppInMainThread will always return TRUE, which isn't necessary
correct is it? Why not just ..
Result := GetCurrentThreadID = MainThreadID
...?
Given the prevalence of ppInMainThread being called throughout the RD
code, could this be a problem? Or have we misunderstood something
fundamental.
(Apologies for the invidious challenging of your code - we are just at a
loss to find any solution for this and its causing us headaches).
Our customers get occasional "System has been illegally re-entered"
errors when running reports, and we know that this is ONLY caused when
different threads are accessing the same session concurrently. We can
see in the error log that TppPageRequestThread is involved (we WANT its
functionality) and that its ExecuteReport method has a comment thus:
// enable background generation, without requiring a separate
database connection
ppUtils.ppBeginBackgroundPrinting;
Digging deeper we see that ppBeginBackgroundPrinting just increments the
global var "uBackgroundPrinting". Fine.
But it seems to us that the crucial test function "ppInMainThread" is
wrong (or at least it doesn't make sense to us):
if (uOutlinePreviewing = 0) and (uBackgroundPrinting = 0) then
Result := GetCurrentThreadID = MainThreadID
else
Result := True;
end; {function, ppInMainThread}
Surely if TppPageRequestThread has incremented uBackgroundPrinting then
the ppInMainThread will always return TRUE, which isn't necessary
correct is it? Why not just ..
Result := GetCurrentThreadID = MainThreadID
...?
Given the prevalence of ppInMainThread being called throughout the RD
code, could this be a problem? Or have we misunderstood something
fundamental.
(Apologies for the invidious challenging of your code - we are just at a
loss to find any solution for this and its causing us headaches).
This discussion has been closed.
Comments
ReportBuilder does not spawn multiple threads that simultaneously access
a DB session. It uses a single background thread that solely accesses
the database.
If you are having issues multiple threads accessing the same session, it
is likely due to visual controls (or other objects) sharing the same DB
connections as ReportBuilder. The best option is to keep all Report
datasets and connections separate from other DB controls and objects in
your application. You can also try disabling them before printing.
Nico Cizik
Digital Metaphors
http://www.digital-metaphors.com
We deliberately give RB is own session and database to avoid any
conflict with any other part of our application. You say that RB doesn't
does not spawn multiple threads that simultaneously access
a DB session, but surely that's ONLY the case if the "ppInMainThread"
function is returning correctly (isn't that the point of this function?)
and that is what I am questioning. It doesn't look right to me, but
maybe I have a blind spot here. Could you confirm?
P.S. I emailed you accidentally instead of commenting. Always get that
wrong when using Thunderbird. Sorry.
The code you are referring to is used in numerous placed in
ReportBuilder including the Server and Background Printing. It should
not be altered.
When using the multi-page view (threaded page cache) only a single
thread is created to handle page generation in the background. This
thread accesses the database. ReportBuilder does not employ the main
thread to access any data during this time so if Nexus is detecting a
conflict, it is likely elsewhere in your application.
If you have a simple example that reliably recreates this issue using
only Delphi, RB, and Nexus components, please send it to us in .zip
format and we will test it with the latest version of Nexus.
Nico Cizik
Digital Metaphors
http://www.digital-metaphors.com
I think I've worked something out. It only happens when switching from
from the Preview page of the Report Designer back to the Design tab.
Now, this forces the TppTemplate.OnLoadEnd event to fire and we use that
when loading the report from database (Report.Template.OnLoadEnd :=
OnTemplateLoadEnd) and indeed we DO access the database from the session
the report uses to read in some meta-data that we store in the reporting
table.
I think that what's happening is the cancelling of the report is
happening asynchronously, so the thread that is running the report is
occasionally still tidying up when OnLoadEnd gets called. Now, if there
was a way of telling that the OnLoadEnd event is being called in the
context of the report being restored (via
TppDesignPreviewManager.RestoreReportFromStream) rather than loaded from
database then we could jump out of OnLoadEnd and I think we'll be safe.
Any suggestions?
procedure TnxRBDesignerEngine.DesignerTabChange(Sender: TObject; NewTab:
string; var AllowChange: Boolean);
begin
if NewTab = 'Preview' then
Report.Template.OnLoadEnd := nil else
Report.Template.OnLoadEnd := OnTemplateLoadEnd;
end;
Thanks for helping me end up here.