diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index cce7fce5..1b8d467a 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -2,9 +2,9 @@ package main import "fmt" -const VERSION = "1.0.21" -const REVISION = "60d0e314753d95e7d630a307ef991ac7bc21807e+1" -const NUMBER = 173 +const VERSION = "1.0.22" +const REVISION = "f34bf4ca38af8b5adcf36b67de5ea6fb853e4823+1" +const NUMBER = 174 func getRevision() string { revSuffix := "" diff --git a/runtime/core/version.go b/runtime/core/version.go index 767b39a1..f8c9b730 100644 --- a/runtime/core/version.go +++ b/runtime/core/version.go @@ -6,9 +6,9 @@ import ( "os" ) -const VERSION = "1.0.21" -const REVISION = "60d0e314753d95e7d630a307ef991ac7bc21807e+1" -const NUMBER = 173 +const VERSION = "1.0.22" +const REVISION = "f34bf4ca38af8b5adcf36b67de5ea6fb853e4823+1" +const NUMBER = 174 // these fields will be filled by compiler const XGO_VERSION = "" @@ -24,7 +24,7 @@ func init() { } err := checkVersion() if err != nil { - fmt.Fprintf(os.Stderr, "WARNING: xgo toolchain: %v\nnote: this message can be turned off by setting %s=true\n", err, XGO_CHECK_TOOLCHAIN_VERSION) + fmt.Fprintf(os.Stderr, "WARNING: xgo toolchain: %v\nnote: this message can be turned off by setting %s=false\n", err, XGO_CHECK_TOOLCHAIN_VERSION) } } diff --git a/runtime/test/trace/main.go b/runtime/test/trace/main.go deleted file mode 100644 index 8a783536..00000000 --- a/runtime/test/trace/main.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/xhd2015/xgo/runtime/trace" -) - -func init() { - trace.Enable() -} - -func main() { - A() - B() - C() -} - -func A() { - fmt.Printf("A\n") -} - -func B() { - fmt.Printf("B\n") - C() -} -func C() { - fmt.Printf("C\n") -} diff --git a/runtime/test/trace/trace_mock_test.go b/runtime/test/trace/trace_mock_test.go new file mode 100644 index 00000000..7c03029f --- /dev/null +++ b/runtime/test/trace/trace_mock_test.go @@ -0,0 +1,61 @@ +package trace + +import ( + "testing" + + "github.com/xhd2015/xgo/runtime/mock" + "github.com/xhd2015/xgo/runtime/test/util" + "github.com/xhd2015/xgo/runtime/trace" +) + +func TestMockedFuncShouldShowInTrace(t *testing.T) { + var root *trace.Root + trace.Options().OnComplete(func(r *trace.Root) { + root = r + }).Collect(func() { + mock.Patch(A, func() string { + return "mock_A" + }) + h() + }) + data, err := trace.MarshalAnyJSON(root.Export()) + if err != nil { + t.Fatal(err) + } + + // t.Logf("trace: %s", data) + + expectTraceSequence := []string{ + "{", + `"Name":"h",`, + `"Name":"A",`, + `mock_A`, + `"Name":"B",`, + `"Name":"C",`, + `"Name":"C",`, + "}", + } + err = util.CheckSequence(string(data), expectTraceSequence) + if err != nil { + t.Fatalf("%v", err) + } +} + +func h() { + A() + B() + C() +} + +func A() string { + return "A" +} + +func B() string { + C() + return "B" +} + +func C() string { + return "C" +} diff --git a/runtime/test/trace_panic_peek/trace_panic_peek_test.go b/runtime/test/trace_panic_peek/trace_panic_peek_test.go index b267a0ed..0655fd0b 100644 --- a/runtime/test/trace_panic_peek/trace_panic_peek_test.go +++ b/runtime/test/trace_panic_peek/trace_panic_peek_test.go @@ -4,9 +4,9 @@ import ( "bytes" "fmt" "io" - "strings" "testing" + "github.com/xhd2015/xgo/runtime/test/util" "github.com/xhd2015/xgo/runtime/trace" ) @@ -45,7 +45,7 @@ func TestTracePanicPeek(t *testing.T) { `"Error":"Work panic: doWork panic",`, "}", } - err := CheckSequence(string(traceData), expectTraceSequence) + err := util.CheckSequence(string(traceData), expectTraceSequence) if err != nil { t.Fatalf("%v", err) } @@ -74,34 +74,3 @@ func doWork(w io.Writer) { fmt.Fprintf(w, "call: doWork\n") panic("doWork panic") } - -func indexSequence(s string, sequence []string, begin bool) (int, int) { - if len(sequence) == 0 { - return 0, 0 - } - firstIdx := -1 - base := 0 - for i, seq := range sequence { - idx := strings.Index(s, seq) - if idx < 0 { - return i, -1 - } - if firstIdx < 0 { - firstIdx = idx - } - s = s[idx+len(seq):] - base += idx + len(seq) - } - if begin { - return -1, firstIdx - } - return -1, base -} - -func CheckSequence(output string, sequence []string) error { - missing, idx := indexSequence(output, sequence, false) - if idx < 0 { - return fmt.Errorf("sequence at %d: missing %q", missing, sequence[missing]) - } - return nil -} diff --git a/runtime/test/util/util.go b/runtime/test/util/util.go new file mode 100644 index 00000000..fa3d1897 --- /dev/null +++ b/runtime/test/util/util.go @@ -0,0 +1,37 @@ +package util + +import ( + "fmt" + "strings" +) + +func indexSequence(s string, sequence []string, begin bool) (int, int) { + if len(sequence) == 0 { + return 0, 0 + } + firstIdx := -1 + base := 0 + for i, seq := range sequence { + idx := strings.Index(s, seq) + if idx < 0 { + return i, -1 + } + if firstIdx < 0 { + firstIdx = idx + } + s = s[idx+len(seq):] + base += idx + len(seq) + } + if begin { + return -1, firstIdx + } + return -1, base +} + +func CheckSequence(output string, sequence []string) error { + missing, idx := indexSequence(output, sequence, false) + if idx < 0 { + return fmt.Errorf("sequence at %d: missing %q", missing, sequence[missing]) + } + return nil +} diff --git a/runtime/trace/trace.go b/runtime/trace/trace.go index cb884a64..4b69464a 100644 --- a/runtime/trace/trace.go +++ b/runtime/trace/trace.go @@ -129,7 +129,7 @@ func setupInterceptor() { return } // collect trace - trap.AddInterceptor(&trap.Interceptor{ + trap.AddInterceptorHead(&trap.Interceptor{ Pre: func(ctx context.Context, f *core.FuncInfo, args core.Object, results core.Object) (interface{}, error) { key := uintptr(__xgo_link_getcurg()) localOptStack, ok := collectingMap.Load(key) diff --git a/runtime/trap/interceptor.go b/runtime/trap/interceptor.go index db53e5bd..172488ff 100644 --- a/runtime/trap/interceptor.go +++ b/runtime/trap/interceptor.go @@ -43,16 +43,46 @@ type Interceptor struct { Post func(ctx context.Context, f *core.FuncInfo, args core.Object, result core.Object, data interface{}) error } -var interceptors []*Interceptor +type interceptorManager struct { + head []*Interceptor + tail []*Interceptor +} + +func (c *interceptorManager) copy() *interceptorManager { + if c == nil { + return nil + } + head := make([]*Interceptor, len(c.head)) + tail := make([]*Interceptor, len(c.tail)) + copy(head, c.head) + copy(tail, c.tail) + return &interceptorManager{ + head: head, + tail: tail, + } +} + +var interceptors = &interceptorManager{} var localInterceptors sync.Map // goroutine ptr -> *interceptorGroup func AddInterceptor(interceptor *Interceptor) func() { + return addInterceptor(interceptor, false) +} + +func AddInterceptorHead(interceptor *Interceptor) func() { + return addInterceptor(interceptor, true) +} +func addInterceptor(interceptor *Interceptor, head bool) func() { ensureTrapInstall() if __xgo_link_init_finished() { - dispose, _ := addLocalInterceptor(interceptor, false) + dispose, _ := addLocalInterceptor(interceptor, false, head) return dispose } - interceptors = append(interceptors, interceptor) + if head { + interceptors.head = append(interceptors.head, interceptor) + } else { + interceptors.tail = append(interceptors.tail, interceptor) + } return func() { panic("global interceptor cannot be cancelled, if you want to cancel a global interceptor, use WithInterceptor") } @@ -69,7 +99,7 @@ func AddInterceptor(interceptor *Interceptor) func() { // even from init because it will be soon cleared // without causing concurrent issues. func WithInterceptor(interceptor *Interceptor, f func()) { - dispose, _ := addLocalInterceptor(interceptor, false) + dispose, _ := addLocalInterceptor(interceptor, false, false) defer dispose() f() } @@ -78,16 +108,39 @@ func WithInterceptor(interceptor *Interceptor, f func()) { // in current goroutine temporarily, it returns a function // that can be used to cancel the override. func WithOverride(interceptor *Interceptor, f func()) { - _, disposeGroup := addLocalInterceptor(interceptor, true) + _, disposeGroup := addLocalInterceptor(interceptor, true, false) defer disposeGroup() f() } func GetInterceptors() []*Interceptor { - return interceptors + return interceptors.getInterceptors() +} + +func (c *interceptorManager) getInterceptors() []*Interceptor { + return mergeInterceptors(c.tail, c.head) +} + +func mergeInterceptors(groups ...[]*Interceptor) []*Interceptor { + n := 0 + for _, g := range groups { + n += len(g) + } + list := make([]*Interceptor, 0, n) + for _, g := range groups { + list = append(list, g...) + } + return list } func GetLocalInterceptors() []*Interceptor { + g := getLocalInterceptorList() + if g == nil { + return nil + } + return g.getInterceptors() +} +func getLocalInterceptorList() *interceptorManager { group := getLocalInterceptorGroup() if group == nil { return nil @@ -118,32 +171,30 @@ func GetAllInterceptors() []*Interceptor { func getAllInterceptors() ([]*Interceptor, int) { group := getLocalInterceptorGroup() - var locals []*Interceptor + var localHead []*Interceptor + var localTail []*Interceptor var g int if group != nil { gi := group.currentGroupInterceptors() if gi != nil { g = group.currentGroup() if gi.override { - return gi.list, g + return gi.list.getInterceptors(), g } - locals = gi.list + localHead = gi.list.head + localTail = gi.list.tail } } - global := GetInterceptors() - if len(locals) == 0 { - return global, g - } - if len(global) == 0 { - return locals, g - } + globalHead := interceptors.head + globalTail := interceptors.tail + // run locals first(in reversed order) - return append(global[:len(global):len(global)], locals...), g + return mergeInterceptors(globalTail, localTail, globalHead, localHead), g } // returns a function to dispose the key // NOTE: if not called correctly,there might be memory leak -func addLocalInterceptor(interceptor *Interceptor, override bool) (removeInterceptor func(), removeGroup func()) { +func addLocalInterceptor(interceptor *Interceptor, override bool, head bool) (removeInterceptor func(), removeGroup func()) { ensureTrapInstall() key := uintptr(__xgo_link_getcurg()) list := &interceptorGroup{} @@ -156,7 +207,7 @@ func addLocalInterceptor(interceptor *Interceptor, override bool) (removeInterce list.enterNewGroup(override) } g := list.currentGroup() - list.appendToCurrentGroup(interceptor) + list.appendToCurrentGroup(interceptor, head) removedInterceptor := false // used to remove the local interceptor @@ -172,7 +223,12 @@ func addLocalInterceptor(interceptor *Interceptor, override bool) (removeInterce if curG != g { panic(fmt.Errorf("interceptor group changed: previous=%d, current=%d", g, curG)) } - interceptors := list.groups[g].list + manager := list.groups[g].list + + interceptors := manager.tail + if head { + interceptors = manager.head + } removedInterceptor = true var idx int = -1 for i, intc := range interceptors { @@ -189,7 +245,11 @@ func addLocalInterceptor(interceptor *Interceptor, override bool) (removeInterce interceptors[i] = interceptors[i+1] } interceptors = interceptors[:n-1] - list.groups[g].list = interceptors + if head { + manager.head = interceptors + } else { + manager.tail = interceptors + } } removedGroup := false @@ -218,16 +278,20 @@ type interceptorGroup struct { type interceptorList struct { override bool - list []*Interceptor + list *interceptorManager } -func (c *interceptorList) append(interceptor *Interceptor) { - c.list = append(c.list, interceptor) +func (c *interceptorList) append(interceptor *Interceptor, head bool) { + if head { + c.list.head = append(c.list.head, interceptor) + } else { + c.list.tail = append(c.list.tail, interceptor) + } } -func (c *interceptorGroup) appendToCurrentGroup(interceptor *Interceptor) { +func (c *interceptorGroup) appendToCurrentGroup(interceptor *Interceptor, head bool) { g := c.currentGroup() - c.groups[g].append(interceptor) + c.groups[g].append(interceptor, head) } func (c *interceptorGroup) groupsEmpty() bool { @@ -248,7 +312,7 @@ func (c *interceptorGroup) currentGroupInterceptors() *interceptorList { func (c *interceptorGroup) enterNewGroup(override bool) { c.groups = append(c.groups, &interceptorList{ override: override, - list: make([]*Interceptor, 0, 1), + list: &interceptorManager{}, }) } func (c *interceptorGroup) exitGroup() { diff --git a/runtime/trap/trap.go b/runtime/trap/trap.go index 131c3de0..11887f82 100644 --- a/runtime/trap/trap.go +++ b/runtime/trap/trap.go @@ -34,17 +34,14 @@ func init() { if isByPassing() { return } - interceptors := GetLocalInterceptors() - if len(interceptors) == 0 { + local := getLocalInterceptorList() + if local == nil || (len(local.head) == 0 && len(local.tail) == 0) { return } - copyInterceptors := make([]*Interceptor, len(interceptors)) - copy(copyInterceptors, interceptors) - // inherit interceptors of last group localInterceptors.Store(g, &interceptorGroup{ groups: []*interceptorList{{ - list: copyInterceptors, + list: local.copy(), }}, }) })