]> Cypherpunks repositories - gostls13.git/commitdiff
runtime, internal/synctest, syscall/js: keep bubble membership in syscalls
authorDamien Neil <dneil@google.com>
Fri, 22 Nov 2024 01:32:22 +0000 (17:32 -0800)
committerGopher Robot <gobot@golang.org>
Fri, 22 Nov 2024 16:21:27 +0000 (16:21 +0000)
Propagate synctest bubble membership through syscall/js.Func
functions. Avoids panics from cross-bubble channel operations
in js syscalls.

Fixes #70512

Change-Id: Idbd9f95da8bc4f055a635dfac041359f848dad1a
Reviewed-on: https://go-review.googlesource.com/c/go/+/631055
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>

src/go/build/deps_test.go
src/internal/synctest/synctest.go
src/runtime/synctest.go
src/syscall/js/func.go

index 90b1eed00ed01d3ff91ec8d8f94fb62715fc96de..cc7f4df7f388eaaff4513a18b800d011bb3d8792 100644 (file)
@@ -131,8 +131,12 @@ var depsRules = `
 
        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
@@ -658,10 +662,6 @@ var depsRules = `
        FMT, DEBUG, flag, runtime/trace, internal/sysinfo, math/rand
        < testing;
 
-       RUNTIME
-       < internal/synctest
-       < testing/synctest;
-
        log/slog, testing
        < testing/slogtest;
 
index 7714a82bf495470da80d8c0be27e549c172de4ee..19190d30f1602772a39ca5e677bcf28db959e847 100644 (file)
@@ -16,3 +16,48 @@ func Run(f func())
 
 //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)
+       }
+}
index 0fd5e7873e4f5df12a98956568383613a51f8574..09748d5c1cd23c50c8f4a30c08a3cebed5454577 100644 (file)
@@ -270,3 +270,30 @@ func synctestwait_c(gp *g, _ unsafe.Pointer) bool {
        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()
+}
index 53a4d79a95e322a96764187687e23e0bef9ed857..23530170ffcd4c17d0bebe406151d6a128ee34f6 100644 (file)
@@ -6,7 +6,10 @@
 
 package js
 
-import "sync"
+import (
+       "internal/synctest"
+       "sync"
+)
 
 var (
        funcsMu    sync.Mutex
@@ -16,8 +19,9 @@ var (
 
 // 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.
@@ -42,11 +46,23 @@ func FuncOf(fn func(this Value, args []Value) any) Func {
        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),
        }
 }
 
@@ -54,6 +70,7 @@ func FuncOf(fn func(this Value, args []Value) any) Func {
 // 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()