In our application we want to execute scripts on a list of objects (several hundred to thousand items). For each object one script will be executed, the scripts are similiar but differ from each other. We use always the same LMDScriptControl-instance and process the scripts sequentially.
Our script-code looks fine and works on single execution. But if we process the complete list some script-executions result in an Access Violation. On average, 5% of the cases are erroneous. If we try to rerun the erroneous scripts, they complete successfully.
Until now we have not been able to create a simple example application, which makes the error reproducable. However, with the help of the LMD source code we could probably already identify the source of the problem (and possibly even find an aproach for the solution).
Our Script-Code looks like this:
var
myObject;
begin
myObject := myController.CreateTestInstance('TTestSubClass');
try
//Do something
myObject.SomeFunction();
finally
myObject.Free();
end;
end;
This is a simplified Code-example of how we run the scripts in the delphi-application
procedure TForm1.bExecuteClick(Sender: TObject);
var
controllerObject : TControllerClass;
begin
try
controllerObject := nil;
var amountStr : string := eAmount.Text;
for var i := 1 to amountStr.ToInteger() do
begin
controllerObject := TControllerClass.Create();
LMDScriptControl1.Language := slPascal;
LMDScriptControl1.Source.Clear();
LMDScriptControl1.Source.Text := eScript.Lines.Text;
LMDScriptControl1.Prepare();
LMDScriptControl1.AddUnits([UTestClasses_sw], true);
LMDScriptControl1.AddObject('myController',
TControllerClass_sw.ToVar(controllerObject));
LMDScriptControl1.Open();
LMDScriptControl1.Close(true);
FreeAndNil(controllerObject);
end;
except
on e : Exception do
begin
//not active but still holding Objects, if Open() fails
LMDScriptControl1.ClearObjects();
FreeAndNil(controllerObject);
lStatus.Caption := 'Error.';
MessageDlg(e.Message, mtError, [mbOk], 0);
end;
end;
end;
We pass an object via AddObject to the script ("myController"). Each script will get an individual instance which will also be freed after the execution.
In the script we create a new Object with the given object. We do not call the contstructor directly, instead we have a factory method. The method creates an Object of a class, which has a LMD-ScriptWrapper (TTestClass). But the method may create an instance of a subclass (TTestSubClass), which has no individual ScriptWrapper-Class.The call of CreateTestInstance throws the exception.
Sorry for the long introduction, but we wanted to provide as much background information as possible. In the following I describe the specific problem and our analysis.
The wrapped code of CreateTestInstance runs normally, the new instance is created. The call ToVar / ConvToVar in the wrapper results in the access violation:
function TControllerClass_CreateTestInstance_si(var AObj;
const AArgs: TLMDDispArgs; AArgsSize: Integer; IsGet: Boolean): OleVariant;
begin
Result := TTestClass_sw.ToVar(TControllerClass(AObj).CreateTestInstance(
string(AArgs[0])));
end;
We have dived in a little bit deeper in the source code and assume to have found the problematic code.
class function TLMDClassWrapper.ConvToVar(AObject: TObject): OleVariant;
var
wrrcls: TLMDClassWrapperClass;
wrapper: TLMDClassWrapper;
begin
if AObject = nil then
begin
Result := LMDVarNil;
Exit;
end;
wrapper := TLMDClassWrapper(WrapperByObject.Find(AObject));
if wrapper = nil then
begin
wrrcls := TWrapperClassItem.Get(AObject.ClassType);
wrapper := wrrcls.Create(AObject);
end;
Result := WrapperToVariant(wrapper);
end;
ConvToVar first tries to find the object in its hashtable. If the object is not known, a new wrapper-object will be created. Finally the wrapper will be returned as variant.
Since we have just created the instance (AObject), this cannot be in the hashtable yet. But in some cases the Find will return a result. But the returned object does not appear to be valid and seems corrupted, the WrapperToVariant call will then raise the exception.
We assume, that there is some problem with the memory management. It seems that the HashTable holds objects which were already destroyed, and under certain circumstances returns that object-pointer by mistake.
As mentioned at the beginning, we tried to reproduce this in a simple sample application, with the code above, but we were unable to do so. The sample application did not produce any access violations.
We enabled some logging in the lmd components and could observe an interesting behavior in our application after the access violation. After execution every minute were several calls to the HashTable.Find method made. We assume that there ist some backgroundthread which tries to clean up the HashTable. Maybe in some cases the data is so much corrupted that it cannot complete the tidy-up-process.
We made a simple adjustment in the source code and could actually run thousands of scripts without error. We added a global unit-level function
to LMDSctWrappers and call this after every script-execution (After
LMDScriptControl1.Close(true)). We assume that this cause some side-effects but it may help you to identify the cause of the problem and find a solution.
procedure HotfixClear();
begin
WrapperByObject.FMap.Clear();
end;
Comments
Windows Server 2016 Datacenter Version 1607
Embarcadero® Delphi 10.4 Version 27.0.40680.4203
Windows Server 2019 Version 1809
are there any new findings on this issue yet? For us, this issue is relatively critical, as our customers want to run scripts in large quantities, but due to the error there is a high scrap rate. A temporary workaround would also help us a lot at first.
As last mentioned we are ready to work together to find the problem and the solution. For example, we could do a debugging session together where we provoke the error in our environment.
If this would help you, or you have another idea on how we can help, please feel free to let us know.