Merge pull request '[agent/codex:gpt-5.3-codex-spark] Read docs/RFC.md fully. Find ONE feature described in the sp...' (#3) from main into dev
This commit is contained in:
commit
f07ce8cfc4
2 changed files with 121 additions and 0 deletions
82
scope.go
Normal file
82
scope.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package cgo
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Scope tracks multiple allocations and releases them together.
|
||||
//
|
||||
// scope := cgo.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 zero-value scope for grouped CGo allocations.
|
||||
func NewScope() *Scope {
|
||||
return &Scope{}
|
||||
}
|
||||
|
||||
// Buffer allocates a new managed buffer.
|
||||
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.
|
||||
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.
|
||||
func (s *Scope) FreeAll() {
|
||||
if !s.freed.CompareAndSwap(false, true) {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
39
scope_test.go
Normal file
39
scope_test.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package cgo
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestScopeFreesAllResources(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scope := NewScope()
|
||||
buffer := scope.Buffer(4)
|
||||
cString := scope.CString("agent")
|
||||
if cString == nil {
|
||||
t.Fatal("expected CString allocation")
|
||||
}
|
||||
|
||||
if copied := buffer.CopyFrom([]byte("go")); copied != 2 {
|
||||
t.Fatalf("expected 2 bytes copied, got %d", copied)
|
||||
}
|
||||
|
||||
scope.FreeAll()
|
||||
scope.FreeAll() // idempotent
|
||||
|
||||
if !buffer.IsFreed() {
|
||||
t.Fatal("expected buffer to be freed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScopePanicsAfterFreeAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scope := NewScope()
|
||||
scope.FreeAll()
|
||||
|
||||
assertPanics(t, "scope is already freed", func() {
|
||||
scope.Buffer(1)
|
||||
})
|
||||
assertPanics(t, "scope is already freed", func() {
|
||||
scope.CString("nope")
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue