Skip to content
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

Merged
merged 1 commit into from Jun 26, 2017

Conversation

suwc
Copy link

@suwc suwc commented Jun 23, 2017

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.

@suwc
Copy link
Author

suwc commented Jun 23, 2017

Addresses #3035

{
inlineHeadSegment = DetermineInlineHeadSegmentPointer<JavascriptNativeFloatArray, 0, true>((JavascriptNativeFloatArray*)pArr);
}
else if (JavascriptNativeIntArray::Is(pArr))
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Jun 23, 2017

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

Copy link
Contributor

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);
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Jun 23, 2017

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

Copy link
Author

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)
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Jun 23, 2017

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);
Copy link
Collaborator

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);
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Jun 23, 2017

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

Copy link
Author

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)
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Jun 23, 2017

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);
}
Copy link
Collaborator

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

Copy link
Contributor

@leirocks leirocks Jun 23, 2017

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct.


In reply to: 123792263 [](ancestors = 123792263)

@@ -529,6 +529,31 @@ namespace Js
VirtualTableInfo<JavascriptNativeFloatArray>::HasVirtualTable(aValue));
}

bool JavascriptArray::IsInlineSegment(SparseArraySegmentBase *seg, JavascriptArray *pArr)
Copy link
Contributor

@akroshg akroshg Jun 23, 2017

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)
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Jun 23, 2017

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

@agarwal-sandeep
Copy link
Collaborator

agarwal-sandeep commented Jun 23, 2017

                    current = current->GrowByMin(recycler, growby);

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)

@suwc
Copy link
Author

suwc commented Jun 26, 2017

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);
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Jun 26, 2017

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

@agarwal-sandeep
Copy link
Collaborator

agarwal-sandeep commented Jun 26, 2017

Can we add the test case you have as a snippet in Exprgen? #Resolved

@agarwal-sandeep
Copy link
Collaborator

Rest LGTM

@suwc
Copy link
Author

suwc commented Jun 26, 2017

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.
@chakrabot chakrabot merged commit baf6fcb into chakra-core:release/1.6 Jun 26, 2017
chakrabot pushed a commit that referenced this pull request Jun 26, 2017
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.
@suwc suwc deleted the build/suwc/Issue3035 branch June 26, 2017 19:51
chakrabot pushed a commit that referenced this pull request Jun 26, 2017
…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.
suwc pushed a commit to suwc/ChakraCore that referenced this pull request Jul 12, 2017
The assertions are intended to future-proof the assumption that
isInlineSegment being true implies segment swap.
chakra-core#3211
chakrabot pushed a commit that referenced this pull request Jul 12, 2017
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.
#3211
chakrabot pushed a commit that referenced this pull request Jul 12, 2017
…d 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.
#3211
chakrabot pushed a commit that referenced this pull request Jul 12, 2017
…ion 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.
#3211
chakrabot added a commit to nodejs/node-chakracore that referenced this pull request Jul 13, 2017
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants