-
Notifications
You must be signed in to change notification settings - Fork 397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix for circular references #659
Conversation
@@ -298,7 +301,8 @@ export class HeapDumpService { | |||
// cloned into a new variable that has a variable name. | |||
private copyReferenceContainer( | |||
refContainer: ApexVariableContainer, | |||
varName: string | |||
varName: string, | |||
createVarRef = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was necessary because the variablesRef (which is what tells VS Code that the variable can be expanded) didn't need to be recreated for a variable that's part of the circular reference. The reason cloning was necessary was due to the name changing. For instance local var MyVar of type Foo has field that's a List and we add MyVar to its own list. The name of the local variable is MyVar but once added to the list the name is '0' or whatever number in the list it is.
@@ -636,6 +637,71 @@ describe('Replay debugger adapter variable handling - unit', () => { | |||
) as ApexVariableContainer; | |||
expect(updatedNonRefVariable.value).to.be.eq('5'); | |||
}); | |||
|
|||
it('Should correctly deal with circular references and variable values', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please clean up needless comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing in that test is a 'needless' comment. Please be specific about which ones you think are needless.
@@ -597,24 +630,44 @@ export class HeapDumpService { | |||
public createVariableFromReference( | |||
varName: string, | |||
refVariable: ApexVariableContainer, | |||
visitedMap: Map<string, null> | |||
visitedMap: Map<string, ApexVariableContainer | null>, | |||
updateAfterVarCreation: ApexVariableContainer[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UpdateAfterVarCreation is used in the finally clause. I ended up getting into a chicken and egg situation here and some variable values cannot be updated until the variable is finished being created. This is only applicable to circular references. createVariableFromReference is a recursive function and because of the visitedMap we when we remove the last entry we know we're done with the initial variable being created. It's at this that time it's safe to update the values of any variables that we couldn't update during creation because we know the variable is complete. Trying to create the value earlier, when the variable was incomplete, would end up creating a value that was also incomplete.
@@ -134,7 +134,8 @@ export class HeapDumpService { | |||
const updatedVariable = this.createVariableFromReference( | |||
symbolName, | |||
refVar, | |||
new Map<string, null>() | |||
new Map<string, null>(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The replaceFrameVariablesWithHeapDump
is quite long.
It is worth considering breaking this up into smaller methods, which each method having a more descriptive name than having all the comments.
I don't know if you have time to do this now but, in general, this code type of long method code is not maintainable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can create a work item to do this but it's not happening right now. There are other bugs that need to get immediately looked at from the Blitz.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please clean up all unnecessary comments.
The method itself is too long for me to actually make any meaningful comments since I can't follow the logic of all this.
// was set up correctly. The local variable cf1, of type CircularReference contains | ||
// a field that's a List<CircularReference>. After creating cf1 we add cf1 to its own | ||
// list. | ||
const expectedVariableValue = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not removing this comment, this is what the test case is setting up and verifying.
|
||
// Verify the value is set correctly and there's exactly 1 item | ||
// in the list | ||
expect(listChildVar.value).to.be.eq(expectedListVarValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove.
expect(updatedLocRefVariable.value).to.be.eq(expectedVariableValue); | ||
|
||
// There should be 2 children, 1 list named cfList and 1 integer named someInt | ||
expect(updatedLocRefVariable.variables.size).to.be.eq(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'll remove these.
|
||
// There should be 1 child item on the list and it should have the same | ||
// value as cf1, expectedVariableValue. | ||
const listElementVar = listChildVar.variables.get( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
'0' | ||
) as ApexVariableContainer; | ||
expect(listElementVar.value).to.be.eq(expectedVariableValue); | ||
// Lastly, verify that it's list has the same value as cfList, expectedListVarValue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove.
topFrame.id = id; | ||
frameInfo.locals.set(localRefVariable.name, localRefVariable); | ||
// Update it with the heap dump | ||
heapDumpService.replaceVariablesWithHeapDump(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove.
.returns(heapdump); | ||
|
||
// Create a ref variable to update | ||
const localRefVariable = new ApexVariableContainer( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove.
|
||
it('Should correctly deal with circular references and variable values', () => { | ||
// Create the heapdump | ||
const heapdump = createHeapDumpWithCircularRefs(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can agree to disagree about our standards of comments.
I would share https://blog.codinghorror.com/coding-without-comments/. The technique of making smaller methods with more descriptive names, often obviates the need for comments.
This is something that keeps coming up during the reviews so I thought I will drill into it more. It is also codified in our coding guidelines.
Codecov Report
@@ Coverage Diff @@
## develop #659 +/- ##
===========================================
+ Coverage 74.83% 74.87% +0.03%
===========================================
Files 158 158
Lines 6405 6431 +26
Branches 1001 1009 +8
===========================================
+ Hits 4793 4815 +22
- Misses 1357 1358 +1
- Partials 255 258 +3
Continue to review full report at Codecov.
|
@@ -327,7 +337,8 @@ export class HeapDumpService { | |||
// 1: '2' | |||
// 2: '3' | |||
private createStringFromVarContainer( | |||
varContainer: ApexVariableContainer | |||
varContainer: ApexVariableContainer, | |||
visitedMap: Map<string, null> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why we use it as a Map<string, null>
? I think we don't ever use the key so whatever it is fine. But, in that case, why not use a Set
since you are just checking for membership?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Habit and slight ignorance. Both Map and Set are close to O(1) in lookup times and I keep seeing Set and thinking Array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, please change it then. Using a Set
is much clearer rather than a Map<string, null>
since there is nothing keyed off the value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's changed with the latest commit.
// If the varContainer is a ref and it's already been visited then return the string | ||
if (varContainer.ref) { | ||
if (visitedMap.has(varContainer.ref)) { | ||
return 'already output'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am guessing that this is the text matches the interactive debugger?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. The pictures in the description illustrate live VS replay which is why I'd used "already output"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, the technique seems to be a "check-if-we-have-seen-this-before" and it we have put out "already output". We store what we have seen in a Map (possibly replaceable by a Set). This looks OK to me.
@DatGuyJonathan - Any other concerns here?
@vazexqi sure, I may tend to over comment and I admit to that. With simple code doing simple things, sure, it's 'self commenting' but I've had to deal with a lot of code that wasn't simple code doing simple things that had no comments. In the case of dealing heap dumps and extents I'd much rather over comment than under comment, especially when we're dealing with things like |
I'm trying to figure out where in this change it would've ran into the error |
@DatGuyJonathan the project wasn't added initially because I'm the only that ended up fixing the bug. It's been added now. The Max Stacksize exceeded was due to in an infinite loop in in the heap dump processing, not something coming from the server. |
Okay, my question was about W-5491858 which got duped out to W-5491876. So, if W-5491858 is not fixed yet, you should probably re-open it. |
It was duped because the issues were related and I fixed both them with this PR. |
Thanks for the contribution! Unfortunately we can't verify the commit author(s): James Suplizio <j***@s***.com>. One possible solution is to add that email to your GitHub account. Alternatively you can change your commits to another email and force push the change. After getting your commits associated with your GitHub account, refresh the status of this Pull Request. |
Thanks for the contribution! Unfortunately we can't verify the commit author(s): James Suplizio <j***@s***.com>. One possible solution is to add that email to your GitHub account. Alternatively you can change your commits to another email and force push the change. After getting your commits associated with your GitHub account, refresh the status of this Pull Request. |
What does this PR do?
There was an issue where circular references weren't being hooked up correctly. This was causing the value of the name/value pair displayed in VS Code to display the wrong thing. Initially I'd opted stop hooking things up when there was a circular reference but this ended leaving a dangling reference. This also caused the value, when it was computed, to be incorrect as well. Right now, with the way it's hooked up it'll mimic the live debugger which is to say that you can just open up the variables until rapture. There is a slight difference in the replay debugger vs live debugger here. The live debugger does something special when it encounters a circular reference and puts it in braces (see picture) instead of keeping it in the same list/variable format as non-recusive references.
![livedebuggercircref](https://user-images.githubusercontent.com/13556087/46500848-b185cf00-c7d8-11e8-917b-a8fa52fb491c.png)
The replay-debugger keeps the live debugging format but still identifies the circular dependency. Mainly because the code to special case and add braces would be painful to deal with (the live debugger has a bit more leniency when dealing with variables since it doesn't have to compute the values from heap dump extent entries.
![replaydebuggercircref](https://user-images.githubusercontent.com/13556087/46501164-8b146380-c7d9-11e8-8067-a9dc7eafe260.png)
What issues does this PR fix or reference?
@W-5491876@