To whom it may concern,
We are having problems with memory leaks and unexpected exceptions in our production environment using the DevExpress-VCL PdfViewer 19.1.5.
In our production applications, the user is able to select from multiple available pdf documents.
Clicking between the different pdfs (e.g in a grid, list or button) forces the viewer to abort the previously selected pdf and start loading the next selected pdf.
It is in this scenario that the issues arise.
To reproduce these errors I have created a "Simple" stress testing application, using a windows timer, to emulate the production circumstances.
The stress testing application has been able to surface these errors.
The problem has been replicated in both Delphi XE3 and Delphi 10.3.2, using the DevExpress VCL subscription 19.1.5
Please find attached a zip file with a Delphi project "Simple.dproj" including some of our production PDFs to reproduce the problem.
I have included two screen shots, in the Errors folder, to highlight some of the memory errors encountered in production.
Note: FastMM is included and must be enabled with FullDebugMode turned on.
To run the test
- Enable / Disable the "Destroy Viewer" Checkbox to highlight different errors
- Enable / Disable the "Clear Viewer" Checkbox to influence the type of failure
- Click the Test button
- Let the test perform at least a couple of iterations
- Click the Abort button
- Exit the application
The following points highlight some of the issues encountered in our testing.
1. Memory leaks
- Even without an application error, memory leaks are surfaced.
- When closing the application, it may take a long time for it to actually close.
It eventually does, but FastMM will display many memory leak instances.
2. Memory Errors
- FastMM has detected an error during a free block scan operation.
- FastMM detected that a block has been modified after being freed.
- FastMM has detected an attempt to call a virtual method on a freed object.
3. Exceptions raised
- Invalid class typecast
The attached test application has certainly assisted me in discovering thread-related and race-condition-related issues.
However the supplied code only scratches the surface so I believe more intensive and targeted tests may need to be developed.
Please could you also tackle the thread related memory leak problems as our production environment encounters many "out of memory" related issues tied to the PDFViewer.
Kind Regards,
John Vander Reest
[Attachment removed by DevExpress]
Hello,
Thank you for sharing your research results. It may take us some time to examine them.
Hello,
We have some progress with this scenario. Would you please help us test our solution?
procedure TdxPDFBackgroundService.CancelAllAndWait; var AHandle: THandle; ACount: Integer; AList: TList; begin CancelTasks(False); ACount := 1; while ACount > 0 do begin AList := FTasks.LockList; ACount := AList.Count; AHandle := 0; if ACount > 0 then try if ACount > 0 then AHandle := THandle(AList[0]) finally FTasks.UnlockList; end else Break; Cancel(AHandle, True); end; end; //............................. procedure TdxPDFDocumentViewerCustomRenderer.OnTimerHandler(Sender: TObject); begin if not Viewer.IsDestroying then RenderNextPage; end;
initialization dxFontFamilyInfos; //This line finalization FreeAndNil(dxgFontFamilyInfos);
As an alternative solution you can disable the OptionsBehavior.RenderContentInBackground property (if acceptable). This property is protected, so it is necessary to use an accessor class:
type TdxPDFViewerOptionsBehaviorAccess = class(TdxPDFViewerOptionsBehavior); //............................ TdxPDFViewerOptionsBehaviorAccess(FViewer.OptionsBehavior).RenderContentInBackground := False;
Hi Paulo,
Sorry for the delay, I was not able to do any thorough testing until now.
I have implemented all your suggestions and incorporated them in my stress-test application.
Sadly I was NOT able to see any great improvement.
Perhaps I made a mistake so I have included the revised stress-test application including source code, demo PDFs and error logs.
Please review the Log file containing Delphi XE3 error callstacks resulting from exceptions raised during testing, using different options
"Simple\Errors\Errorlog_20200121.txt"
Also included is the FastMM4 log file from the session that generated the above error log file
"Simple\Errors\Simple_MemoryManager_EventLog_20200121.txt"
Kind Regards,
John Vander Reest
Hi Paulo,
This is the attachment that seems to have failed to upload for the previous comment.
Kind Regards,
John Vander Reest
[Attachment removed by DevExpress]
Thanks. We will try to find another solution for your new project. It may take some time.
Hello,
We have found a solution that should help you overcome this problem. Would you please help us test it?
Attached is an archive containing new versions of our source files. Please install the latest version of our controls (19.2.5), replace default files with attached units, and run our installer in Recompile mode. I hope this patch will resolve the issue.
Hi Paulo,
I'm happy to report the tests in the "PDFViewer Stess Test" application, passed with flying colors.
I also found no memory leaks when running the tests.
I extensively tested with no options, as well as "Destroy Viewer" = True, and "Clear Viewer" = True
Everything worked beautifully.
Very happy indeed.
I have added some additional user simulation tests.
These include "Change Page", "Zoom Page", and "Rotate Page"
These tests, once successfully having loaded the document, attempt to emulate the user
…, prior to changing pre-maturely to a new document.
These tests sadly did not fair as well.
I have included the new source as well as the test results for each of the above tests.
Also please find the memory leak reports, for each test, in the Errors folder.
"Simple\Errors\Change_Page - Simple_MemoryManager_EventLog_20200303.txt"
"Simple\Errors\Change_Zoom - Simple_MemoryManager_EventLog_20200303.txt"
"Simple\Errors\Rotate_Error - Simple_MemoryManager_EventLog_20200303.txt"
The new simulations were added to emulate typical user behaviour and to tease out potential threading problems.
From my understanding of the "DevExpress" code any requested action in the PDF Viewer is passed on to the "dxPDFCommandInterpreter.TdxPDFCustomCommandInterpreter" to execute the request in a separate thread.
I would therefore assume that it doesn't matter what type of request is made (load document, close document, goto page, zoom, rotate, export, select text, save images, etc.)
The action should always be protected (serialized) to avoid conflicts or threading issues.
Thank you for the already discernible invested effort.
What has been accomplishment is a vast improvement.
I believe the changes will eliminate some of the current issues we are having with the PDF Viewer.
I hope my newly found issues will highlight the general pattern and trust the remaining issues can be expeditiously resolved.
Kind Regards,
John Vander Reest
=== Test Results ===
DevExpress Vcl Subscription 19.2.5 + Supplied Fixes
Test Program Simple.exe (Source provided)
Edition: Windows 10 Pro, 32.0 GB (x64-bit)
Version: 1909
OS build: 18363.657
Delphi Version: XE3 - Update 2, Rio 10.3.3
=== 1. Test: Change Page is True ===
Error: Raised Exception class EInvalidCast with message 'Invalid class typecast'
Debugger stopped @ line 3938 of dxPDFCore.pas
function TdxPDFInteractiveFormField.GetFontInfo(AState: TdxPDFDocumentState): TdxPDFFontInfo; begin Result.FontSize := TextState.FontSize; Result.FontData := AState.SearchFontData(TextState.FontCommand) as TdxPDFEditableFontData; <<=== Debugger Stopped Here! === end;
Call Stack - Thread 08372
=== 2. Test: Zoom Page is True ===
Error: Raised Exception class EInvalidCast with message 'Invalid class typecast'
Debugger stopped @ line 3938 of dxPDFCore.pas
function TdxPDFInteractiveFormField.GetFontInfo(AState: TdxPDFDocumentState): TdxPDFFontInfo; begin Result.FontSize := TextState.FontSize; Result.FontData := AState.SearchFontData(TextState.FontCommand) as TdxPDFEditableFontData; <<=== Debugger Stopped Here! === end;
Call Stack - Thread 13744
Local Variables - Thread 13744
- Self + - FAlternateName = 'gCompleteMessage'#0#0 ... - FForm = $930092 - FFormCreated = True - FKids = $970096 - FKidsResolved = True - FParent = $2140201 - FResources = $335 - FTextJustification = tjLeftJustified - FTextState = nil - FValuesProvider = nil - FWidget = nil
=== 3. Test: Rotate Page is True ===
Error: Raised Exception class EAccessViolation with message 'Access Violation at address xxxxxxxx'
Debugger stopped @ line 1631 of System.Generics.Collections.pas
function TDictionary<TKey,TValue>.GetBucketIndex(const Key: TKey; HashCode: Integer): Integer; . . . while True do begin hc := FItems[Result].HashCode; <<=== Debugger Stopped Here! ===
Call Stack - Thread 15044
Local Variables - Thread 15044
- Self + - FItems [0] - HashCode = 661139492 - Key = 409959543 - Value = $7F2826E0 [1] = (-1, 0, nil) [2] = (-1, 0, nil) [3] = (-1, 0, nil) - FCount = 1 - GrowThreashhold = 3 - Key = 409959543 - HashCode = 661139492 - Result = 661139492 - start = 661139492 - hc == 4503230
end.
[Attachment removed by DevExpress]
Thank you for sharing your test results. To my regret, we were not able to reproduce this new problem. However, we examined your log files and modified our source code accordingly. Would you please check if adding the attached file to our previous patch can help you overcome it?