mirror of
https://github.com/golang/go
synced 2024-11-08 12:36:13 -07:00
e083dc6307
This commit adds support for JavaScript callbacks back into WebAssembly. This is experimental API, just like the rest of the syscall/js package. The time package now also uses this mechanism to properly support timers without resorting to a busy loop. JavaScript code can call into the same entry point multiple times. The new RUN register is used to keep track of the program's run state. Possible values are: starting, running, paused and exited. If no goroutine is ready any more, the scheduler can put the program into the "paused" state and the WebAssembly code will stop running. When a callback occurs, the JavaScript code puts the callback data into a queue and then calls into WebAssembly to allow the Go code to continue running. Updates #18892 Updates #25506 Change-Id: Ib8701cfa0536d10d69bd541c85b0e2a754eb54fb Reviewed-on: https://go-review.googlesource.com/114197 Reviewed-by: Austin Clements <austin@google.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
236 lines
7.3 KiB
Go
236 lines
7.3 KiB
Go
// Copyright 2016 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build !js
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"internal/trace"
|
|
"io/ioutil"
|
|
rtrace "runtime/trace"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// stacks is a fake stack map populated for test.
|
|
type stacks map[uint64][]*trace.Frame
|
|
|
|
// add adds a stack with a single frame whose Fn field is
|
|
// set to the provided fname and returns a unique stack id.
|
|
func (s *stacks) add(fname string) uint64 {
|
|
if *s == nil {
|
|
*s = make(map[uint64][]*trace.Frame)
|
|
}
|
|
|
|
id := uint64(len(*s))
|
|
(*s)[id] = []*trace.Frame{{Fn: fname}}
|
|
return id
|
|
}
|
|
|
|
// TestGoroutineCount tests runnable/running goroutine counts computed by generateTrace
|
|
// remain in the valid range.
|
|
// - the counts must not be negative. generateTrace will return an error.
|
|
// - the counts must not include goroutines blocked waiting on channels or in syscall.
|
|
func TestGoroutineCount(t *testing.T) {
|
|
w := trace.NewWriter()
|
|
w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp]
|
|
w.Emit(trace.EvFrequency, 1) // [ticks per second]
|
|
|
|
var s stacks
|
|
|
|
// In this test, we assume a valid trace contains EvGoWaiting or EvGoInSyscall
|
|
// event for every blocked goroutine.
|
|
|
|
// goroutine 10: blocked
|
|
w.Emit(trace.EvGoCreate, 1, 10, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id]
|
|
w.Emit(trace.EvGoWaiting, 1, 10) // [timestamp, goroutine id]
|
|
|
|
// goroutine 20: in syscall
|
|
w.Emit(trace.EvGoCreate, 1, 20, s.add("pkg.f2"), s.add("main.f2"))
|
|
w.Emit(trace.EvGoInSyscall, 1, 20) // [timestamp, goroutine id]
|
|
|
|
// goroutine 30: runnable
|
|
w.Emit(trace.EvGoCreate, 1, 30, s.add("pkg.f3"), s.add("main.f3"))
|
|
|
|
w.Emit(trace.EvProcStart, 2, 0) // [timestamp, thread id]
|
|
|
|
// goroutine 40: runnable->running->runnable
|
|
w.Emit(trace.EvGoCreate, 1, 40, s.add("pkg.f4"), s.add("main.f4"))
|
|
w.Emit(trace.EvGoStartLocal, 1, 40) // [timestamp, goroutine id]
|
|
w.Emit(trace.EvGoSched, 1, s.add("main.f4")) // [timestamp, stack]
|
|
|
|
res, err := trace.Parse(w, "")
|
|
if err != nil {
|
|
t.Fatalf("failed to parse test trace: %v", err)
|
|
}
|
|
res.Stacks = s // use fake stacks.
|
|
|
|
params := &traceParams{
|
|
parsed: res,
|
|
endTime: int64(1<<63 - 1),
|
|
}
|
|
|
|
// Use the default viewerDataTraceConsumer but replace
|
|
// consumeViewerEvent to intercept the ViewerEvents for testing.
|
|
c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1)
|
|
c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) {
|
|
if ev.Name == "Goroutines" {
|
|
cnt := ev.Arg.(*goroutineCountersArg)
|
|
if cnt.Runnable+cnt.Running > 2 {
|
|
t.Errorf("goroutine count=%+v; want no more than 2 goroutines in runnable/running state", cnt)
|
|
}
|
|
t.Logf("read %+v %+v", ev, cnt)
|
|
}
|
|
}
|
|
|
|
// If the counts drop below 0, generateTrace will return an error.
|
|
if err := generateTrace(params, c); err != nil {
|
|
t.Fatalf("generateTrace failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGoroutineFilter(t *testing.T) {
|
|
// Test that we handle state changes to selected goroutines
|
|
// caused by events on goroutines that are not selected.
|
|
|
|
var s stacks
|
|
|
|
w := trace.NewWriter()
|
|
w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp]
|
|
w.Emit(trace.EvFrequency, 1) // [ticks per second]
|
|
|
|
// goroutine 10: blocked
|
|
w.Emit(trace.EvGoCreate, 1, 10, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id]
|
|
w.Emit(trace.EvGoWaiting, 1, 10) // [timestamp, goroutine id]
|
|
|
|
// goroutine 20: runnable->running->unblock 10
|
|
w.Emit(trace.EvGoCreate, 1, 20, s.add("pkg.f2"), s.add("main.f2"))
|
|
w.Emit(trace.EvGoStartLocal, 1, 20) // [timestamp, goroutine id]
|
|
w.Emit(trace.EvGoUnblockLocal, 1, 10, s.add("pkg.f2")) // [timestamp, goroutine id, stack]
|
|
w.Emit(trace.EvGoEnd, 1) // [timestamp]
|
|
|
|
// goroutine 10: runnable->running->block
|
|
w.Emit(trace.EvGoStartLocal, 1, 10) // [timestamp, goroutine id]
|
|
w.Emit(trace.EvGoBlock, 1, s.add("pkg.f3")) // [timestamp, stack]
|
|
|
|
res, err := trace.Parse(w, "")
|
|
if err != nil {
|
|
t.Fatalf("failed to parse test trace: %v", err)
|
|
}
|
|
res.Stacks = s // use fake stacks
|
|
|
|
params := &traceParams{
|
|
parsed: res,
|
|
endTime: int64(1<<63 - 1),
|
|
gs: map[uint64]bool{10: true},
|
|
}
|
|
|
|
c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1)
|
|
if err := generateTrace(params, c); err != nil {
|
|
t.Fatalf("generateTrace failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPreemptedMarkAssist(t *testing.T) {
|
|
w := trace.NewWriter()
|
|
w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp]
|
|
w.Emit(trace.EvFrequency, 1) // [ticks per second]
|
|
|
|
var s stacks
|
|
// goroutine 9999: running -> mark assisting -> preempted -> assisting -> running -> block
|
|
w.Emit(trace.EvGoCreate, 1, 9999, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id]
|
|
w.Emit(trace.EvGoStartLocal, 1, 9999) // [timestamp, goroutine id]
|
|
w.Emit(trace.EvGCMarkAssistStart, 1, s.add("main.f1")) // [timestamp, stack]
|
|
w.Emit(trace.EvGoPreempt, 1, s.add("main.f1")) // [timestamp, stack]
|
|
w.Emit(trace.EvGoStartLocal, 1, 9999) // [timestamp, goroutine id]
|
|
w.Emit(trace.EvGCMarkAssistDone, 1) // [timestamp]
|
|
w.Emit(trace.EvGoBlock, 1, s.add("main.f2")) // [timestamp, stack]
|
|
|
|
res, err := trace.Parse(w, "")
|
|
if err != nil {
|
|
t.Fatalf("failed to parse test trace: %v", err)
|
|
}
|
|
res.Stacks = s // use fake stacks
|
|
|
|
params := &traceParams{
|
|
parsed: res,
|
|
endTime: int64(1<<63 - 1),
|
|
}
|
|
|
|
c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1)
|
|
|
|
marks := 0
|
|
c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) {
|
|
if strings.Contains(ev.Name, "MARK ASSIST") {
|
|
marks++
|
|
}
|
|
}
|
|
if err := generateTrace(params, c); err != nil {
|
|
t.Fatalf("generateTrace failed: %v", err)
|
|
}
|
|
|
|
if marks != 2 {
|
|
t.Errorf("Got %v MARK ASSIST events, want %v", marks, 2)
|
|
}
|
|
}
|
|
|
|
func TestFoo(t *testing.T) {
|
|
prog0 := func() {
|
|
ctx, task := rtrace.NewTask(context.Background(), "ohHappyDay")
|
|
rtrace.Log(ctx, "", "log before task ends")
|
|
task.End()
|
|
rtrace.Log(ctx, "", "log after task ends") // log after task ends
|
|
}
|
|
if err := traceProgram(t, prog0, "TestFoo"); err != nil {
|
|
t.Fatalf("failed to trace the program: %v", err)
|
|
}
|
|
res, err := parseTrace()
|
|
if err != nil {
|
|
t.Fatalf("failed to parse the trace: %v", err)
|
|
}
|
|
annotRes, _ := analyzeAnnotations()
|
|
var task *taskDesc
|
|
for _, t := range annotRes.tasks {
|
|
if t.name == "ohHappyDay" {
|
|
task = t
|
|
break
|
|
}
|
|
}
|
|
if task == nil {
|
|
t.Fatal("failed to locate expected task event")
|
|
}
|
|
|
|
params := &traceParams{
|
|
parsed: res,
|
|
mode: modeTaskOriented,
|
|
startTime: task.firstTimestamp() - 1,
|
|
endTime: task.lastTimestamp() + 1,
|
|
tasks: []*taskDesc{task},
|
|
}
|
|
|
|
c := viewerDataTraceConsumer(ioutil.Discard, 0, 1<<63-1)
|
|
|
|
var logBeforeTaskEnd, logAfterTaskEnd bool
|
|
c.consumeViewerEvent = func(ev *ViewerEvent, _ bool) {
|
|
if ev.Name == "log before task ends" {
|
|
logBeforeTaskEnd = true
|
|
}
|
|
if ev.Name == "log after task ends" {
|
|
logAfterTaskEnd = true
|
|
}
|
|
}
|
|
if err := generateTrace(params, c); err != nil {
|
|
t.Fatalf("generateTrace failed: %v", err)
|
|
}
|
|
if !logBeforeTaskEnd {
|
|
t.Error("failed to find 'log before task ends'")
|
|
}
|
|
if !logAfterTaskEnd {
|
|
t.Error("failed to find 'log after task ends'")
|
|
}
|
|
|
|
}
|