- Deleted agent_test.go to streamline testing structure. - Removed mcp package and its related fetch and search files to simplify the codebase. - Enhanced context handling in jsapi_test.go and jsapi.go for better integration with JavaScript. - Introduced no-op objects for Trace and Node to handle uninitialized states gracefully. - Updated i18n translations to include new MCP-related labels and descriptions.
5.9 KiB
Context Resource Management
This document explains the resource management strategy for Context and Trace objects in JavaScript.
Overview
Both Context and Trace objects provide two cleanup methods:
-
__release()- Internal method called automatically by:- V8 garbage collector (when object is collected)
Use()function (immediate cleanup after callback)
-
Release()- Public method for explicit manual cleanup:- Called in
try-finallyblocks - Provides immediate resource cleanup
- Same implementation as
__release()- they do the same thing
- Called in
Resource Hierarchy
When Context.Release() is called, it automatically releases:
-
Trace object - If present, calls
Trace.__release()to cleanup:- Go bridge registry entries
- Trace manager resources
- Background goroutines
-
Context object - Releases:
- Go bridge registry entry for the Context itself
This ensures proper cleanup of the entire resource tree.
Usage Patterns
Pattern 1: Automatic Cleanup with Use() (Recommended)
Best for: Most cases, clean code, automatic resource management
// Context is released automatically after callback
Use(Context, contextData, (ctx) => {
// Access Trace (released automatically with context)
const trace = ctx.Trace
const node = trace.Add({ type: "step" }, { label: "Processing" })
trace.Info("Doing work")
node.Complete({ result: "done" })
return result
})
// ctx.Release() called automatically, which also releases Trace
Pattern 2: Manual Cleanup with try-finally
Best for: Explicit control, critical memory scenarios
const ctx = getContext() // or passed as parameter
const trace = ctx.Trace
try {
const node = trace.Add({ type: "step" }, { label: "Processing" })
trace.Info("Doing work")
node.Complete({ result: "done" })
return result
} finally {
// Explicit cleanup (also releases Trace)
ctx.Release()
}
Pattern 3: Separate Trace Cleanup
Best for: When you want to release Trace independently
const ctx = getContext()
const trace = ctx.Trace
try {
const node = trace.Add({ type: "step" }, { label: "Processing" })
trace.Info("Doing work")
node.Complete({ result: "done" })
// Release trace early if needed
trace.Release()
// Continue using ctx...
return result
} finally {
// Release context (Trace already released, safe to call again)
ctx.Release()
}
Pattern 4: No Explicit Cleanup (Not Recommended)
Avoid in production: Relies on GC, unpredictable timing
function processData(ctx) {
const trace = ctx.Trace
const node = trace.Add({ type: "step" }, { label: "Processing" })
trace.Info("Doing work")
node.Complete({ result: "done" })
return result
// Waits for V8 GC to call __release() - SLOW!
}
No-op Trace Handling
When Trace is not initialized, ctx.Trace returns a no-op object:
- All methods are no-ops (do nothing)
Release()is safe to call (no-op)- No errors are thrown
- Provides consistent API regardless of trace initialization
// Works even if Trace is not initialized
const ctx = getContext()
const trace = ctx.Trace // might be no-op
trace.Info("Message") // safe even if no-op
trace.Release() // safe even if no-op
ctx.Release() // always safe
Error Handling
Cleanup happens even when errors occur:
const ctx = getContext()
try {
const trace = ctx.Trace
const node = trace.Add({ type: "step" }, { label: "Processing" })
throw new Error("Something went wrong")
} finally {
// Cleanup still happens
ctx.Release() // also releases Trace
}
With Use():
try {
Use(Context, contextData, (ctx) => {
throw new Error("Something went wrong")
})
} catch (error) {
// Error is caught
// ctx.Release() was already called automatically
}
Memory Management
✅ Good: Immediate Cleanup
// Loop with immediate cleanup
for (let i = 0; i < 10000; i++) {
Use(Context, data, (ctx) => {
const trace = ctx.Trace
trace.Info(`Processing item ${i}`)
// Released immediately after each iteration
})
}
❌ Bad: Waiting for GC
// Memory accumulates until GC runs
for (let i = 0; i < 10000; i++) {
const ctx = getContext()
const trace = ctx.Trace
trace.Info(`Processing item ${i}`)
// No cleanup - may run out of memory!
}
Implementation Details
Context.Release() / Context.__release()
- Checks if
ctx.Traceexists - If yes, calls
trace.__release()to cleanup Trace resources - Releases Context from bridge registry
- Safe to call multiple times (idempotent)
- Errors in cleanup are silently ignored
Trace.Release() / Trace.__release()
- Releases Go manager object from bridge registry
- Calls
trace.Release(traceID)to cleanup:- Remove from global trace registry
- Stop background goroutines
- Free associated resources
- Safe to call multiple times (idempotent)
No-op Objects
Both no-op Trace and no-op Node provide:
- All methods as no-ops
Release()and__release()methods- Consistent API for error-free operation
- Zero memory overhead
Best Practices
- ✅ Use
Use()for automatic cleanup in most cases - ✅ Use
try-finallywithRelease()when you need explicit control - ✅ Release Context (which also releases Trace) rather than releasing each separately
- ✅ Release resources in loops to prevent memory accumulation
- ❌ Don't rely on GC for resource cleanup in production code
- ❌ Don't worry about calling
Release()twice - it's idempotent
Testing
See jsapi_release_test.go for comprehensive tests of:
- Context Release
- Trace Release
- Cascading cleanup (Context → Trace)
- try-finally pattern
- No-op object Release
- Error handling with cleanup
Run tests:
cd yao
go test -v ./agent/context -run Release