Bug #10639
Incremental conversion - unexpected DMO generation for space-only changes
100%
Related issues
History
#1 Updated by Octavian Adrian Gavril 10 months ago
Use the following testcase:a.p:
define temp-table tt no-undo
field f1 as integer
field f2 as integer
field f3 as integer.b.p:define temp-table tt no-undo
field f11 as integer
field f2 as integer
field f3 as integer.
Full conversion generates two DMOs, Tt_1.java and Tt_2.java (field names differences trigger different structures). If I add some newlines in a.p and run incremental conversion, a new DMO is generated, Tt_3.java (which corresponds to Tt_1.java). The issue is that Tt_1.java should be reused and Tt_3.java should NOT be created.
#2 Updated by Constantin Asofiei 10 months ago
I don't recall exactly when, but this was a known 'limitation' of incremental conversion, from few years back. I think this is a good time to find a fix for this and re-use the DMO.
#3 Updated by Octavian Adrian Gavril 10 months ago
- Status changed from New to WIP
The root cause of not considering Tt_1.java is that when p2o_pre.xml is executed and pre_tmpTabNames is created as a new H2Map, its content doesn't include any key that store Tt_1.java. Only Tt_2.java is found in pre_tmpTabNames.
#4 Updated by Octavian Adrian Gavril 10 months ago
It seems that pre_tmpTabNames_work is the one that doesn't contain Tt_1.java, not pre_tmpTabNames.
#5 Updated by Greg Shah 10 months ago
- Related to Feature #6083: schema (.df) changes in incremental conversion mode should only reconvert those programs that reference the changed schema added
#7 Updated by Greg Shah 10 months ago
- Related to Bug #10613: Incremental conversion issue: temp-table dmo does not get converted added
#9 Updated by Octavian Adrian Gavril 10 months ago
- % Done changed from 0 to 70
/**
* Clean all records about this file, from the incremental conversion database.
* <p>
* This will clear all work tables which have records for this file AST ID, and all
* records from the <code>CLASS_DEFINITIONS</code> table.
*
* @param file
* The file to clear.
*
* @return The set of dependency files.
*/
public static Set<String> clean(String file)
{
[...]
Consumer<ResultSet> c = (rs) ->
{
try
{
String workTableName = rs.getString(1);
String deleteSql = "delete from " + workTableName + " where fileid = ?";
for (Long fileId : fileIds)
{
if (fileId == null)
{
continue;
}
helper.executeUpdate(deleteSql, fileId);
}
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
};
helper.executeSQL("DELETE FROM CLASS_DEFINITIONS WHERE FILENAME = ?", file);
helper.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES " +
"WHERE TABLE_SCHEMA = SCHEMA() AND TABLE_NAME LIKE '%__WORK'", c);
[...]
}
This statement is responsible with deleting all the records related to a specific file during incremental conversion. Now, all the DMO names are removed and new ones are created. We can fix this adding this condition: AND TABLE_NAME NOT IN ('PRE_TMPTABNAMES__WORK', 'TMPTABNAMES__WORK'). But we need to establish what are the tables which don't need this kind of processing.
#10 Updated by Constantin Asofiei 10 months ago
I would go a different way: is OK to re-generate all DMOs, but the name should be preserved if the key matches. Maybe we need a global table (not per-file) with the DMO key and its name from the full conversion.
#11 Updated by Octavian Adrian Gavril 10 months ago
Constantin Asofiei wrote:
I would go a different way: is OK to re-generate all DMOs, but the name should be preserved if the key matches. Maybe we need a global table (not per-file) with the DMO key and its name from the full conversion.
Yes, I agree. From my understanding, this is what pre_tmpTabNames__work and tmpTabNames__work tables contain. They keep the interface names, respectively the implementation names, based on the AstKey.
This way, when we execute p2o_pre.xml and p2o.xml, we will generate new DMOs but we will use the names from the full conversion.
#13 Updated by Octavian Adrian Gavril 10 months ago
Greg Shah wrote:
Danut has changes in this area in 6083a.
I tested Danut's changes but the issue is not fixed.
public static Set<String> clean(String file)
{
// go through tables and remove records for this specific file
String[] exts = { "", ".ast", ".p2o", ".schema", ".dict" };
Long[] fileIds = new Long[exts.length];
AstManager mgr = AstManager.get();
for (int i = 0; i < exts.length; i++)
{
if (exts[i].isEmpty())
{
continue;
}
[...]The reason is that in 6083a, the file id of original code is avoided but not the .schema file. The file id registered in pre_tmpTabNames__work and tmpTabNames__work corresponds to the .schema file. So, the full conversion DMO names are still being removed with 6083a.#14 Updated by Octavian Adrian Gavril 10 months ago
Does anyone know how could I generate this kind of hierarchy: Tt_1.java, Tt_1_1.java and Tt_1_2.java?
start.p:
def new shared temp-table tt no-undo field price as int field description as char. run v1.p. find first tt. message tt.price.
v1.p:
def shared temp-table tt no-undo field price as int field description as char initial "desc". create tt. assign tt.price = 10.
My testcase generates Tt_1.java Tt_1_1.java for start.p and Tt_2.java for v1.p.
#15 Updated by Greg Shah 10 months ago
We should be converting everything dependent upon the Tt_1.java when it gets reconverted. If we don't include all programs that reference a compatible temp-table, then changes to that parent class could break the unmodified programs.
I don't know if we handle that properly in 6083a.
#16 Updated by Ovidiu Maxiniuc 10 months ago
Because v1's definition has an additional initial "desc", FWD decides the tables are not compatible. I think this is probably a regression?
- the
initial "desc"should not be taken into consideration forshared temp-tables, only when defining anew shared temp-table.
Details here: Data_Model_Objects
#17 Updated by Octavian Adrian Gavril 10 months ago
Greg Shah wrote:
We should be converting everything dependent upon the
Tt_1.javawhen it gets reconverted.
Yes. I noticed that we should keep only the name of the super interfaces, in this case Tt_1.java which is stored in pre_tmpTabNames.
Keeping the sub-interfaces name generates errors due to how p2o.xml is designed:
<action>tmpTabKey = create('com.goldencode.p2j.uast.AstKey', this, tmpTabCrit)</action>
<rule>!tmpTabNames.containsKey(tmpTabKey)
[...]
<rule on="false">true
<!-- log this access -->
<!-- <action>nextTTSuffixMap.containsKey(tt_interface)</action> -->
<action>oldPeer = tmpTabNodes.get(tmpTabKey)</action>
<rule>!oldPeer.isAnnotation("tt_suffix")tmoTabNames will be populated but tmpTabNodes will not.
My next solution would be to turn this condition AND TABLE_NAME NOT IN ('PRE_TMPTABNAMES__WORK', 'TMPTABNAMES__WORK') into this AND TABLE_NAME NOT LIKE 'PRE_TMPTABNAMES__WORK'. But I want to test what's the impact of generating the second layer of interfaces (Tt1_1.java, Tt_1_2.java). That's why I needed a testcase with this hierarchy.
#18 Updated by Octavian Adrian Gavril 10 months ago
Ovidiu Maxiniuc wrote:
Because
v1's definition has an additionalinitial "desc", FWD decides the tables are not compatible. I think this is probably a regression?
For some reason, the output is correct. The value of tt.price after running v1.p is 10. But I don't understand why a Tt_1_1 can be used as a Tt_2.
#19 Updated by Ovidiu Maxiniuc 10 months ago
Indeed, that is a puzzle. Try to remove the build/ folder and reconvert/recompile the project again. Some classes may be dangling, remnant from a previous conversion.
Before doing this, please add the description to message tt.price. IIRC, even in v1.p it looks like its value should be "desc" it will actually be empty, since the create will use the definition from the new shared table. That is, if the code will not crash because of table mismatch.
#20 Updated by Octavian Adrian Gavril 10 months ago
- % Done changed from 70 to 100
- Status changed from WIP to Review
- reviewer Alexandru Lungu, Ovidiu Maxiniuc added
I've committed the last proposed solution in 10639a/16189. I followed the suggestions from #10639-19 to modify the testcase, but it still fails to generate Tt_1_1 and Tt_1_2.
#21 Updated by Ovidiu Maxiniuc 10 months ago
I can confirm your observation that the _2 and subsequent interfaces are no longer created.
Therefore I studied a bit how the tmpTabCrit evolved in rules/schema/p2o_pre.xml, rules/schema/p2o.xml and rules/include/common-progress.rules. It has changed a bit since r11164. I think there is a regression since 20170901. I find it strange that we did not notice in the regression tests, especially ETF sets.
Review of 10639a/16189.
The code should work now. But I do not understand the usage of LIKE operator in this case (TABLE_NAME NOT LIKE 'PRE_TMPTABNAMES__WORK'). There is no pattern. It does the job, but it would be faster to use the equality test directly.
#22 Updated by Octavian Adrian Gavril 10 months ago
Ovidiu Maxiniuc wrote:
Review of 10639a/16189.
The code should work now. But I do not understand the usage ofLIKEoperator in this case (TABLE_NAME NOT LIKE 'PRE_TMPTABNAMES__WORK'). There is no pattern. It does the job, but it would be faster to use the equality test directly.
I changed that in 10639a/16190 ✅.
#23 Updated by Dănuț Filimon 10 months ago
Octavian, please check #10536-27 - #10536-32 and #10536-38 - #10536-39.
#24 Updated by Octavian Adrian Gavril 10 months ago
Dănuț Filimon wrote:
Octavian, please check #10536-27 - #10536-32 and #10536-38 - #10536-39.
I included that patch in 10639a/16191. I tested the scenario and it works fine. Both DMOs are generated properly.
#25 Updated by Dănuț Filimon 10 months ago
- Related to Bug #10626: DMO names can collide on case-insensitive file-system added
#27 Updated by Alexandru Lungu 9 months ago
I understand the change and it looks functionally right to me (especially after re-reading #10639). But I still have two concerns:Alex: Please review.
- I did not see any discussion upon what happens if you actually change the table. If you avoid clearing the
PRE_TMPTABNAMES__WORK, does it mean that all (even unused) table schemas are still retained?- Please clarify how this happens.
- Is this a potential memory leak?
- Of course, I think that changing a temp-table to match another one from another file won't make it share the same interface/multiplex, right? This might happen only if the other file is incrementally converted as well. This is also not happening in trunk, so it is fine not to assess now.
- If I change a temp-table 100 times, would I end up with a new
Tt_100, even though there are only 2 differentTtacross the legacy code?
- If I change a temp-table 100 times, would I end up with a new
Overall, I think this is a quick fix that fixed #10639, but the technicality of incremental conversion is still fragile in terms of DMO merging. In other words, incrementally converting some files in a different order may generate different outputs (incrementally convert A, B and after C is different that incrementally converting C, A, B), considering temp-table definitions are changes.
#28 Updated by Octavian Adrian Gavril 9 months ago
Alexandru Lungu wrote:
I understand the change and it looks functionally right to me (especially after re-reading #10639). But I still have two concerns:
- I did not see any discussion upon what happens if you actually change the table. If you avoid clearing the
PRE_TMPTABNAMES__WORK, does it mean that all (even unused) table schemas are still retained?
- Please clarify how this happens.
- Is this a potential memory leak?
- Of course, I think that changing a temp-table to match another one from another file won't make it share the same interface/multiplex, right? This might happen only if the other file is incrementally converted as well. This is also not happening in trunk, so it is fine not to assess now.
- If I change a temp-table 100 times, would I end up with a new
Tt_100, even though there are only 2 differentTtacross the legacy code?
Indeed, this is correct. I tested those scenarios and the unused temporary table schemas are retained. This seems a consequence of the 10639a changes.
#29 Updated by Octavian Adrian Gavril 9 months ago
Alexandru Lungu wrote:
I understand the change and it looks functionally right to me (especially after re-reading #10639). But I still have two concerns:
- I did not see any discussion upon what happens if you actually change the table. If you avoid clearing the
PRE_TMPTABNAMES__WORK, does it mean that all (even unused) table schemas are still retained?
- Please clarify how this happens.
- Is this a potential memory leak?
- Of course, I think that changing a temp-table to match another one from another file won't make it share the same interface/multiplex, right? This might happen only if the other file is incrementally converted as well. This is also not happening in trunk, so it is fine not to assess now.
- If I change a temp-table 100 times, would I end up with a new
Tt_100, even though there are only 2 differentTtacross the legacy code?
As Alex mentioned, we can ignore the second point and create a separate task for that issue.
But the first point seems like an obstacle introduced by the current fix. This is what I noticed during analyzing the scenario described in the first point:
- After the full conversion of two file that use different schema temp-tables, I got
Tt_1andTt_2inPRE_TMPTABNAMES__WORK. As Alex already mentioned, if I change the schema of one file and run incremental conversion, I got aTt_3besidesTt_1andTt_2. Look at the content ofPRE_TMPTABNAMES__WORK:FILEID ASTID POS KEY VALUE 55834574848 55834574850 5 aced0001[...] Tt_1 60129542144 60129542146 6 aced0002[...] Tt_2 64424509440 64424509442 7 aced0003[...] FwdEmbeddedWindow_1 64424509440 64424509468 8 aced0004[...] PageToolbar_1 55834574848 55834574850 9 aced0005[...] Tt_3
Shouldn't the pair (FILEID, ASTID) act like a primary key here?
In this case, it clearly doesn't. I think if we can fix that, the original issue can be fixed without any memory leak.
#30 Updated by Octavian Adrian Gavril 9 months ago
- Status changed from Review to WIP
- % Done changed from 100 to 90
#31 Updated by Octavian Adrian Gavril 9 months ago
- Status changed from WIP to Review
- % Done changed from 90 to 100
I analyzed the current implementation that handles insertions in H2Map. I changed the INSERT statement with MERGE statement in order to insert new row if there is a new pair of (FILEID, ASTID) or to update the existing row with the new KEY. Now, there are no unused temp-table schema retained in the PRE_TMPTABNAMES__WORK anymore. So, I think this fixes the memory leak.
I avoided any changes related to the current UNIQUE INDEX and to the AstKey class.
I committed the new solution into 10639a/16192.
Alex, Ovidiu, please review.
#32 Updated by Ovidiu Maxiniuc 9 months ago
I think the key column should be part of the key in workInsertSql statement, to match the unique index.
this.workInsertSql = "MERGE INTO " + workTable + " (fileId, astId, key, value) " +
"KEY (fileId, astId, key) VALUES (?, ?, ?, ?)";
Beside the evident hint from the unique index, skipping the key column would cause a single pair <key, value> to be possible for a <fileId, astId> entry. In your example, the key was aced000[...] - (apparently) the same for all entries, so it would work. But adding a different key for one of the existing <fileId, astId> will cause the existing one to be updated, even if the key is different.
I do not think this change will make any difference. It will only avoid UNIQUE INDEX KEY (fileId, astId, key) collisions. Since the conversion was executing without such incidents, this change will have no effect (except being more robust).
Returning to the issue you found, maybe the same-tree detection is not bulletproof? I think you might have found a flaw here.
#33 Updated by Octavian Adrian Gavril 9 months ago
Ovidiu Maxiniuc wrote:
In your example, the key was
aced000[...]- (apparently) the same for all entries, so it would work.
I apologize for the confusion. In fact, all of the keys from #10639-31 are different. I edited the table to make this clear. Initially, I had to cut a lot of text from each key and all of those start with the same characters. That's why the key seemed to be the same, but it's not.
But adding a different key for one of the existing
<fileId, astId>will cause the existing one to be updated, even if the key is different.
This is the expected behavior. We should not have two entries sharing the same key <fileId, astId. If that were the case, it would imply that a single file contains two different temporary tables defined by ASTs with identical IDs, which is fundamentally impossible. Therefore, I designed the process to overwrite those keys during incremental conversion to ensure consistency with the final output source code.
#34 Updated by Alexandru Lungu 8 months ago
Ovidiu, is there something left to assess?
#35 Updated by Ovidiu Maxiniuc 8 months ago
Ultimately, H2Map is a HashMap, which means it should associate values to keys. Here is the put() method:
public Object put(Object key, Object value)
{
value = wrapValue(value);
Object old = super.put(key, value);
logChange(key, value);
return unwrapValue(old);
}
Ignoring the key will cause the db backed version to de-synchronize from the in-memory data stored by super class.
Let me present another usage of same class:
- during conversion we have
funcRetTypeswhich is a collection meant to map the 4GL function names to their return types:<rule>funcRetTypes = createString2StringMap("funcRetTypes")</rule> CommonAstSupport.java,createString2StringMap()delegates toConversionData.createMap(String.class, String.class, name);- from
ConversionData.javawe have anew H2Map(keyClass, valueClass, name, helper);
If we 'merge' using a key formed only from fileId, astId, this structure will be able to store the return type of a single function. It would be impossible to store information for function my-len return int (input character str) and function to-upper return character(input character str) from same source file. Since they belong to same file, both functions will share the fileId and AST root astId.
#36 Updated by Octavian Adrian Gavril 8 months ago
Ovidiu Maxiniuc wrote:
If we 'merge' using a key formed only from
fileId, astId, this structure will be able to store the return type of a single function. It would be impossible to store information forfunction my-len return int (input character str)andfunction to-upper return character(input character str)from same source file. Since they belong to same file, both functions will share thefileIdand AST rootastId.
I'm not sure if that's correct. From my understanding, astId is the id of the associated source AST, not the AST root. Isn't that right?