unicode !< path;
+ RUNTIME
+ < internal/synctest
+ < testing/synctest;
+
# SYSCALL is RUNTIME plus the packages necessary for basic system calls.
- RUNTIME, unicode/utf8, unicode/utf16
+ RUNTIME, unicode/utf8, unicode/utf16, internal/synctest
< internal/syscall/windows/sysdll, syscall/js
< syscall
< internal/syscall/unix, internal/syscall/windows, internal/syscall/windows/registry
FMT, DEBUG, flag, runtime/trace, internal/sysinfo, math/rand
< testing;
- RUNTIME
- < internal/synctest
- < testing/synctest;
-
log/slog, testing
< testing/slogtest;
//go:linkname Wait
func Wait()
+
+//go:linkname acquire
+func acquire() any
+
+//go:linkname release
+func release(any)
+
+//go:linkname inBubble
+func inBubble(any, func())
+
+// A Bubble is a synctest bubble.
+//
+// Not a public API. Used by syscall/js to propagate bubble membership through syscalls.
+type Bubble struct {
+ b any
+}
+
+// Acquire returns a reference to the current goroutine's bubble.
+// The bubble will not become idle until Release is called.
+func Acquire() *Bubble {
+ if b := acquire(); b != nil {
+ return &Bubble{b}
+ }
+ return nil
+}
+
+// Release releases the reference to the bubble,
+// allowing it to become idle again.
+func (b *Bubble) Release() {
+ if b == nil {
+ return
+ }
+ release(b.b)
+ b.b = nil
+}
+
+// Run executes f in the bubble.
+// The current goroutine must not be part of a bubble.
+func (b *Bubble) Run(f func()) {
+ if b == nil {
+ f()
+ } else {
+ inBubble(b.b, f)
+ }
+}
unlock(&gp.syncGroup.mu)
return true
}
+
+//go:linkname synctest_acquire internal/synctest.acquire
+func synctest_acquire() any {
+ if sg := getg().syncGroup; sg != nil {
+ sg.incActive()
+ return sg
+ }
+ return nil
+}
+
+//go:linkname synctest_release internal/synctest.release
+func synctest_release(sg any) {
+ sg.(*synctestGroup).decActive()
+}
+
+//go:linkname synctest_inBubble internal/synctest.inBubble
+func synctest_inBubble(sg any, f func()) {
+ gp := getg()
+ if gp.syncGroup != nil {
+ panic("goroutine is already bubbled")
+ }
+ gp.syncGroup = sg.(*synctestGroup)
+ defer func() {
+ gp.syncGroup = nil
+ }()
+ f()
+}
package js
-import "sync"
+import (
+ "internal/synctest"
+ "sync"
+)
var (
funcsMu sync.Mutex
// Func is a wrapped Go function to be called by JavaScript.
type Func struct {
- Value // the JavaScript function that invokes the Go function
- id uint32
+ Value // the JavaScript function that invokes the Go function
+ bubble *synctest.Bubble
+ id uint32
}
// FuncOf returns a function to be used by JavaScript.
funcsMu.Lock()
id := nextFuncID
nextFuncID++
+ bubble := synctest.Acquire()
+ if bubble != nil {
+ origFn := fn
+ fn = func(this Value, args []Value) any {
+ var r any
+ bubble.Run(func() {
+ r = origFn(this, args)
+ })
+ return r
+ }
+ }
funcs[id] = fn
funcsMu.Unlock()
return Func{
- id: id,
- Value: jsGo.Call("_makeFuncWrapper", id),
+ id: id,
+ bubble: bubble,
+ Value: jsGo.Call("_makeFuncWrapper", id),
}
}
// The function must not be invoked after calling Release.
// It is allowed to call Release while the function is still running.
func (c Func) Release() {
+ c.bubble.Release()
funcsMu.Lock()
delete(funcs, c.id)
funcsMu.Unlock()