Welcome to the SRP Forum! Please refer to the SRP Forum FAQ post if you have any questions regarding how the forum works.
Observation of Write/WriteV failure, but no error raised by OI
in OpenInsight
I have code that attempts to update a field in a table record, with error-handling in the Else part of the WriteV statement, but the program does not enter the Else section despite that the record is not updated with the new values. This happens during concurrent writes. I expected to see an FS104 or FS133 or similar error. No such error is raised; instead the program executes the Then part of the WriteV statement where I verify manually that the WriteV has failed. I can and will wrap any such code in a Lock/Unlock pair, but does OI's Lock statement do better with failures than Write/WriteV apparently does? Or does my code need something else in it to guarantee that the Else part of WriteV will be entered after a write-failure?
This is the relevant part of my code, with some extra error-handling omitted:
Any help is appreciated.
This is the relevant part of my code, with some extra error-handling omitted:
service DeleteValue(value, table, key, field)
failedCheck = 0
table.fldUpd = ""
open table to table.file then
if RowExists(table, key) then
readV table.fld from table.file, key, field then
locate value in table.fld setting pos then
table.fld = Delete(table.fld, 1, pos, 0)
Flush
GarbageCollect
writeV table.fld to table.file, key, field then
//
// Check that writeV wrote.
//
Flush
GarbageCollect
readV table.fldUpd from table.file, key, field then
debug
locate value in table.fldUpd setting pos then
failedCheck = 1; * Previous write failed.
action = "Write"
gosub ErrorData
end
end
end else
action = "Write"; * This section is not entered.
gosub ErrorData
end
end
end else
action = "Read"
gosub ErrorData
end
end
end else
action = "Open"
gosub ErrorData
end
end service /* DeleteValue */
Any help is appreciated.
Comments
I mean, I can't see any reason why it wouldn't be, but was thinking just maybe, the writev is working but you're writing away a different value to what you're expecting to see after the read.
Don, Hi, how're you going? I added Flush and GarbageCollect as a last resort after a suggestion by a colleague. I find they have no effect on the WriteV. Flush alone seems to help with the subsequent ReadV. Without it, ReadV frequently shows table.fldUpd with 2 values as well as 3 values during many runs, this despite the fact that reopening the record afterward in the editor shows the same 3 values as before the concurrent access. Without Flush I think caching interferes with what ReadV actually reads. When I step through in debug mode, I never see the false read of two values; it is always 3 values in the debugger, confirmed by reopening the record afterward in the editor.
Record before concurrent access:
Field before deletion of value 012345:
Field before WriteV:
Field after ReadV (write-check):
Record reopened after concurrent access:
Incidentally, this is the ErrorData internal subroutine I used to collect error data:
ErrorData: stamp = TimeDate() access = table: "." :key: " <" :field: ">" if failedCheck then error = action: " failed despite no error raised by OI: " end error := "Status(): " : Status() : "; @FILE_ERROR: " if @FILE_ERROR<1> then error := "FS" : @FILE_ERROR<1> if @FILE_ERROR<2> then error := ", " : @FILE_ERROR<2> if @FILE_ERROR<3> then error := ", " : @FILE_ERROR<3> Response = 0 :@VM: stamp :@VM: action :@VM: access :@VM: error return
I just made your code an OI subroutine though.
So are you saying that on a write-failure your program enters the Else part of the WriteV, starting line 26 in the service above?
end else action = "Write"; * This section is not entered. gosub ErrorData end
Have you tested this using Read / Write rather than ReadV / WriteV? Also, what if you use Read / WriteV?
I have not used ReadV / WriteV extensively. However, I know they are called by RTP7 and RTP8 respectively and then they go through the READ.RECORD and WRITE.RECORD file system primitives the same way that the Read and Write statements do.
It would not surprise me if some kind of caching is involved. WriteV must be able to protect the other fields from being updated so maybe the original record is cached and then retrieved before the row is written using RTP8.
Now that I have a pointer to the primitives, I see from the prog. ref. manual that although READ.RECORD indicates the result of the Read in a STATUS argument, WRITE.RECORD does not:
Purpose
Argument: STATUS
In: unassigned
Out: true if record was read successfully
Purpose
Argument: STATUS
In: unassigned
Out: unchanged
Record before concurrent access:
Before deletion of value "012345":
Before Write:
After Read (write-check):
Record reopened after concurrent access:
ServerOnly=1
ServerName=vince_opto
TcpIpPort=777
I think a little explanation about the primitives is in order.
Nothing uses these primitives in the way I think you are framing the question. Think of the primitives as event handlers for database transactions.
The real question would be, what does the SRP Editor use to write/read/delete a record? The answer is the Write statements, Read statements, and Delete statements (respectively).
Each of these statements is the normal approach to initiate a standard transaction with a table. This triggers what is called the File System stack. The File System stack is comprised of zero to n number of Modifying File System (MFS) routines and then the Base File System (BFS) routine. There must always be the BFS, as this is the lowest level procedure that communicates directly with the server. The BFS is typically supplied by Revelation Software. RTP57 is the name of the BFS for the Linear Hash database.
An MFS is a hook that we can add to a table that intercepts the transaction before it gets to the BFS. Some MFS routines are provided by Revelation, such as SI.MFS, which will then react to a completed transaction and update a secondary table as needed. Developers can provide their own MFS routines to do whatever is required.
Regardless if this is an MFS or the BFS, all transactions get passed through the appropriate primitive. WRITE.RECORD is the primitive associated with any processes that writes the record to the table. This includes the Basic+ Write statement, the Write_Row routine, or anything else.
Getting back to the problem at hand, if I read your results correctly, WriteV is a bit of a red herring. Is that correct? That is, you are able to duplicate the problem using just Read and Write statements? If so, then that simplifies the issue but it also moves the goal post.
The reason I used WriteV was an assumption of efficiency: Write one field rather than the entire record. But now, if I understand what the WRITE.RECORD primitive does (it appears to write the whole record), using WriteV over Write perhaps makes no difference on that count.
Well, I have no other ideas to offer. This behavior is unexpected. Honestly, if it was a normal behavior this would have been shouted from the rooftops long before now.
I was remarking to a colleague only yesterday about how one can fail to see the forest for all the trees in the way, and here I've done it one more time. My apologies to everyone involved in this discussion for the time spent.
What is reassuring, now that I have the test right, is that for one minute the looping program on my machine reads and writes the record over 450,000 times, and my test program successfully updated the record for the several times I ran the test this morning.