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
Issue3035: Memory leak in Array.splice() #3211
Conversation
Addresses #3035 |
{ | ||
inlineHeadSegment = DetermineInlineHeadSegmentPointer<JavascriptNativeFloatArray, 0, true>((JavascriptNativeFloatArray*)pArr); | ||
} | ||
else if (JavascriptNativeIntArray::Is(pArr)) |
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.
if (JavascriptNativeIntArray::Is(pArr) [](start = 17, length = 38)
This can be converted to an assert instead as JavascriptNativeArray::Is already verified that it is either a NativeFloat or NativeInt array #Resolved
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 should be NativeIntArray. FailFast would be good.
In reply to: 123650724 [](ancestors = 123650724)
@@ -2033,6 +2064,7 @@ namespace Js | |||
SparseArraySegmentBase *seg, *nextSeg, *prevSeg = nullptr; | |||
for (seg = fArray->head; seg; seg = nextSeg) | |||
{ | |||
bool isInlineSegment = JavascriptArray::IsInlineSegment(seg, fArray); |
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.
isInlineSegment [](start = 17, length = 15)
Have isInlineSegment as false and move isInlineSegment = JavascriptArray::IsInlineSegment(seg, fArray); in if part where we are allocating newSeg that way when we call ClearElements we need to check only isInlineSegment #WontFix
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.
Point taken and updated in other occurrences. Decided JavascriptNativeFloatArray::ConvertToVarArray() does not need to call clearElements() because its inlined head segment only contains native data. Same thing for JavascriptNativeIntArray::ConvertToVarArray().
In reply to: 123652599 [](ancestors = 123652599)
@@ -5615,6 +5633,11 @@ namespace Js | |||
head->length = offset + next->length; | |||
head->CheckLengthvsSize(); | |||
pArr->head = head; | |||
|
|||
if (oldHead != head && isInlineSegment) |
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.
if (oldHead != head && isInlineSegment) [](start = 16, length = 39)
I think if we reverse the && to have isInlineSegment first compiler can do a test for bool and then a pointer comparison which will be faster #Resolved
@@ -6500,7 +6523,13 @@ namespace Js | |||
uint32 newLength = head->length + countUndefined; | |||
if (newLength > head->size) | |||
{ | |||
SparseArraySegmentBase *oldHead = head; | |||
bool isInlineSegment = JavascriptArray::IsInlineSegment(head, this); | |||
head = SparseArraySegment<Var>::From(head)->GrowByMin(recycler, newLength - head->size); |
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.
GrowByMin [](start = 60, length = 9)
Seems like this will always allocate a new segment so the check oldHead != head is not needed #Closed
hasInlineSegment = HasInlineHeadSegment(pArr->head->length); | ||
} | ||
|
||
bool hasInlineSegment = IsInlineSegment(startSeg, pArr); |
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.
startSeg [](start = 48, length = 8)
Seems like we can have scenario where startSeg is nullptr (check below), after your change for those scenario we will have AV in FromVar during JavascriptNativeArray::Is #Resolved
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.
Added nullptr checking in JavascriptArray::IsInlineSegment().
In reply to: 123654814 [](ancestors = 123654814)
@@ -7137,6 +7149,11 @@ namespace Js | |||
{ | |||
segInsert = segInsert->GrowByMinMax(recycler, insertLen - spaceLeft, segInsert->next->left - segInsert->left - segInsert->size); | |||
} | |||
|
|||
if (oldSegInsert != segInsert && isInlineSegment) |
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.
oldSegInsert != segInsert [](start = 24, length = 25)
Redundant GrowBy* will always allocate a new segment #Resolved
if (isInlineSegment) | ||
{ | ||
this->ClearElements(seg, 0); | ||
} |
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.
Instead of clearing the elements can't we free it? next/previous is already adjusted so no one should be using seg. Will save recycler some work at same cost of clearing elements. #Closed
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 think the inline segment is a plus alloc, it's part of the array structure, you can't free only a part of memory of one allocation #Closed
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.
@@ -529,6 +529,31 @@ namespace Js | |||
VirtualTableInfo<JavascriptNativeFloatArray>::HasVirtualTable(aValue)); | |||
} | |||
|
|||
bool JavascriptArray::IsInlineSegment(SparseArraySegmentBase *seg, JavascriptArray *pArr) |
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.
seg [](start = 66, length = 3)
return false when seg is null? could be a valid case. #Resolved
@@ -7579,6 +7605,10 @@ namespace Js | |||
pArr->head = SparseArraySegment<T>::From(pArr->head)->GrowByMinMax(recycler, unshiftElements, ((nextToHeadSeg->left + unshiftElements) - pArr->head->left - pArr->head->size)); | |||
} | |||
|
|||
if (oldHead != pArr->head && isInlineSegment) |
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.
oldHead != pArr->head [](start = 12, length = 21)
Redundant? #Resolved
Only in this case current is changed, we can prevent oldCurrent != current below by initializing isInlineSegment to false and moving IsInlineSegment here #Resolved Refers to: lib/Runtime/Library/JavascriptArray.inl:919 in 05b1641. [](commit_id = 05b1641, deletion_comment = False) |
05b1641
to
bde3c38
Compare
Any other questions or comments? |
@@ -1357,6 +1397,8 @@ SECOND_PASS: | |||
const bool currentWasFull = current->length == current->size; | |||
|
|||
Assert(itemIndex == current->left + current->size); | |||
SparseArraySegmentBase* oldSegment = current; | |||
bool isInlineSegment = JavascriptArray::IsInlineSegment(oldSegment, this); | |||
current = SparseArraySegment<T>::CopySegment(recycler, (SparseArraySegment<T>*)current, next->left, next, next->left, next->length); |
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.
SparseArraySegment::CopySegment [](start = 30, length = 34)
CopySegment doesn't always allocate a new segment, can we assert before clearing that current != oldSegment #Resolved
Can we add the test case you have as a snippet in Exprgen? #Resolved |
Rest LGTM |
Sure In reply to: 311135664 [](ancestors = 311135664) |
Memory leak occurs when the entire inline segment is removed from the array's linked list, because the inline segment is not collected like other segments. Fix by clearing the elements of the inline segment when it is replaced.
bde3c38
to
baf6fcb
Compare
Merge pull request #3211 from suwc:build/suwc/Issue3035 Memory leak occurs when the entire inline segment is removed from the array's linked list, because the inline segment is not collected like other segments. Fix by clearing the elements of the inline segment when it is replaced.
…ce() Merge pull request #3211 from suwc:build/suwc/Issue3035 Memory leak occurs when the entire inline segment is removed from the array's linked list, because the inline segment is not collected like other segments. Fix by clearing the elements of the inline segment when it is replaced.
The assertions are intended to future-proof the assumption that isInlineSegment being true implies segment swap. chakra-core#3211
…GE #3322 @suwc] OS12507864: Correct assertion introduced in PR#3211 Merge pull request #3322 from suwc:build/suwc/OS12507864 The assertions are intended to future-proof the assumption that isInlineSegment being true implies segment swap. chakra-core/ChakraCore#3211
Memory leak occurs when the entire inline segment is removed
from the array's linked list, because the inline segment is
not collected like other segments. Fix by clearing the elements
of the inline segment when it is replaced.