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

fix: memory leaks. #2489

Merged
merged 1 commit into from
Mar 17, 2021
Merged

fix: memory leaks. #2489

merged 1 commit into from
Mar 17, 2021

Conversation

docmerlin
Copy link
Contributor

@docmerlin docmerlin commented Feb 26, 2021

There were a number of memory leaks of the form of a = a[i:].
These were replaced with grow-able circular queue.

@lesam
Copy link
Contributor

lesam commented Feb 26, 2021

Could we extract the pattern? i.e.

a.PushBack(<slice>)
a.PopFront(<slice>)
a.Peek(index) <slice>

We already have codegen tools for the various slice types and it would make me feel better.

Copy link
Contributor

@nathanielc nathanielc left a comment

Choose a reason for hiding this comment

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

LGTM, in short these buffers grow indefinitely so when we reslice them to only the end of the buffer we need to shift all the data to the front of the buffer otherwise Go cannot free the unused space.

These appends therefore do not do any new allocations rather move the data to the beginning of the slice

@lesam
Copy link
Contributor

lesam commented Feb 26, 2021

Also this repo should be review-protected!

Copy link
Contributor

@lesam lesam left a comment

Choose a reason for hiding this comment

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

see comment

@lesam
Copy link
Contributor

lesam commented Feb 26, 2021

Mostly I'm worried about someone relying on these slices not being mutated, if we had the pattern extracted that would prevent future errors. i.e. these two are not equivalent:

a := []int{1,2,3}
b := a[1:]
fmt.Println(a[0])
a := []int{1,2,3}
b := append(a[0:], a[:1]...)
fmt.Println(a[0])

@lesam
Copy link
Contributor

lesam commented Feb 26, 2021

Possibly a better interface:

a.PushBack(<slice>)
a.PopFront()
a.PeekFront() <value>

endL := len(v) + q.l - q.tail
q.data = append(q.data[:0], append(v[endL:], append(q.data[q.head:q.tail], v[:endL]...)...)...)
q.tail += endL
break
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This may be a bit overengineered, I ended up not needing more than 1 enqueue at a time. I am not sure if I should remove the ability to enqueue multiple items at once.

Copy link
Contributor

Choose a reason for hiding this comment

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

I vote for single enqueue - feel free to save the multi-enqueue version somewhere in case we need it in the future. Much easier to test single enqueue.

Copy link
Contributor

Choose a reason for hiding this comment

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

Easier for me to review as well 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i := i // make i a local object
item := circularIntPtr(&i)
runtime.SetFinalizer(item, func(q circularIntPtr) {
// if the finalizer is called, that means that the GC believes the objects should be freed
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We use runtime.SetFinalizer to test if the GC is seeing the objects for deletion.

// Can't trust lowMark until all parents have reported.
// Remove any unneeded match points.
n.matchGroupsBuffer[groupId] = matches[i:]
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This block above, looks like it creates duplicates when stuff hasn't reported. I removed it, it could be related to an issue that has been seen.


// newSrcPointconstructs a Circular Queue
// with a given buffer buf. It is ok for buf to be nil.
func newSrcPointCircularQueue(buf ...srcPoint) *srcPointCircularQueue {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this is never called?

@docmerlin docmerlin force-pushed the fix-memory-leak branch 3 times, most recently from c6d819c to d0bd4a5 Compare March 4, 2021 23:45
@docmerlin docmerlin force-pushed the fix-memory-leak branch 5 times, most recently from 6465890 to c43b954 Compare March 17, 2021 19:31

// Peek peeks i ahead of the current head of queue. It should be used in conjunction with .Len() to prevent a panic.
func (q *{{ $k }}CircularQueue) Peek(i int) {{ $k }} {
p := q.head + i
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we bounds check to make sure we don't get garbage?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's intended to be used in conjuction with .Len to avoid that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok added the bounds check.

if q.head < q.tail{
copy(buf, q.data[q.head:q.tail])
} else {
copy(buf[copy(buf, q.data[q.head:]):], q.data[:q.tail])
Copy link
Contributor

Choose a reason for hiding this comment

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

This might be easier to understand written on more than one line:

partialLength := copy(buf, q.data[q.head:])
copy(buf[partialLength):], q.data[:q.tail])


}

//if q.Dequeue(1); q.Peek(-1) != q.Val() {
Copy link
Contributor

Choose a reason for hiding this comment

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

commented code ?

}
}

func Test_leakCircularBuf(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be easier to use Benchmarks for this sort of test, it gives you the number of allocations. (for the future, this is fine as-is)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This isn't for testing allocations, but for testing to make sure things get properly de-allocated (a test against leaks).

type {{ $k }}CircularQueue struct {
data []{{ $k }}
head int
tail int
Copy link
Contributor

Choose a reason for hiding this comment

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

invariant: tail = (head + l) % len(data) right? Is it worth keeping it separately?

Copy link
Contributor Author

@docmerlin docmerlin Mar 17, 2021

Choose a reason for hiding this comment

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

Yes, its mostly so we don't have to do a mod over and over again, it isn't strictly necessary.

}

// Peek peeks i ahead of the current head of queue. It should be used in conjunction with .Len() to prevent a panic.
func (q *{{ $k }}CircularQueue) Peek(i int) {{ $k }} {
Copy link
Contributor

Choose a reason for hiding this comment

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

This might be better named At since we're only showing one value at an index, Peek sounds like it should return a slice. Not a blocker.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I will keep it this same.

There were a number of memory leaks of the form of `a = a[i:]`.
These were replaced with circular queue buffers, to prevent the leaks.
@docmerlin docmerlin merged commit d06d07c into master Mar 17, 2021
@docmerlin docmerlin deleted the fix-memory-leak branch March 17, 2021 21:12
alespour pushed a commit to bonitoo-io/kapacitor that referenced this pull request Mar 24, 2021
There were a number of memory leaks of the form of `a = a[i:]`.
These were replaced with circular queue buffers, to prevent the leaks.
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.

3 participants