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:
Virgil 2026-04-03 19:13:27 +00:00
commit f07ce8cfc4
2 changed files with 121 additions and 0 deletions

82
scope.go Normal file
View 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
View 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")
})
}