From 9848a00f5cb55a1da3ec045dec61d353c4f98620 Mon Sep 17 00:00:00 2001 From: Roger Chapman Date: Fri, 19 Feb 2021 11:00:59 +1100 Subject: [PATCH] Release value memory on context close (#78) * Release value memory on context close * retry test with a larger number of contexts * no need to re-cast * Update changelog and release v0.5.1 --- CHANGELOG.md | 5 ++ context.go | 12 ++- context_test.go | 17 +++++ function_template.go | 1 - v8go.cc | 171 ++++++++++++++++++++++++++++--------------- v8go.h | 1 + value.go | 1 - 7 files changed, 145 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c484ab36..0b8c5adc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.5.1] - 2021-02-19 + +### Fixed +- Memory being held by Values after the associated Context is closed + ## [v0.5.0] - 2021-02-08 ### Added diff --git a/context.go b/context.go index 3d7d01ed..b365dbe1 100644 --- a/context.go +++ b/context.go @@ -116,11 +116,11 @@ func (c *Context) RunScript(source string, origin string) (*Value, error) { func (c *Context) Global() *Object { valPtr := C.ContextGlobal(c.ptr) v := &Value{valPtr, c} - runtime.SetFinalizer(v, (*Value).finalizer) return &Object{v} } // Close will dispose the context and free the memory. +// Access to any values assosiated with the context after calling Close may panic. func (c *Context) Close() { c.finalizer() } @@ -165,13 +165,17 @@ func getContext(ref int) *Context { return r.ctx } +//export goContext +func goContext(ref int) C.ContextPtr { + ctx := getContext(ref) + return ctx.ptr +} + func getValue(ctx *Context, rtn C.RtnValue) *Value { if rtn.value == nil { return nil } - v := &Value{rtn.value, ctx} - runtime.SetFinalizer(v, (*Value).finalizer) - return v + return &Value{rtn.value, ctx} } func getError(rtn C.RtnValue) error { diff --git a/context_test.go b/context_test.go index 23c05d28..307f4d13 100644 --- a/context_test.go +++ b/context_test.go @@ -92,6 +92,23 @@ func TestContextRegistry(t *testing.T) { } } +func TestMemoryLeak(t *testing.T) { + t.Parallel() + + iso, _ := v8go.NewIsolate() + + for i := 0; i < 6000; i++ { + ctx, _ := v8go.NewContext(iso) + obj := ctx.Global() + _ = obj.String() + _, _ = ctx.RunScript("2", "") + ctx.Close() + } + if n := iso.GetHeapStatistics().NumberOfNativeContexts; n >= 6000 { + t.Errorf("Context not being GC'd, got %d native contexts", n) + } +} + func BenchmarkContext(b *testing.B) { b.ReportAllocs() vm, _ := v8go.NewIsolate() diff --git a/function_template.go b/function_template.go index 95e7b783..3d9308c4 100644 --- a/function_template.go +++ b/function_template.go @@ -70,7 +70,6 @@ func goFunctionCallback(ctxref int, cbref int, args *C.ValuePtr, argsCount int) argv := (*[1 << 30]C.ValuePtr)(unsafe.Pointer(args))[:argsCount:argsCount] for i, v := range argv { val := &Value{ptr: v} - runtime.SetFinalizer(val, (*Value).finalizer) info.args[i] = val } diff --git a/v8go.cc b/v8go.cc index 6478e1ad..e49fa9f6 100644 --- a/v8go.cc +++ b/v8go.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include "libplatform/libplatform.h" #include "v8.h" @@ -21,19 +22,20 @@ auto default_platform = platform::NewDefaultPlatform(); auto default_allocator = ArrayBuffer::Allocator::NewDefaultAllocator(); typedef struct { - Persistent ptr; Isolate* iso; + std::vector vals; + Persistent ptr; } m_ctx; typedef struct { - Persistent ptr; - Persistent ctx; Isolate* iso; + m_ctx* ctx; + Persistent> ptr; } m_value; typedef struct { - Persistent