diff --git a/scope.go b/scope.go index ae38f92..a9ddb3c 100644 --- a/scope.go +++ b/scope.go @@ -59,6 +59,10 @@ func (s *Scope) CString(value string) *C.char { // FreeAll releases every allocation created under this scope. func (s *Scope) FreeAll() { + if s == nil { + return + } + if !s.freed.CompareAndSwap(false, true) { return } @@ -88,3 +92,9 @@ func (s *Scope) IsFreed() bool { } return s.freed.Load() } + +// Close releases every allocation in the scope and implements io.Closer. +func (s *Scope) Close() error { + s.FreeAll() + return nil +} diff --git a/scope_test.go b/scope_test.go index 718fa76..f651616 100644 --- a/scope_test.go +++ b/scope_test.go @@ -56,3 +56,31 @@ func TestScopeIsFreedTracksLifecycle(t *testing.T) { t.Fatal("expected nil scope to be treated as freed") } } + +func TestScopeCloseIsSafeAndIdempotent(t *testing.T) { + t.Parallel() + + var nilScope *Scope + if err := nilScope.Close(); err != nil { + t.Fatalf("expected nil scope close to return nil, got %v", err) + } + + 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) + } + + if err := scope.Close(); err != nil { + t.Fatalf("expected close to return nil, got %v", err) + } + + scope.Close() + if !buffer.IsFreed() { + t.Fatal("expected buffer to be freed after close") + } +}