Files
go-fuse/fuse/bufferpool.go
Han-Wen Nienhuys 9e813d539d fuse: fix memory leak for read buffers
In commit 418525a ("fuse: drop server.allocOut"), I intended to move
where requestAlloc.bufferPoolOutputBuf was set, but I actually removed
the line.

This effectively disables the sync.Pool used for shipping large data
(ie. reads) back to the kernel. This was visible in the benchmark
outputs, but that requires close inspection to notice the problem. 

Fix this, and add a test that catches this problem in the future.

Thanks to https://github.com/darthShadow for diagnosing and reporting
this problem.

Fixes https://github.com/hanwen/go-fuse/issues/542

Change-Id: I40e17274c8e8f2b12e46008661c324c68fc81335
2024-11-19 10:16:37 +01:00

83 lines
1.9 KiB
Go

// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fuse
import (
"os"
"sync"
)
// bufferPool implements explicit memory management. It is used for
// minimizing the GC overhead of communicating with the kernel.
type bufferPool struct {
lock sync.Mutex
// For each page size multiple a list of slice pointers.
buffersBySize []*sync.Pool
// Number of outstanding allocations. Used for testing.
countersBySize []int
}
var pageSize = os.Getpagesize()
func (p *bufferPool) counters() []int {
p.lock.Lock()
defer p.lock.Unlock()
d := make([]int, len(p.countersBySize))
copy(d, p.countersBySize)
return d
}
func (p *bufferPool) getPool(pageCount int, delta int) *sync.Pool {
p.lock.Lock()
defer p.lock.Unlock()
for len(p.buffersBySize) <= pageCount {
p.buffersBySize = append(p.buffersBySize, nil)
p.countersBySize = append(p.countersBySize, 0)
}
if p.buffersBySize[pageCount] == nil {
p.buffersBySize[pageCount] = &sync.Pool{
New: func() interface{} { return make([]byte, pageSize*pageCount) },
}
}
p.countersBySize[pageCount] += delta
return p.buffersBySize[pageCount]
}
// AllocBuffer creates a buffer of at least the given size. After use,
// it should be deallocated with FreeBuffer().
func (p *bufferPool) AllocBuffer(size uint32) []byte {
sz := int(size)
if sz < pageSize {
sz = pageSize
}
if sz%pageSize != 0 {
sz += pageSize
}
pages := sz / pageSize
b := p.getPool(pages, 1).Get().([]byte)
return b[:size]
}
// FreeBuffer takes back a buffer if it was allocated through
// AllocBuffer. It is not an error to call FreeBuffer() on a slice
// obtained elsewhere.
func (p *bufferPool) FreeBuffer(slice []byte) {
if slice == nil {
return
}
if cap(slice)%pageSize != 0 || cap(slice) == 0 {
return
}
pages := cap(slice) / pageSize
slice = slice[:cap(slice)]
p.getPool(pages, -1).Put(slice)
}