diff --git a/_integration-tests/gls/access.go b/_integration-tests/gls/access.go new file mode 100644 index 00000000..96a5b1f0 --- /dev/null +++ b/_integration-tests/gls/access.go @@ -0,0 +1,45 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023-present Datadog, Inc. + +package gls + +/* +extern void cgoCallback(); +*/ +import "C" + +import ( + _ "runtime" // Provides go:linkname targets (if Orchestrion modifies) + _ "unsafe" // For go:linkname +) + +var ( + //go:linkname __dd_orchestrion_gls_get __dd_orchestrion_gls_get + __dd_orchestrion_gls_get func() any + + //go:linkname __dd_orchestrion_gls_set __dd_orchestrion_gls_set + __dd_orchestrion_gls_set func(any) + + get = func() any { return nil } + set = func(any) {} +) + +func init() { + if __dd_orchestrion_gls_get != nil { + get = __dd_orchestrion_gls_get + } + if __dd_orchestrion_gls_set != nil { + set = __dd_orchestrion_gls_set + } +} + +//export cgoCallback +func cgoCallback() { + set("I am inside a cgo callback") +} + +func cgoCall() { + C.cgoCallback() +} diff --git a/_integration-tests/gls/access_test.go b/_integration-tests/gls/access_test.go new file mode 100644 index 00000000..d263008a --- /dev/null +++ b/_integration-tests/gls/access_test.go @@ -0,0 +1,86 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023-present Datadog, Inc. + +package gls + +import ( + "runtime" + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +//dd:orchestrion-enabled +const orchestrionEnabled = false + +func TestSimple(t *testing.T) { + expected := "Hello, World!" + + set(expected) + actual := get() + + if orchestrionEnabled { + t.Log("Orchestrion IS enabled") + require.Equal(t, expected, actual) + } else { + t.Log("Orchestrion IS NOT enabled") + require.Nil(t, actual) + } +} + +// TestCGO tests that the GLS is correctly set even when the code comes from a cgo callback. +func TestCGO(t *testing.T) { + if !orchestrionEnabled { + t.Skip("Orchestrion is not enabled") + } + + expected := "I am inside a cgo callback" + set(nil) + cgoCall() + require.Equal(t, expected, get()) +} + +func TestConcurrency(t *testing.T) { + if !orchestrionEnabled { + t.Skip("Orchestrion is not enabled") + } + + nbSets := 5000 + nbGoRoutines := 300 + + var wg sync.WaitGroup + + wg.Add(nbGoRoutines) + for i := 0; i < nbGoRoutines; i++ { + go func() { + defer wg.Done() + for j := 0; j < nbSets; j++ { + set(j) + require.Equal(t, j, get()) + } + }() + } + + wg.Wait() +} + +func BenchmarkGLS(b *testing.B) { + if !orchestrionEnabled { + b.Skip("Orchestrion is not enabled") + } + + b.Run("Set", func(b *testing.B) { + for i := 0; i < b.N; i++ { + set(i) + } + }) + + b.Run("Get", func(b *testing.B) { + for i := 0; i < b.N; i++ { + runtime.KeepAlive(get()) + } + }) +} diff --git a/_integration-tests/gls/access_unix_test.go b/_integration-tests/gls/access_unix_test.go new file mode 100644 index 00000000..2cc0054c --- /dev/null +++ b/_integration-tests/gls/access_unix_test.go @@ -0,0 +1,49 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023-present Datadog, Inc. + +//go:build unix + +package gls + +import ( + "os" + "os/signal" + "syscall" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestSignal tests that the GLS is correctly set even when the code comes from a signal handler. +func TestSignal(t *testing.T) { + if !orchestrionEnabled { + t.Skip("Orchestrion is not enabled") + } + + expected := "I am inside a signal handler" + + set(nil) + + doneSigChan := make(chan struct{}, 1) + checkChan := make(chan struct{}, 1) + doneCheckChan := make(chan struct{}, 1) + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGUSR1) + + go func() { + <-sigChan + set(expected) + doneSigChan <- struct{}{} + + <-checkChan + require.Equal(t, expected, get()) + doneCheckChan <- struct{}{} + }() + + syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) + <-doneSigChan + checkChan <- struct{}{} + <-doneCheckChan +} diff --git a/internal/injector/builtin/generated.go b/internal/injector/builtin/generated.go index 1214943e..8376ba8e 100644 --- a/internal/injector/builtin/generated.go +++ b/internal/injector/builtin/generated.go @@ -525,7 +525,7 @@ var Aspects = [...]aspect.Aspect{ advice.AddStructField("__dd_gls", join.MustTypeName("any")), advice.AddBlankImport("unsafe"), advice.InjectDeclarations(code.MustTemplate( - "//go:linkname __dd_orchestrion_gls_get __dd_orchestrion_gls_get\nfunc __dd_orchestrion_gls_get() any {\n return getg().m.curg.__dd_gls\n}\n\n//go:linkname __dd_orchestrion_gls_set __dd_orchestrion_gls_set\nfunc __dd_orchestrion_gls_set(val any) {\n getg().m.curg.__dd_gls = val\n}", + "//go:linkname __dd_orchestrion_gls_get __dd_orchestrion_gls_get\nvar __dd_orchestrion_gls_get = func() any {\n return getg().m.curg.__dd_gls\n}\n\n//go:linkname __dd_orchestrion_gls_set __dd_orchestrion_gls_set\nvar __dd_orchestrion_gls_set = func(val any) {\n getg().m.curg.__dd_gls = val\n}", map[string]string{}, ), []string{}), }, @@ -595,4 +595,4 @@ var InjectedPaths = [...]string{ } // Checksum is a checksum of the built-in configuration which can be used to invalidate caches. -const Checksum = "sha512:rsdxI7L3776sVLhkOBAVy/OPJ9xpns5J4Fti5H6ceS8f5By+fXZYrbHPF7+WgMZvgtT1KwYf1wrAwgoheH5xCw==" +const Checksum = "sha512:jUoH2+06uMlhRMStdbAans2JcOlDDFuXIhBQlT2LzkS0QCqztstydjGL4tRDly8J+jEo4YvSu936I60FQkMuww==" diff --git a/internal/injector/builtin/yaml/stdlib/runtime.yml b/internal/injector/builtin/yaml/stdlib/runtime.yml index b0da0530..f2c466b6 100644 --- a/internal/injector/builtin/yaml/stdlib/runtime.yml +++ b/internal/injector/builtin/yaml/stdlib/runtime.yml @@ -23,13 +23,14 @@ aspects: type: any - add-blank-import: unsafe # Needed for go:linkname - inject-declarations: + # Reference: https://github.com/golang/go/blob/6d89b38ed86e0bfa0ddaba08dc4071e6bb300eea/src/runtime/HACKING.md?plain=1#L44-L54 template: |- //go:linkname __dd_orchestrion_gls_get __dd_orchestrion_gls_get - func __dd_orchestrion_gls_get() any { + var __dd_orchestrion_gls_get = func() any { return getg().m.curg.__dd_gls } //go:linkname __dd_orchestrion_gls_set __dd_orchestrion_gls_set - func __dd_orchestrion_gls_set(val any) { + var __dd_orchestrion_gls_set = func(val any) { getg().m.curg.__dd_gls = val }