package fuse import ( "fmt" "log" "strings" "sync" "unsafe" ) var _ = log.Println type BufferPool interface { AllocBuffer(size uint32) []byte FreeBuffer(slice []byte) String() string } type GcBufferPool struct { } // NewGcBufferPool is just a fallback to the standard allocation routines. func NewGcBufferPool() *GcBufferPool { return &GcBufferPool{} } func (me *GcBufferPool) AllocBuffer(size uint32) []byte { return make([]byte, size) } func (me *GcBufferPool) FreeBuffer(slice []byte) { } // BufferPool implements a pool of buffers that returns slices with // capacity of a multiple of PAGESIZE, which have possibly been used, // and may contain random contents. type BufferPoolImpl struct { lock sync.Mutex // For each page size multiple a list of slice pointers. buffersBySize [][][]byte // start of slice => true outstandingBuffers map[uintptr]bool // Total count of created buffers. Handy for finding memory // leaks. createdBuffers int } func NewBufferPool() *BufferPoolImpl { bp := new(BufferPoolImpl) bp.buffersBySize = make([][][]byte, 0, 32) bp.outstandingBuffers = make(map[uintptr]bool) return bp } func (me *BufferPoolImpl) String() string { me.lock.Lock() defer me.lock.Unlock() result := []string{} for exp, bufs := range me.buffersBySize { if len(bufs) > 0 { result = append(result, fmt.Sprintf("%d=%d", exp, len(bufs))) } } return fmt.Sprintf("created: %d, outstanding %d. Sizes: %s", me.createdBuffers, len(me.outstandingBuffers), strings.Join(result, ", ")) } func (me *BufferPoolImpl) getBuffer(pageCount int) []byte { for ; pageCount < len(me.buffersBySize); pageCount++ { bufferList := me.buffersBySize[pageCount] if len(bufferList) > 0 { result := bufferList[len(bufferList)-1] me.buffersBySize[pageCount] = me.buffersBySize[pageCount][:len(bufferList)-1] return result } } return nil } func (me *BufferPoolImpl) addBuffer(slice []byte, pages int) { for len(me.buffersBySize) <= int(pages) { me.buffersBySize = append(me.buffersBySize, make([][]byte, 0)) } me.buffersBySize[pages] = append(me.buffersBySize[pages], slice) } // AllocBuffer creates a buffer of at least the given size. After use, // it should be deallocated with FreeBuffer(). func (me *BufferPoolImpl) AllocBuffer(size uint32) []byte { sz := int(size) if sz < PAGESIZE { sz = PAGESIZE } if sz%PAGESIZE != 0 { sz += PAGESIZE } psz := sz / PAGESIZE me.lock.Lock() defer me.lock.Unlock() var b []byte b = me.getBuffer(psz) if b == nil { me.createdBuffers++ b = make([]byte, size, psz*PAGESIZE) } else { b = b[:size] } me.outstandingBuffers[uintptr(unsafe.Pointer(&b[0]))] = true // For testing should not have more than 20 buffers outstanding. if paranoia && (me.createdBuffers > 50 || len(me.outstandingBuffers) > 50) { panic("Leaking buffers") } return b } // 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 (me *BufferPoolImpl) FreeBuffer(slice []byte) { if slice == nil { return } if cap(slice)%PAGESIZE != 0 || cap(slice) == 0 { return } psz := cap(slice) / PAGESIZE slice = slice[:psz] key := uintptr(unsafe.Pointer(&slice[0])) me.lock.Lock() defer me.lock.Unlock() ok := me.outstandingBuffers[key] if ok { me.addBuffer(slice, psz) delete(me.outstandingBuffers, key) } }