Skip to content

Commit a8eddaf

Browse files
neildgopherbot
authored andcommitted
runtime, internal/synctest, syscall/js: keep bubble membership in syscalls
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 <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Damien Neil <[email protected]>
1 parent 0070991 commit a8eddaf

File tree

4 files changed

+99
-10
lines changed

4 files changed

+99
-10
lines changed

src/go/build/deps_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,12 @@ var depsRules = `
131131
132132
unicode !< path;
133133
134+
RUNTIME
135+
< internal/synctest
136+
< testing/synctest;
137+
134138
# SYSCALL is RUNTIME plus the packages necessary for basic system calls.
135-
RUNTIME, unicode/utf8, unicode/utf16
139+
RUNTIME, unicode/utf8, unicode/utf16, internal/synctest
136140
< internal/syscall/windows/sysdll, syscall/js
137141
< syscall
138142
< internal/syscall/unix, internal/syscall/windows, internal/syscall/windows/registry
@@ -658,10 +662,6 @@ var depsRules = `
658662
FMT, DEBUG, flag, runtime/trace, internal/sysinfo, math/rand
659663
< testing;
660664
661-
RUNTIME
662-
< internal/synctest
663-
< testing/synctest;
664-
665665
log/slog, testing
666666
< testing/slogtest;
667667

src/internal/synctest/synctest.go

+45
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,48 @@ func Run(f func())
1616

1717
//go:linkname Wait
1818
func Wait()
19+
20+
//go:linkname acquire
21+
func acquire() any
22+
23+
//go:linkname release
24+
func release(any)
25+
26+
//go:linkname inBubble
27+
func inBubble(any, func())
28+
29+
// A Bubble is a synctest bubble.
30+
//
31+
// Not a public API. Used by syscall/js to propagate bubble membership through syscalls.
32+
type Bubble struct {
33+
b any
34+
}
35+
36+
// Acquire returns a reference to the current goroutine's bubble.
37+
// The bubble will not become idle until Release is called.
38+
func Acquire() *Bubble {
39+
if b := acquire(); b != nil {
40+
return &Bubble{b}
41+
}
42+
return nil
43+
}
44+
45+
// Release releases the reference to the bubble,
46+
// allowing it to become idle again.
47+
func (b *Bubble) Release() {
48+
if b == nil {
49+
return
50+
}
51+
release(b.b)
52+
b.b = nil
53+
}
54+
55+
// Run executes f in the bubble.
56+
// The current goroutine must not be part of a bubble.
57+
func (b *Bubble) Run(f func()) {
58+
if b == nil {
59+
f()
60+
} else {
61+
inBubble(b.b, f)
62+
}
63+
}

src/runtime/synctest.go

+27
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,30 @@ func synctestwait_c(gp *g, _ unsafe.Pointer) bool {
270270
unlock(&gp.syncGroup.mu)
271271
return true
272272
}
273+
274+
//go:linkname synctest_acquire internal/synctest.acquire
275+
func synctest_acquire() any {
276+
if sg := getg().syncGroup; sg != nil {
277+
sg.incActive()
278+
return sg
279+
}
280+
return nil
281+
}
282+
283+
//go:linkname synctest_release internal/synctest.release
284+
func synctest_release(sg any) {
285+
sg.(*synctestGroup).decActive()
286+
}
287+
288+
//go:linkname synctest_inBubble internal/synctest.inBubble
289+
func synctest_inBubble(sg any, f func()) {
290+
gp := getg()
291+
if gp.syncGroup != nil {
292+
panic("goroutine is already bubbled")
293+
}
294+
gp.syncGroup = sg.(*synctestGroup)
295+
defer func() {
296+
gp.syncGroup = nil
297+
}()
298+
f()
299+
}

src/syscall/js/func.go

+22-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
package js
88

9-
import "sync"
9+
import (
10+
"internal/synctest"
11+
"sync"
12+
)
1013

1114
var (
1215
funcsMu sync.Mutex
@@ -16,8 +19,9 @@ var (
1619

1720
// Func is a wrapped Go function to be called by JavaScript.
1821
type Func struct {
19-
Value // the JavaScript function that invokes the Go function
20-
id uint32
22+
Value // the JavaScript function that invokes the Go function
23+
bubble *synctest.Bubble
24+
id uint32
2125
}
2226

2327
// FuncOf returns a function to be used by JavaScript.
@@ -42,18 +46,31 @@ func FuncOf(fn func(this Value, args []Value) any) Func {
4246
funcsMu.Lock()
4347
id := nextFuncID
4448
nextFuncID++
49+
bubble := synctest.Acquire()
50+
if bubble != nil {
51+
origFn := fn
52+
fn = func(this Value, args []Value) any {
53+
var r any
54+
bubble.Run(func() {
55+
r = origFn(this, args)
56+
})
57+
return r
58+
}
59+
}
4560
funcs[id] = fn
4661
funcsMu.Unlock()
4762
return Func{
48-
id: id,
49-
Value: jsGo.Call("_makeFuncWrapper", id),
63+
id: id,
64+
bubble: bubble,
65+
Value: jsGo.Call("_makeFuncWrapper", id),
5066
}
5167
}
5268

5369
// Release frees up resources allocated for the function.
5470
// The function must not be invoked after calling Release.
5571
// It is allowed to call Release while the function is still running.
5672
func (c Func) Release() {
73+
c.bubble.Release()
5774
funcsMu.Lock()
5875
delete(funcs, c.id)
5976
funcsMu.Unlock()

0 commit comments

Comments
 (0)