Skip to content

Support parallel execution of JS functions without global variables #550

@davedwwang

Description

@davedwwang

Error occurs when invoking the same JS function concurrently via multiple goroutines​.
Here the code:

func IfJs() string {
	return `function callIf(i) {
	var g = i == 1;
    if (g) {
        i = i + 1;
    }
    return i;
}
`
}

func TestIfFuncJsParal(t *testing.T) {
	javascriptVM := New()
	_, err := javascriptVM.Run(IfJs())
	require.NoError(t, err)

	paral := 100
	wg := sync.WaitGroup{}
	wg.Add(paral)
	for i := 0; i < paral; i++ {
		go func() {
			defer wg.Done()
			f, err := javascriptVM.Get("callIf")
			require.NoError(t, err)
			_, err = f.Call(f, 1)
			require.NoError(t, err)
		}()
	}
	wg.Wait()
}

Here the ERROR:

fatal error: concurrent map writes

Description:​​

The root issue stems from how compiled callIf functions become nodeFunctionObject instances, each holding a pointer to a same runtime object (constructed via New()). When the Call method executes:

  1. The function scope is assigned to the runtime.scope field.
  2. Concurrent goroutines sharing the same runtime end up accessing:

The same scope
The same stash
The same underlying map

This creates unsafe concurrent map access when storing variables, triggering the observed race conditions.

Proposed Solutions

Option 1: ThreadLocal Isolation​

Pros: Explicit scope isolation
Cons:

  • Introduces external dependencies
  • Anti-pattern in Go

Option 2: Context-Based Scope Passing​

  • Avoid shared scope by storing scope in context​
  • Built-in cancellation support via context

Request for Feedback​

Which approach would better align with otto’s design philosophy? Are there alternative patterns worth considering?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions