Add concurrent testing and object registration for JsValue function
- Implemented TestJsValueConcurrent to validate the JsValue function under concurrent requests, ensuring proper handling of multiple goroutines. - Added TestJsValueRegistrationAndCleanup to verify the registration and cleanup of context objects, enhancing resource management. - Introduced object registration and release mechanisms in the Context type to manage JavaScript object lifecycles effectively. - Utilized sync.Mutex for thread-safe access to shared resources, improving the robustness of concurrent operations.
This commit is contained in:
parent
240cddbcaf
commit
b13c40e88d
2 changed files with 195 additions and 0 deletions
|
|
@ -1,9 +1,15 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"rogchap.com/v8go"
|
||||
)
|
||||
|
||||
var objectsMutex = sync.Mutex{}
|
||||
var objects = map[string]*Context{}
|
||||
|
||||
// JsValue return the JavaScript value of the context
|
||||
func (ctx *Context) JsValue(v8ctx *v8go.Context) (*v8go.Value, error) {
|
||||
return ctx.NewObject(v8ctx)
|
||||
|
|
@ -11,7 +17,16 @@ func (ctx *Context) JsValue(v8ctx *v8go.Context) (*v8go.Value, error) {
|
|||
|
||||
// NewObject Create a new JavaScript object from the context
|
||||
func (ctx *Context) NewObject(v8ctx *v8go.Context) (*v8go.Value, error) {
|
||||
|
||||
jsObject := v8go.NewObjectTemplate(v8ctx.Isolate())
|
||||
|
||||
id := uuid.NewString()
|
||||
ctx.objectRegister(id)
|
||||
|
||||
// Set the id and release function
|
||||
jsObject.Set("__id", id)
|
||||
jsObject.Set("__release", ctx.objectRelease(v8ctx.Isolate(), id))
|
||||
|
||||
jsObject.Set("ChatID", ctx.ChatID)
|
||||
jsObject.Set("AssistantID", ctx.AssistantID)
|
||||
jsObject.Set("Sid", ctx.Sid)
|
||||
|
|
@ -21,3 +36,18 @@ func (ctx *Context) NewObject(v8ctx *v8go.Context) (*v8go.Value, error) {
|
|||
}
|
||||
return instance.Value, nil
|
||||
}
|
||||
|
||||
func (ctx *Context) objectRegister(id string) {
|
||||
objectsMutex.Lock()
|
||||
defer objectsMutex.Unlock()
|
||||
objects[id] = ctx
|
||||
}
|
||||
|
||||
func (ctx *Context) objectRelease(iso *v8go.Isolate, id string) *v8go.FunctionTemplate {
|
||||
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||
objectsMutex.Lock()
|
||||
defer objectsMutex.Unlock()
|
||||
delete(objects, id)
|
||||
return v8go.Undefined(info.Context().Isolate())
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -32,6 +34,7 @@ func TestJsValue(t *testing.T) {
|
|||
t.Fatalf("Call failed: %v", err)
|
||||
}
|
||||
assert.Equal(t, "ChatID-123456", res)
|
||||
assert.Equal(t, 0, len(objects))
|
||||
}
|
||||
|
||||
func testContextJsvalueEmbed(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
||||
|
|
@ -56,3 +59,165 @@ func testContextJsvalueFunction(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|||
|
||||
return chatID
|
||||
}
|
||||
|
||||
// TestJsValueConcurrent test the JsValue function with concurrent requests
|
||||
func TestJsValueConcurrent(t *testing.T) {
|
||||
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
v8.RegisterFunction("testContextJsvalue", testContextJsvalueEmbed)
|
||||
|
||||
// Number of concurrent goroutines
|
||||
concurrency := 10
|
||||
iterationsPerGoroutine := 5
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, concurrency*iterationsPerGoroutine)
|
||||
results := make(chan string, concurrency*iterationsPerGoroutine)
|
||||
|
||||
// Launch concurrent goroutines
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func(routineID int) {
|
||||
defer wg.Done()
|
||||
|
||||
for j := 0; j < iterationsPerGoroutine; j++ {
|
||||
chatID := fmt.Sprintf("ChatID-%d-%d", routineID, j)
|
||||
assistantID := fmt.Sprintf("AssistantID-%d-%d", routineID, j)
|
||||
sid := fmt.Sprintf("Sid-%d-%d", routineID, j)
|
||||
|
||||
cxt := &Context{
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Sid: sid,
|
||||
}
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
function test(cxt) {
|
||||
return testContextJsvalue(cxt)
|
||||
}`, cxt)
|
||||
|
||||
if err != nil {
|
||||
errors <- fmt.Errorf("routine %d iteration %d failed: %v", routineID, j, err)
|
||||
return
|
||||
}
|
||||
|
||||
results <- res.(string)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait for all goroutines to complete
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
close(results)
|
||||
|
||||
// Check for errors
|
||||
for err := range errors {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Verify all results
|
||||
resultCount := 0
|
||||
for res := range results {
|
||||
assert.Contains(t, res, "ChatID-")
|
||||
resultCount++
|
||||
}
|
||||
|
||||
// Verify the correct number of results
|
||||
expectedResults := concurrency * iterationsPerGoroutine
|
||||
assert.Equal(t, expectedResults, resultCount, "Should have %d results", expectedResults)
|
||||
|
||||
// Verify all objects are cleaned up after GC
|
||||
// Note: objects should be released when v8 values are garbage collected
|
||||
assert.Equal(t, 0, len(objects), "All objects should be cleaned up")
|
||||
}
|
||||
|
||||
// TestJsValueRegistrationAndCleanup test the object registration and cleanup mechanism
|
||||
func TestJsValueRegistrationAndCleanup(t *testing.T) {
|
||||
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
// Clear objects map before test
|
||||
objectsMutex.Lock()
|
||||
objects = map[string]*Context{}
|
||||
objectsMutex.Unlock()
|
||||
|
||||
v8.RegisterFunction("testContextRegistration", testContextRegistrationEmbed)
|
||||
|
||||
// Create multiple contexts and verify registration
|
||||
contextCount := 5
|
||||
for i := 0; i < contextCount; i++ {
|
||||
cxt := &Context{
|
||||
ChatID: fmt.Sprintf("ChatID-%d", i),
|
||||
AssistantID: fmt.Sprintf("AssistantID-%d", i),
|
||||
Sid: fmt.Sprintf("Sid-%d", i),
|
||||
}
|
||||
|
||||
_, err := v8.Call(v8.CallOptions{}, `
|
||||
function test(cxt) {
|
||||
return testContextRegistration(cxt)
|
||||
}`, cxt)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Call %d failed: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// All objects should be cleaned up after v8.Call completes
|
||||
assert.Equal(t, 0, len(objects), "All objects should be cleaned up after execution")
|
||||
}
|
||||
|
||||
func testContextRegistrationEmbed(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
||||
return v8go.NewFunctionTemplate(iso, testContextRegistrationFunction)
|
||||
}
|
||||
|
||||
func testContextRegistrationFunction(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||
var args = info.Args()
|
||||
if len(args) < 1 {
|
||||
return bridge.JsException(info.Context(), "Missing parameters")
|
||||
}
|
||||
|
||||
ctx, err := args[0].AsObject()
|
||||
if err != nil {
|
||||
return bridge.JsException(info.Context(), err)
|
||||
}
|
||||
|
||||
// Verify the object has __id field
|
||||
id, err := ctx.Get("__id")
|
||||
if err != nil {
|
||||
return bridge.JsException(info.Context(), err)
|
||||
}
|
||||
|
||||
if !id.IsString() {
|
||||
return bridge.JsException(info.Context(), fmt.Errorf("__id should be a string"))
|
||||
}
|
||||
|
||||
// Verify the object has __release function
|
||||
release, err := ctx.Get("__release")
|
||||
if err != nil {
|
||||
return bridge.JsException(info.Context(), err)
|
||||
}
|
||||
|
||||
if !release.IsFunction() {
|
||||
return bridge.JsException(info.Context(), fmt.Errorf("__release should be a function"))
|
||||
}
|
||||
|
||||
// Verify the object is registered
|
||||
objectsMutex.Lock()
|
||||
idStr := id.String()
|
||||
_, exists := objects[idStr]
|
||||
objectsMutex.Unlock()
|
||||
|
||||
if !exists {
|
||||
return bridge.JsException(info.Context(), fmt.Errorf("object %s not registered", idStr))
|
||||
}
|
||||
|
||||
val, err := v8go.NewValue(info.Context().Isolate(), true)
|
||||
if err != nil {
|
||||
return bridge.JsException(info.Context(), err)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue