go-cgo/scope.go

127 lines
2.3 KiB
Go
Raw Permalink Normal View History

package cgo
/*
#include <stdlib.h>
*/
import "C"
import (
"runtime"
"sync"
"sync/atomic"
"unsafe"
)
// Scope tracks multiple C allocations and releases them together.
//
// scope := NewScope()
// defer scope.FreeAll()
// buffer := scope.Buffer(32)
// cString := scope.CString("hello")
type Scope struct {
lock sync.Mutex
buffers []*Buffer
strings []unsafe.Pointer
freed atomic.Bool
}
// NewScope creates a grouped allocator for temporary C memory.
//
// scope := NewScope()
// defer scope.FreeAll()
func NewScope() *Scope {
scope := &Scope{}
runtime.SetFinalizer(scope, func(owned *Scope) {
owned.FreeAll()
})
return scope
}
// Buffer allocates a managed buffer and registers it for cleanup.
//
// buffer := scope.Buffer(64)
func (s *Scope) Buffer(size int) *Buffer {
s.lock.Lock()
defer s.lock.Unlock()
if s.freed.Load() {
panic("cgo.Scope.Buffer: scope is already freed")
}
buffer := NewBuffer(size)
s.buffers = append(s.buffers, buffer)
return buffer
}
// CString allocates a managed C string and registers it for cleanup.
//
// cString := scope.CString("hello")
func (s *Scope) CString(value string) *C.char {
s.lock.Lock()
defer s.lock.Unlock()
if s.freed.Load() {
panic("cgo.Scope.CString: scope is already freed")
}
cString := CString(value)
s.strings = append(s.strings, unsafe.Pointer(cString))
return cString
}
// FreeAll releases every allocation created under this scope.
//
// scope := NewScope()
// defer scope.FreeAll()
func (s *Scope) FreeAll() {
if s == nil {
return
}
if !s.freed.CompareAndSwap(false, true) {
panic("cgo.Scope.FreeAll: double-free detected")
}
runtime.SetFinalizer(s, nil)
s.lock.Lock()
buffers := s.buffers
strings := s.strings
s.buffers = nil
s.strings = nil
s.lock.Unlock()
for _, buffer := range buffers {
if buffer != nil && !buffer.IsFreed() {
buffer.Free()
}
}
for _, pointer := range strings {
Free(pointer)
}
}
// IsFreed reports whether FreeAll has been called.
//
// if scope.IsFreed() {
// // scope is inactive
// }
func (s *Scope) IsFreed() bool {
if s == nil {
return true
}
return s.freed.Load()
}
// Close releases every allocation in the scope and implements io.Closer.
//
// scope := NewScope()
// defer scope.Close()
//
// // same as:
// scope.FreeAll()
func (s *Scope) Close() error {
s.FreeAll()
return nil
}