Skip to content

Commit

Permalink
fix: cannot lazily link against GLS accessors (#127)
Browse files Browse the repository at this point in the history
In order to be able to lazily link against GLS accessors, these need to
be declared as variables instead of functions. This allows creation of
bindings on the other side that can be conditionally satisffied by the
existence of initialized declarations somewhere else.

Adds an integration test to demonstrate it is possible to build programs
with and without the runtime modification using this technique.

Enables: DataDog/dd-trace-go#2761

---------

Signed-off-by: Eliott Bouhana <eliott.bouhana@datadoghq.com>
Signed-off-by: github-actions on behalf of eliottness <github-actions@github.com>
Co-authored-by: Eliott Bouhana <eliott.bouhana@datadoghq.com>
Co-authored-by: github-actions on behalf of eliottness <github-actions@github.com>
  • Loading branch information
3 people committed Jul 15, 2024
1 parent 66b7af5 commit 7d606d2
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 4 deletions.
45 changes: 45 additions & 0 deletions _integration-tests/gls/access.go
Original file line number Diff line number Diff line change
@@ -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()
}
86 changes: 86 additions & 0 deletions _integration-tests/gls/access_test.go
Original file line number Diff line number Diff line change
@@ -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())
}
})
}
49 changes: 49 additions & 0 deletions _integration-tests/gls/access_unix_test.go
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 2 additions & 2 deletions internal/injector/builtin/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions internal/injector/builtin/yaml/stdlib/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 7d606d2

Please sign in to comment.