Bug #10390
FIND FIRST buffer field change does not propagate into the next FIRST FIRST statements.
100%
Related issues
History
#2 Updated by Teodor Gorghe 12 months ago
The following test case has issues on FWD:
FWD fails this test on the second assertion because the FIND FIRST statement finds the same record from the FIND FIRST statement.
#3 Updated by Teodor Gorghe 11 months ago
I have analyzed the 4GL behavior more in depth. I have added a ON WRITE to check when the buffer gets written to the persistent database.
Code A works OK on FWD. The following sequence of message is:
Before second find Fired 0 After second find
Code B does not work on FWD (same test case from #10390-2). These two buffer are different, pointing to the same persistent database table. The first buffer has the value changed, but not released (committed to database), while the second buffer fetches the data from some sort of shared buffer from process. FWD create a new buffer and fetches the data from database.
Before call no After call Fired 0
#4 Updated by Teodor Gorghe 11 months ago
- Related to Bug #10083: Infinite loop when changing walking index fields added
#5 Updated by Teodor Gorghe 11 months ago
- Status changed from New to WIP
Created task branch 10390a.
#6 Updated by Teodor Gorghe 11 months ago
- % Done changed from 0 to 100
- Status changed from WIP to Review
- Assignee set to Teodor Gorghe
- reviewer Alexandru Lungu added
Committed revision 16101 on task branch 10390a:
- RandomAccessQuery.executeImpl allow dmo to be changed before exiting the loop.
This issue is very related to 10083, which fixed the infinite loop on a similar case.
I have updated the tests for testing the fixes for 10083:
ABL Code
#7 Updated by Teodor Gorghe 11 months ago
- % Done changed from 100 to 50
- Status changed from Review to WIP
While the initial test case was done, I have also encountered the following test case, which is very similar to the previous, but a little bit different in implementation:
@Test.
METHOD PUBLIC VOID TestFindQueryModifyIndexSecondField():
CREATE adaptivept9.
adaptivept9.k1 = 1.
adaptivept9.k2 = 2.
RELEASE adaptivept9.
DEFINE BUFFER buf1 FOR adaptivept9.
FIND FIRST adaptivept9 WHERE adaptivept9.k1 = 1 NO-ERROR.
Assert:IsTrue(AVAILABLE adaptivept9).
adaptivept9.k2 = 3.
FIND buf1 WHERE buf1.k1 = 1 AND buf1.k2 = 2 NO-ERROR. // This instruction only differs from the first test. FIRST keyword is missing, which makes the activeBundleKey to be UNIQUE.
Assert:IsFalse(AVAILABLE buf1).
FINALLY:
FOR EACH adaptivept9:
DELETE adaptivept9.
END.
END FINALLY.
END METHOD.
#8 Updated by Teodor Gorghe 11 months ago
- Status changed from WIP to Feedback
What happens for the FIND unique case, which is different from the FIND first case:
- The flow is mostly similar.
- FIND unique DirtyShareContext.getDirtyInfo returns null because there are some checks (:927, :930) about unique queryType. Since the persistent database returns the old records, I don't get it and I have didn't found a test case for this. Removing lines :927-935 might fix the issue.
- FIND first does not meet the if condition from :927 and :930, which makes it to return non-null DirtyInfo. With rev 16101, the test passes for FIND FIRST and some customer application tests are also fixed.
#9 Updated by Alexandru Lungu 11 months ago
Teodor, there is a particularity of FIND unique in the sense that if there are ambiguous results, calling AMBIGUOUS adaptivept9 should return true. FWD should acknowledge the fact that there were multiple records for the same FIND unique and eventually execute some logic to match AMBIGUOUS. This doesn't explain #10390-8 per-se.
Quick question, as getDirtyInfo returns null, it means that !earlyInserts.containsKey(new RecordIdentifier<>(entity, foundID))) holds? In other words, the entity is not inside earlyInserts?
#10 Updated by Teodor Gorghe 11 months ago
Alexandru Lungu wrote:
Teodor, there is a particularity of FIND unique in the sense that if there are ambiguous results, calling
AMBIGUOUS adaptivept9should return true. FWD should acknowledge the fact that there were multiple records for the same FIND unique and eventually execute some logic to matchAMBIGUOUS. This doesn't explain #10390-8 per-se.Quick question, as
getDirtyInforeturns null, it means that!earlyInserts.containsKey(new RecordIdentifier<>(entity, foundID)))holds? In other words, the entity is not insideearlyInserts?
earlyInserts is actually null.
#11 Updated by Alexandru Lungu 11 months ago
Ah, I see. The DMO is updated and validated. It is inserted in the RecordNursery for future use. It is required at some point, so it is made visible through the record nursery. As it is validated already, it won't be part of earlyInserts.
QueryConstants.UNIQUE conditional is not reasonable. If removing it fixes your issue, I am OK with the approach. ATM, DirtyShareContext is so verbose and full of caveats in order to leverage the cross-session dirty-share support (that is currently active only for the ChUI regression testing). The protocol is:
- if the piece of code can be changed and ChUI regression testing still passes, it is OK!
- if the piece of code is critical for cross-session dirty-share, mind wrapping it in
DirtyShareSupport.isEnabledCrossSession().
In the near-future, DirtyShareContext will be completely replaced by #8388. Any effort in this area should be done just enough to avoid regressions, but still fix issues.
#12 Updated by Teodor Gorghe 11 months ago
- Related to Bug #10455: Persistent table CAN-FIND FIRST issue added
#13 Updated by Teodor Gorghe 11 months ago
- Status changed from Feedback to WIP
- % Done changed from 50 to 100
- reviewer Constantin Asofiei added
- reviewer deleted (
Alexandru Lungu)
Committed revision 16102 on task branch 10390a:
- Allow FIND UNIQUE to retrieve dmo from dirty database when dmo state is CHANGED.
#14 Updated by Teodor Gorghe 11 months ago
- Status changed from WIP to Review
#15 Updated by Teodor Gorghe 11 months ago
Alexandru Lungu wrote:
Ah, I see. The DMO is updated and validated. It is inserted in the
I think theRecordNurseryfor future use. It is required at some point, so it is made visible through the record nursery. As it is validated already, it won't be part ofearlyInserts.QueryConstants.UNIQUEconditional is not reasonable. If removing it fixes your issue, I am OK with the approach. ATM,DirtyShareContextis so verbose and full of caveats in order to leverage the cross-session dirty-share support (that is currently active only for the ChUI regression testing). The protocol is:
- if the piece of code can be changed and ChUI regression testing still passes, it is OK!
- if the piece of code is critical for cross-session dirty-share, mind wrapping it in
DirtyShareSupport.isEnabledCrossSession().In the near-future,
DirtyShareContextwill be completely replaced by #8388. Any effort in this area should be done just enough to avoid regressions, but still fix issues.
ChUI regression testing passed with the current changes.
#16 Updated by Constantin Asofiei 11 months ago
- reviewer Alexandru Lungu added
Alex, please review this.
#17 Updated by Alexandru Lungu 11 months ago
- Status changed from Review to WIP
- % Done changed from 100 to 80
I am not sure the changes in 10390a are right because:
- moving the
if (hasEnteredIteration)after means that we eagerly stop iterating thewhile (true)if we encountered a NEXT. - using UNIQUE in the switch doesn't make much sense. If you request the FIRST/LAST and failed the check with dirty DB, then it is fair to retry with NEXT/PREV. If you request the NEXT/PREV and failed the check with dirty DB, then it is fair to retry. If you request the UNIQUE and failed the check, I can't tell why it is reasonable to retry with NEXT.
- That might be the reason why you moved
if (hasEnteredIteration)afterwards though. I think this will regress NEXT/PREV with dirty share.
- That might be the reason why you moved
- I think the whole
if (foundID != null && queryType == QueryConstants.UNIQUE && changes != null)conditional should be removed. This will allow dirty share to report information about UNIQUE queries - The fat if conditional from RAQ shouldn't enter if
activeBundleKeyisUNIQUE, if DMO is changed. Thus, 10390a changes are not reasonable to alter this.if (info.isModified() && dirtyDMO != null && dmoSorter != null)checks if the record in the dirty database is different from the one in the primary database. It is not very relevant for UNIQUE, because in both primary and dirty the DMO matched the FQL, so it does not matter how they sort on the index (aka which was the first one). Thusint comp = dmoSorter.compare(dmo, compDirtyDMO);is not relevant for UNIQUE. For NEXT it is:- if it was changed and moved before the placeholder, then try again.
- if it was changed but it sorts the same on the index, use it.
- most of these are not relevant for UNIQUE because we don't want to ever "try again". So this whole if conditional is not dedicated for UNIQUE.
- There is an interesting check however for
info.isDeleted(), which check if the record found in the primary database was marked as deleted in the dirty-database. I think this is happening only in cross-session dirty-share cases, because in intra-session cases the record would have been deleted directly from the primary database. So this is a low priority aspect of this task. - What I really think is of interest for UNIQUE is the
processDirtyResultscall at the end. This checks which of the primary database DMO or the dirty database DMO is suitable. For your test-case in #10390-7, you need to convinceprocessDirtyResultsthat the dirty database one is suitable.processDirtyResultsseem to decide which DMO is more suitable based ondmoSorter.compare(aka which is earlier in the index). This is reasonable for NEXT/FIRST/PREV/LAST. But for UNIQUE, there should be a separate case to understand what DMO is more suitable:- I think that setting
useDirtyDMOto true forUNIQUEis always correct. - What it bothers me is that there is no evident way to state whether the FIND was ambiguous or not. We can defer such work. The tricky thing is that FWD has implementation for AMBIGUOUS only when loading from persistent database. This will be mitigated by #8388.
- I think that setting
TL;DR: please purge if (foundID != null && queryType == QueryConstants.UNIQUE && changes != null) conditional and change processDirtyResults to set useDirtyDMO if the FIND was UNIQUE.
#18 Updated by Teodor Gorghe 11 months ago
- the
if (hasEnteredIteration)conditional fromRAQ.executeImplwas moved because if we attempt theFIND FIRSTand we fail to check the dmo record from dirty db, on the second iteration withFIND NEXT, if it still fails, theplaceholdershould be set to dmo anddmoto null. Theif (hasEnteredIteration), when it is placed before the switch block, it does not get a chance for these two variables to be set. - on
processDirtyResults, theuseDirtyDMOis already set to true and there are no changes required.
#19 Updated by Teodor Gorghe 11 months ago
- % Done changed from 80 to 100
- Status changed from WIP to Review
Committed revision 16103 on task branch 10390a:
- Addressed code review #10390-17.
#20 Updated by Alexandru Lungu 11 months ago
- Status changed from Review to WIP
- % Done changed from 100 to 90
on processDirtyResults, the useDirtyDMO is already set to true and there are no changes required.
Right; this is correct.
I do not fully agree with this statement:the if (hasEnteredIteration) conditional from RAQ.executeImpl was moved because if we attempt the FIND FIRST and we fail to check the dmo record from dirty db, on the second iteration with FIND NEXT, if it still fails, the placeholder should be set to dmo and dmo to null. The if (hasEnteredIteration), when it is placed before the switch block, it does not get a chance for these two variables to be set.
- with NEXT/PREV, the code words the same in trunk vs 10390a
I am OK with this.
- with FIRST/LAST:
- in trunk there is a FIRST/LAST executed, due to dirty a NEXT/PREV executed (
hasEnteredIterationis set) and another NEXT/PREV is executed. AshasEnteredIterationis set it will now: break with stale DMO after 3 queries - in 10390a there is a FIRST/LAST executed, due to dirty a NEXT/PREV executed (
hasEnteredIterationis set). AshasEnteredIterationis set and there is a conditional after switch it will now: break with null DMO after 2 queries
- in trunk there is a FIRST/LAST executed, due to dirty a NEXT/PREV executed (
This is a deviation from trunk that doesn't seem required in regard to #10390-7. Maybe it is in regard to #10390-3? Personally I think it is not right to set the dmo on null and then break - this hints that we forcefully pretend that there is no DMO matching the WHERE (although we found one both in primary and dirty).
- with UNIQUE:
- in 10390a there is a query executed, due to dirty
hasEnteredIterationis set and it will now: break with null DMO after 1 query
- in 10390a there is a query executed, due to dirty
I am concerned that the activeBundleKey != UNIQUE && clause in if ((DirtyShareSupport.isEnabledCrossSession() || DirtyShareSupport.isEnabledIntraSession()) && shouldn't have been removed because it doesn't quite make sense to check for index sorting or re-execute query in primary database for UNIQUE.
Teodor, can you share the schema of adaptivept9? I am concerned that I do not have the full picture here.
#21 Updated by Teodor Gorghe 11 months ago
The full schema for adaptivept9 (taken from testcases project) is:
ADD TABLE "adaptivept9" AREA "Schema Area" DUMP-NAME "adaptivept9" ADD FIELD "k1" OF "adaptivept9" AS integer FORMAT "->>,>>9" INITIAL "0" POSITION 2 MAX-WIDTH 4 ORDER 10 ADD FIELD "k2" OF "adaptivept9" AS integer FORMAT "->>,>>9" INITIAL "0" POSITION 3 MAX-WIDTH 4 ORDER 20 ADD INDEX "idx3" ON "adaptivept9" AREA "Schema Area" UNIQUE PRIMARY INDEX-FIELD "k1" ASCENDING INDEX-FIELD "k2" ASCENDING
#22 Updated by Teodor Gorghe 11 months ago
I have checked the bundle and the results when FIND FIRST is used.
When activeBundleKey is first, the old record is retrieved from database. Then, it checks the dirty database, retrieves the updated record (is not null). It gets into the switch where the activateNext() is called.
On activate next, the following bundle is generated
Bundle
On activate next iteration, first two statements does not retrieve any record, but on the third statement, the old DMO is retrieved. Checks the dirty database, which returns the new record, goes into the that
if (hasEnteredIteration), returning the old record.
In this form, that third FQL statement seems valid to me and is normal to return a record from database, since the adaptivept9 k1 1 is located into the database, and 1 is greater than 0, but I don't quite understand his purpose.
#23 Updated by Alexandru Lungu 11 months ago
In this form, that third FQL statement seems valid to me and is normal to return a record from database, since the adaptivept9 k1 1 is located into the database, and 1 is greater than 0, but I don't quite understand his purpose.
It should not retrieve any record. Unless the placeholder is wrong and uses 0 as parameter for k1 > ?. From your statement I understand that this is the case.
adaptivept9.k1 = 0.
This update never reached primary database. Also, the primary database shouldn't be interrogated with information from dirty. In other words, if primary database didn't find out that k1 was updated to 0, then no query should presume that.
From placeholder = dmo; I understand that the placeholder should have had 1 instead of 0. Why is the query emitted with 0 as subst. param?
#24 Updated by Teodor Gorghe 11 months ago
Because persistence.load (Session.getImpl) returns the cached version of the dmo. The cached version of the dmo contains value k1 0 and k2 0.
#25 Updated by Constantin Asofiei 9 months ago
Teodor, is this an issue with the #9457 customer app?
#26 Updated by Teodor Gorghe 9 months ago
Yes, there are 2 unit tests that suffers this issue, but I have didn't considered this high priority to be in trunk because it is being solved by #8388.
#27 Updated by Alexandru Lungu 6 months ago
Added Andrei as a watcher. Teodor, let me know when this is ready for review.
#28 Updated by Teodor Gorghe 6 months ago
- % Done changed from 90 to 30
#29 Updated by Teodor Gorghe 6 months ago
I have starting working on this task and I have might found the root cause for each issue.
With the latest trunk, this is the following result:
╷
├─ JUnit Jupiter ✔
├─ JUnit Vintage ✔
├─ JUnit Platform Suite ✔
└─ FWD Test ✔
└─ tests.Test10390a ✔
├─ testFindQueryAndUpdate ✘ Expected: FALSE but was: yes
├─ testFindQueryOnSameBuffer ✔
├─ test10083TestcaseIfWorks ✔
└─ testFindQueryModifyIndexSecondField Expected: FALSE but was: yes
For the testFindQueryAndUpdate testcase, which is the original issue for this task, the issue happens because the dmo reference from RAQ.executeImpl is the same as the one from the Session cache. When the record gets updated, the record from Session cache also gets updated. This breaks the second FIND FIRST which uses the updated DMO parameters for second iteration NEXT (or dirty share fetch). I have disabled the cache and the test is passing. I think I will change the Session.getCached or Persistence.load to return a copy of DMO from cache.
For the testFindQueryModifyIndexSecondField, this is a real issue from RAQ.executeImpl which is related by DirtyShareContext.getDirtyInfo (the code which should be removed, #10390-17), and by RAQ.processDirtyResults (compDirtyDMO is null but for this case, it should actually execute the code bellow).
#30 Updated by Teodor Gorghe 6 months ago
- % Done changed from 30 to 90
Rebased branch 10390a to trunk rev 16353.
Committed revision 16357 on task branch 10390a:
- RAQ.executeImpl fix for cached dmos. (rev 16354 has been reverted).
Alexandru, I would like to discuss with you about the issue regarding DMO instance cache and check if this fix is right.
#31 Updated by Teodor Gorghe 6 months ago
- Status changed from WIP to Feedback
#32 Updated by Alexandru Lungu 6 months ago
Teodor, please mind that Andrei is already working is a close area. Please have a chat with him first to see if you can share knowledge, tests and fixes. After that, I can take a look at the attempt on 10390a and discuss with you on it.
#33 Updated by Teodor Gorghe 6 months ago
Alexandru Lungu wrote:
Teodor, please mind that Andrei is already working is a close area. Please have a chat with him first to see if you can share knowledge, tests and fixes. After that, I can take a look at the attempt on 10390a and discuss with you on it.
I have discussed and we discussed more about the issue and the conclusion is the following:
- the latest fix from #11027 fixes FIND NEXT/FOR EACH (Dynamic mode) but regresses FIND FIRST, making the execution to be in a infinite loop.
- description: since the DMO returned from Session cache reflects the modified DMO and not what is in the PRIMARY database. The hydrated field values no longer reflects the FQL WHERE criteria which leads to the wrong parameter substitution on the NEXT iteration.
- proposed solution: if the record is not being found in the dirty database (but marked as CACHED and CHANGED), it should attempt with NEXT using the correct substitution values from pristine record.
I will remove the partial fix for the first testcase and I will keep the fix for FIND UNIQUE.
#34 Updated by Andrei Plugaru 6 months ago
- Related to Bug #11027: Unflushed record causes infinite loop added
#35 Updated by Teodor Gorghe 6 months ago
- Status changed from Feedback to WIP
#36 Updated by Teodor Gorghe 6 months ago
- % Done changed from 90 to 100
- Status changed from WIP to Review
#37 Updated by Teodor Gorghe 6 months ago
#38 Updated by Constantin Asofiei 5 months ago
Alex: please review this.
#39 Updated by Alexandru Lungu 5 months ago
- Status changed from Review to Internal Test
I am OK with the changes - they are on spot with UNIQUE handling in dirty-share.
Please run ChUI regression testing to see if this regressed cross-session dirty-share. There is a cut-out of cross-session dirty-share tests in the xfer/testcases (flushing_and_validation, cross_session). It is a harness you can run (but you need to activate cross-session dirty-share in directory.xml).
If ChUI passes, let me know to see what to test next.
#40 Updated by Teodor Gorghe 5 months ago
About the testcases cross-session tests, they have passed.
I have run the ChUI regression testing and I see two unexpected fails. I have tested them by hand and I see that they pass. I will test with a clean trunk with the same revision as was tested with 10390a and check if it actually comes from trunk.
#41 Updated by Teodor Gorghe 5 months ago
ChUI regression testing has passed.