Clientdataset Append/Post fails intermittently - delphi

I have an external message coming in every second.
The message payload is saved in a ClientDataSet and displayed in a dbGrid.
No data base is involved. RAM storage only.
This works fine,
BUT,
I have intermittent problems when the dataset is empty and populated the first time.
The code is as follows:
procedure TCtlCfg_DM_WarningsFaults_frm.DecodeRxFrame(Protocol: TProtocolSelection;
// PROVA UTAN VAR VAR Frame : CAN_Driver.TCAN_Frame);
Frame : CAN_Driver.TCAN_Frame);
var
OldRecNo : integer;
// OldIxname : string;
// bMark : TBookMark;
WasFiltered : Boolean;
IdBitFields : TCanId_IdBitFields;
Msg : TCan_Msg;
MsgType : integer;
GlobalNode : TCanId_GlobalNode;
LocalNode : TCanId_LocalNode;
SubNode : TCanId_SubNode;
EntryType : integer;
SubSystemType : integer;
SubSystemDeviceId : integer;
IsActive : Boolean;
IsAcked : Boolean;
begin
with cdsWarningsFaults do
begin
if not Active then Exit;
Msg := Frame.Msg;
IdBitFields := DecodeCanId(Protocol, Frame.ID);
if IdBitFields.SubNode <> cSubNode_Self then Exit; // Ignore non controller/slave messages
if IdBitFields.AddressMode <> cCanId_AddrMode_CA then Exit;
MsgType := IdBitFields.MessageType;
if MsgType <> cMsg_CTL_CA_Broadcast_WarningAndFaultList then Exit;
if Frame.MsgLength < 5 then Exit;
GlobalNode := IdBitFields.GlobalNode;
LocalNode := IdBitFields.LocalNode;
SubNode := IdBitFields.SubNode;
// Silent exit if wrong node
if GlobalNode <> fNodeFilter.GlobalNode then Exit;
if LocalNode <> fNodeFilter.LocalNode then Exit;
if SubNode <> fNodeFilter.SubNode then Exit;
EntryType := Msg[1];
SubSystemType := Msg[2];
IsActive := (Msg[3] = 1);
SubSystemDeviceId := Msg[4];
IsAcked := (Msg[8] = 1);
DisableControls; // 2007-12-03/AJ Flytta inte scrollbars under uppdatering
OldRecNo := RecNo;
// OldIxName := IndexName; // Save current index
// IndexName := IndexDefs.Items[0].Name;
WasFiltered := Filtered; // Save filter status
Filtered := False;
try
try
if Findkey([GlobalNode, LocalNode, SubNode, EntryType, SubSystemType, SubSystemDeviceId]) then
begin // Update record
Edit;
FieldByName('fIsActive').AsBoolean := IsActive;
FieldByName('fIsAcked').AsBoolean := IsAcked;
FieldByName('fTimeout').AsDateTime := GetDatabaseTimeoutAt;
Post;
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Edit. N=' + IntToStr(GlobalNode) + ' ' +
IntToStr(LocalNode) + ' ' +
IntToStr(SubNode) +
' RecCnt=' + IntToStr(RecordCount) + ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
end
else
begin // Create new record
Append;
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Append. N=' + IntToStr(GlobalNode) + ' ' +
IntToStr(LocalNode) + ' ' +
IntToStr(SubNode) +
' RecCnt=' + IntToStr(RecordCount) + ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
try
FieldByName('fGlobalNode').AsInteger := GlobalNode;
FieldByName('fLocalNode').AsInteger := LocalNode;
FieldByName('fSubNode').AsInteger := SubNode;
FieldByName('fEntryType').AsInteger := EntryType;
FieldByName('fSubSystemType').AsInteger := SubSystemType;
FieldByName('fSubSystemDeviceId').AsInteger := SubSystemDeviceId;
FieldByName('fIsActive').AsBoolean := IsActive;
FieldByName('fIsAcked').AsBoolean := IsAcked;
FieldByName('fTimeout').AsDateTime := GetDatabaseTimeoutAt;
finally
try
Post; // VArför biter inte denna post så att det blir edit nästa gång
except
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Exception efter Post.', True);
end;
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Post. N=' + IntToStr(GlobalNode) + ' ' +
IntToStr(LocalNode) + ' ' +
IntToStr(SubNode) +
' RecCnt=' + IntToStr(RecordCount) + ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
end;
end;
except
on E: Exception do
begin
MainForm.AddToActivityLog('Post exception message: [' + E.Message + ']', False);
MainForm.AddToActivityLog('Post exception class: [' + E.ClassName + ']', False);
MainForm.AddToActivityLog('Post exception Error code: [' + IntToStr(EDBCLIENT (E).ErrorCode) + ']', False);
MainForm.AddToActivityLog('Post exception ReadOnly is: [' + BoolToStr(ReadOnly) + ']', False);
MainForm.AddToActivityLog('Post exception CanModify is: [' + BoolToStr(CanModify) + ']', False);
MainForm.AddToActivityLog('DecodeRxFrame: Exception inside FindKey block', False);
Cancel;
end;
end;
finally
// IndexName := OldIxName; // Restore previous index
Filtered := WasFiltered; // Restore filter state
if (OldRecNo >= 1) and (OldRecNo <= RecordCount) then RecNo := OldRecNo;
EnableControls;
end;
end;
//MainForm.AddToActivityLog('DecodeRxFrame: Exit ur proceduren', False);
end;
The problem is when the record does not already exist,
and I need to Append a new record.
It often works fine, but many times it seems the POST does not work,
and the append is repeated a few or many times when new data comes in.
Suddenly the append works, and subbsequent updates are done using edit,
and as far as I can tell, after that it then works forever.
The issue is intermittent and the number of tries needed to succeed vary.
It feels like a timing issue, but I cannot figure it out.
Any ideas greatly appreciated.
Thanks,
Anders J

As mentioned in my comment a lot can be figured out about how the code flows using an extract of the logs. (Also as a side-note, sometimes you need to be careful of the reliability of your logging system. Your logging is at least partially home-brew, so I have no idea what it means when you arbitrarily pass True/False values to the AddToActivityLog method.)
However, I am still able to offer some guidance to help you identify your problem. I also have some general comments to help you improve your code.
You're not shy to use logging to narrow down your problem: this is a good thing!
However you technique could use a little improvement. You're trying to determine what's going wrong around the Post method. Given such a clear goal, your logging seems surprisingly haphazard.
You should do the following:
//Log the line before Post is called. It confirms when it is called.
//Log important state information to check you're ready to post "correctly"
//In this it's especially useful to know the Sate of the dataset (dsEdit/dsInsert).
Post;
//Log the line after Post to confirm it completed.
//Note that "completed" and "succeeded" aren't always the same, so...
//Again, you want to log important state information (in this case dsBrowse).
If you had this this logging, you might (for example) be able to tell us that:
Before calling Post dataset is in dsInsert state.
And (assuming no exceptions as you say): after calling Post the dataset is still in dsInsert state.
NOTE: If it were in dsBrowse but Post still considered "unsuccessful", you'd be told to log details of the record before and after Post.
So now: Post "completing" without the record being "posted" would give a few more things to look at:
What events are hooked to the data set? Especially consider events used for validation.
Since you're working with TClientDataSet there's an important event you'll want to take a look at OnPostError. DBClient uses this callback mechanism to notify the client of errors while posting.
If you log OnPostError I'm sure you'll get a better idea of the problem.
Finally I mentioned you have a lot of other problems with your code; especially the error handling.
Don't use with until you know how to use it correctly. When you know how to use it correctly, you'll also know there's never a good reason to use it. As it stands, your code is effectively 2 characters short of a subtle bug that could have been a nightmare to even realise it even existed; but would be a complete non-issue without with. (You declared and used a property called IsActive differing by only 2 characters from TDataSet's Active. I'm sure you didn't realise this; and their difference is but an accident. However, if they had been the same, with would very quietly use the wrong one.)
You need to write smaller methods - MUCH smaller! Blobs of code like you have are a nightmare to debug and are excellent at hiding bugs.
Your exception handling is fundamentally wrong:
Your comment about logging and exception handling suggests that you've been simply adding what you can out of desperation. I think it pays to understand what's going on to keep your logging useful and avoid the clutter. Let's take a close look at the most problematic portion.
/_ try
/_ FieldByName('fGlobalNode').AsInteger := GlobalNode;
/_E FieldByName('fLocalNode').AsInteger := LocalNode;
| FieldByName('fSubNode').AsInteger := SubNode;
| FieldByName('fEntryType').AsInteger := EntryType;
| FieldByName('fSubSystemType').AsInteger := SubSystemType;
| FieldByName('fSubSystemDeviceId').AsInteger := SubSystemDeviceId;
| FieldByName('fIsActive').AsBoolean := IsActive;
| FieldByName('fIsAcked').AsBoolean := IsAcked;
| FieldByName('fTimeout').AsDateTime := GetDatabaseTimeoutAt;
|_ finally
/_ try
/_ Post;
/ except
| MainForm.AddToActivityLog(..., True);
| end;
|_ MainForm.AddToActivityLog(..., False);
/ end;
|
...
So, in the above code:
If no exceptions happen, you'd simply step from one line to the next.
But as soon as an exception happens, you jump to the next finally/except block.
The first problem is: Why would you try to force a Post if you haven't finished setting your field values. It's a recipe for headaches when you end up with records that have only a fraction of the data they should - unless you're lucky and Post fails because critical data is missing.
When finally finishes during an exception, code immediately jumps to the next finally/except in the call-stack.
Except is slightly different, it only gets called if something did go wrong. (Whereas finally guarantees it will be called with/without an exception.
TIPS: (good for 99% of exception handling cases.
Only use try finally for cleanup that must happen in both success and error cases.
Nest your try finally blocks: The pattern is <Get Resource>try .... finally<Cleanup>end (The only place to do another resource protection is inside the .....)
Avoid except in most cases.
The main exception to the previous rule is when cleanup is needed only in the case of an error. In which case: do the cleanup and re-raise the exception.
Other than that: only implement an except block without re-raising if you can fully resolve an error condition. (Meaning the lines of code immediately after the exception swallower truly don't care about the previous exception.

Related

Deleting Records in a dbGrid Delphi 2010

I am looking for a solution to a rather serious problem I'm facing;
I want to delete a record in a dbGrid but when I click on my coded button and confirm 'Delete', I find no immediate results. In order to find the result of the deleted record, I have to close the program and re-run it. Only then do I see that the record is deleted.
My coding looks basically as follows
procedure TfrmPunte.btnDeleteClick(Sender: TObject);
var
sName, sLeerderNo : string;
begin
with dmPunte do
begin
sLeerderNo := tblLeerder['LeerderNr'];
sName := tblLeerder['NaamVan'];
if MessageDlg('Is jy seker dat jy ' + sName + ' met Leerder Nommer ' + sLeerderNo + ' wil wis van die rekords? Neem kennis dat al die rekords van sy/haar aktiwiteite ook uitgevee sal word!', mtWarning, [mbOk, mbCancel],0) = mrOK then
tblDeelname.Open;
tblDeelname.First;
while NOT tblDeelname.Eof do
begin
if tblDeelname['LeerderNr'] = sLeerderNo then
tblDeelname.Delete;
tblDeelname.Next;
end;
tblLeerder.Delete;
tblLeerder.Active := False;
tblLeerder.Active := True;
end;
end;
I am using an ADO table connected to ADO connection connected to an ACCESS database. I do appologize, some variable names are in Afrikaans.
What should I do?
Table.refresh does not work in every environment. The safe way is
Table.Active := False;
Table.Active := True;
Be aware of a problem that the actual dataset is lost. You can store it and get back locate.

How to handle TRegEx named capture groups that might be empty?

I have a regular expression with named capture groups, where the last group is optional. I can't figure out how to iterate the groups and properly deal with the optional group when it's empty; I get an EListOutOfBounds exception.
The regular expression is parsing a file generated by an external system that we receive by email which contains information about checks that have been issued to vendors. The file is pipe-delimited; a sample is in the code below.
program Project1;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.RegularExpressions, System.RegularExpressionsCore;
{
File format (pipe-delimited):
Check #|Batch|CheckDate|System|Vendor#|VendorName|CheckAmount|Cancelled (if voided - optional)
}
const
CheckFile = '201|3001|12/01/2015|1|001|JOHN SMITH|123.45|'#13 +
'202|3001|12/01/2015|1|002|FRED JONES|234.56|'#13 +
'103|2099|11/15/2015|2|001|JOHN SMITH|97.95|C'#13 ;
var
RegEx: TRegEx;
MatchResult: TMatch;
begin
try
RegEx := TRegEx.Create(
'^(?<Check>\d+)\|'#10 +
' (?<Batch>\d{3,4})\|'#10 +
' (?<ChkDate>\d{2}\/\d{2}\/\d{4})\|'#10 +
' (?<System>[1-3])\|'#10 +
' (?<PayID>[0-9X]+)\|'#10 +
' (?<Payee>[^|]+)\|'#10 +
' (?<Amount>\d+\.\d+)\|'#10 +
'(?<Cancelled>C)?$',
[roIgnorePatternSpace, roMultiLine]);
MatchResult := RegEx.Match(CheckFile);
while MatchResult.Success do
begin
WriteLn('Check: ', MatchResult.Groups['Check'].Value);
WriteLn('Dated: ', MatchResult.Groups['ChkDate'].Value);
WriteLn('Amount: ', MatchResult.Groups['Amount'].Value);
WriteLn('Payee: ', MatchResult.Groups['Payee'].Value);
// Problem is here, where Cancelled is optional and doesn't
// exist (first two lines of sample CheckFile.)
// Raises ERegularExpressionError
// with message 'Index out of bounds (8)' exception.
WriteLn('Cancelled: ', MatchResult.Groups['Cancelled'].Value);
WriteLn('');
MatchResult := MatchResult.NextMatch;
end;
ReadLn;
except
// Regular expression syntax error.
on E: ERegularExpressionError do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
I've tried checking to see if the MatchResult.Groups['Cancelled'].Index is less than MatchResult.Groups.Count, tried checking the MatchResult.Groups['Cancelled'].Length > 0, and checking to see if MatchResult.Groups['Cancelled'].Value <> '' with no success.
How do I correctly deal with the optional capture group Cancelled when there is no match for that group?
If the requested named group does not exist in the result, an ERegularExpressionError exception is raised. This is by design (though the wording of the exception message is misleading). If you move your ReadLn() after your try/except block, you would see the exception message in your console window before your process exits. Your code is not waiting for user input when an exception is raised.
Since your other groups are not optional, you can simply test if MatchResult.Groups.Count is large enough to hold the Cancelled group (the string that was tested is in the group at index 0, so it is included in the Count):
if MatchResult.Groups.Count > 8 then
WriteLn('Cancelled: ', Write(MatchResult.Groups['Cancelled'].Value)
else
WriteLn('Cancelled: ');
Or:
Write('Cancelled: ');
if MatchResult.Groups.Count > 8 then
Write(MatchResult.Groups['Cancelled'].Value);
WriteLn('');
BTW, your loop is also missing a call to NextMatch(), so your code is getting stuck in an endless loop.
while MatchResult.Success do
begin
...
MatchResult := MatchResult.NextMatch; // <-- add this
end;
You could also avoid using an optional group and make the cancelled-group obligatory, including either C or nothing. Just change the last line of the regex to
'(?<Cancelled>C|)$'
For your test application, this wouldn't change the output. If you need to work further with cancelled you can simply check if it contains C or an empty string.
if MatchResult.Groups['Cancelled'].Value = 'C' then
DoSomething;

User friendly exception error delphi

I have this method where i execute a sql statement and catch a error in a try except statement
AdoQuery := TAdoQuery.Create(self);
AdoQuery.connection := AdoConnection;
AdoQuery.SQL.Add(sqlStr);
AdoQuery.Prepared := true;
try
begin
AdoQuery.ExecSql;
AdoQuery.Active := false;
end;
except on e:eAdoError do
ShowMessage('Error while creating the table: ' + e.Message);
end;
I can catch the error like this and show it to the user but it's showing some useless info for the user. I Would like to show only the %msg part of the error, take a look at the pic:
I tought e.MEssage allow me to get only the %msg part but it give me the whole thing hardly understoodable by a random user. How do i get only the usefull info in this case
Table reftabtest.rPCE already exists
Thank you.
You can use the Errors property of the TADOConnection object, what you want is the Description member of the Error object.
In your case:
function ParseOBDCError(ErrorDescription : String) : String;
var
Ps : Integer;
Pattern : String;
begin
Pattern := '%msg:';
Ps := Pos(Pattern, ErrorDescription);
if Ps > 0 then
begin
Result := Copy(ErrorDescription, Ps+Length(Pattern)+1);
// if you want, you can clean out other parts like < and >
Result := StringReplace(Result, '<', , '', [rfReplaceAll]);
Result := StringReplace(Result, '>', , '', [rfReplaceAll]);
Result := Trim(Result);
end
else
Result := ErrorDescription;
end;
...
AdoQuery := TAdoQuery.Create(self);
AdoQuery.connection := AdoConnection;
AdoQuery.SQL.Add(sqlStr);
AdoQuery.Prepared := true;
try
AdoQuery.ExecSql;
AdoQuery.Active := false;
except on e : Exception do
begin
if AdoConnection.Errors.Count > 0 then
ShowMessageFmt('Error while creating the table: %s',
[ParseOBDCError(AdoConnection.Errors[0].Description)])
else
ShowMessageFmt('something went wrong here: %s', [e.Message]);
end;
end;
That message dialog wasn't shown by your ShowMessage() code.
First, the icon is wrong - that's not the ShowMessage icon.
Second, the text you added ('Error while creating the table: ') to the message is missing.
This means your exception swallower is not catching the exception, because it's not of the EADOError class. So what's happening is the application's default exception handler is showing the exception.
Before I explain how to fix it, I need to point out that your exception swallower is wrong (your's should not be misnamed an exception handler).
Because you're swallowing the exception: If another method calls yours it will incorrectly think you method succeeded, and possibly do something it shouldn't. You should never write code that makes an assumption that there isn't a significant call stack leading into your method.
Swallowing to show a message to the user doesn't help, because it hides the error from the rest of the program. Especially since, as you can see: there is already code in one place that tells the user about the error without hiding it from the rest of the program. (The problem you have is that you want a friendlier message.)
To fix it:
First find out what the actual exception class is, so you're able to catch the correct error.
Now you have a number of options, but the simplest is as follows:
First log the exception, preferably with call stack. You don't want to be stuck in the situation where the user gets a friendly message but you as developer lose critical information if you need to do some debugging.
To get the call stack you can consider third-party tools like Mad Except, Exceptional Magic, JCLDebug to name a few.
Now show the message to the user.
Finally call Abort. This raises an EAbort exception which by convention is a "silent exception". It tells the rest of the program that there was an error (so it doesn't do things assuming everything is fine). But by convention, any further exception handlers should not show another message to the user. This includes the default handler's dialog in your question.
If the default handler is incorrectly showing EAbort messages, then it should be fixed.

EArgumentOutOfRangeException though nothing has changed the TList

I have another question about TList in Delphi...
I'm getting an EArgumentOutOfRangeException when accessing a TList, just the same way I've done before, after printing a different TList to console.
copy&pasted the original code below - did not change a single line
Writeln('c: '+inttostr(closed.Capacity)+' |l. 281');
for i := 0 to open.Capacity-1 do
begin
Writeln('open: ' + open[i].startpunkt + open[i].endpunkt +
IntToStr(open[i].kantenbewertung));
end;
Writeln('c: '+inttostr(closed.Capacity));
Writeln('closed.capacity: '+inttostr(closed.Capacity));
for i := 0 to closed.Capacity-1 do begin
Writeln('closed: ' + closed[i].startpunkt + closed[i].endpunkt +
IntToStr(closed[i].kantenbewertung));
end;
The Writeln('c: '+inttostr(closed.Capacity)+' |l. 281'); is clearly printed to console, the for i := 0 to open.Capacity-1 do begin-loop as well.
Console output is:
c: 2 |l. 281
open: AC3
open: BC4
open: CD6
- and then there is the error msg
Is supposed to be part of a implementation of Dijkstra's alg. if this helps.
Does anyone spot a mistake I haven't? Or is this some weird special case?
Capacity is not the same (and may be greater) than the actual Count.

Stream read error

I'm getting this error message under heavy load. Here is code abstract and message from my error log.
I tried everything I could think of. Any suggestion would be greatly appreciated.
Procedure tCacheInMemory.StreamValue(Name: String; IgnoreCase: Boolean; Var Stream: TStringStream);
Var
i: Integer;
Begin
i := 0;
Try
If Not active Then
exit;
arrayLock.BeginRead;
Try
i := Search(Name);
If i > -1 Then Begin
If fItems[i].value = Nil Then
exit;
fItems[i].value.Position := 0;
Stream.Position := 0;
Stream.CopyFrom(fItems[i].value, fItems[i].value.Size);
End;
Finally
arrayLock.EndRead;
End;
Except { ...execution jumps to here }
On E: Exception Do Begin
x.xLogError('LogErrorCacheInMemory.txt', 'StreamValue:' + E.Message + ' ItemsCount:' + IntToStr( High(fItems)) + 'Memory:' + IntToStr(x.GetMemoryInfoMemory) + endLn + 'StreamSize : ' + IntToStr(fItems[i].value.Size) + ' i=' + IntToStr(i) + 'Name: ' + Name);
Clear;
End
End;
End;
Log Entries:
3/10/2011 10:52:59 AM: StreamValue:Stream read error ItemsCount:7562 Memory:240816
StreamSize : 43 i=7506 Name: \\xxxxxxxx\WebRoot\\images\1x1.gif
3/10/2011 12:39:14 PM: StreamValue:Stream read error ItemsCount:10172 Memory:345808
StreamSize : 849 i=10108 Name: \\xxxxxxxx\WebRoot\\css\screen.add.css
3/10/2011 3:45:29 PM: StreamValue:Stream read error ItemsCount:11200 Memory:425464
StreamSize : 3743 i=11198 Name: \\xxxxxxxx\WebRoot\\JS\ArtWeb.js
P.S.
arrayLock: TMultiReadExclusiveWriteSynchronizer;
fItems: Array Of rCache;
Type
rCache = Record
Name: String;
value: TStringStream;
expired: TDateTime;
End;
And calling function:
Function tCacheInMemory.CacheCheck(cName: String; Out BlobStream: TStringStream): Boolean;
Begin
Result := False;
If Not IfUseCache Then
exit;
BlobStream.SetSize(0);
BlobStream.Size := 0;
StreamValue(trim(cName), True, BlobStream);
If BlobStream.Size > 0 Then
Result := True;
End;
`
You're not using correct locking. You're acquiring a read lock on the array of cache entries, but once you find the item you want, you modify it. First, you explicitly modify it by assigning its Position property, and then you implicitly modify it by reading from it, which modifies its Position property again. When other code attempts to read from that same cache item, you'll have interference. If the source stream's Position property changes between the time the destination stream calculates how many bytes are available and the time it actually requests to read those bytes, you'll get a stream-read error.
I have a couple pieces of advice related to this:
Don't use streams as a storage device in the first place. You're apparently holding the contents of files. You're not going to change those, so you don't need a data structure designed for making sequential changes. Instead, just store the data in simple arrays of bytes: TBytes. (Also, use of TStringStream in particular introduces confusion over whether those strings' encodings are important. A simple file cache shouldn't be concerned with string encodings at all. If you must use a stream, use a content-agnostic class like TMemoryStream.)
Don't quell an exception that you haven't actually handled. In this code, you're catching all exception types, logging some information, clearing the cache, and then proceeding as though everything is normal. But you haven't done anything to resolve the problem that triggered the exception, so everything is not normal. Since you're not really handling the exception, you need to make sure it propagates to the caller. Call raise after to call Clear. (And when you log the exception, make sure you log the exception's ClassName value as well as its message.)
It looks like something external is blocking your stream files.
You could try to use Process Monitor to see what blocks it.
Another thing you can try is to open the stream in read-deny-write mode (please show us how you open the stream).
Something like this:
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite) ;
Edit 1: Disregard the strike through part: you are using TStringStream.
I'll keep the answer just in case anyone ever gets this kind of error when using TFileStream.
Edit 2: Yuriy posted this interesting addendum, but I'm not sure it will work, as the BlobStream is not initialized, just like Robert Love suspected:
Function TCacheInMemory.CacheCheck(cName: String; Out BlobStream: TStringStream): Boolean;
Begin
Result := False;
Try
If Not IfUseCache Then
exit;
BlobStream.SetSize(0);
BlobStream.Size := 0;
StreamValue(trim(cName), True, BlobStream);
If BlobStream.Size > 0 Then
Result := True;
Except
On E: Exception Do
Begin
x.xLogError('LogErrorCacheInMemory.txt', 'CheckCacheOutStream:' + E.Message + ' ItemsCount:' + IntToStr( High(fItems)) + 'Memory:' + IntToStr(x.GetMemoryInfoMemory));
End;
End;
End;
--jeroen

Resources