Skip to content

Commit

Permalink
internal: add FeatureCache
Browse files Browse the repository at this point in the history
Add a generic cache of FeatureTest for cases where we have a large
number of feature tests, such as when probing (program type, helper).
  • Loading branch information
lmb committed Dec 6, 2022
1 parent abd0c06 commit f17b01b
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 74 deletions.
38 changes: 0 additions & 38 deletions features/errors.go

This file was deleted.

41 changes: 17 additions & 24 deletions features/prog.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package features

import (
"errors"
"fmt"
"os"
"sync"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
Expand Down Expand Up @@ -144,12 +144,14 @@ type helperKey struct {
helper asm.BuiltinFunc
}

var helperCache = struct {
sync.Mutex
results map[helperKey]error
}{
results: make(map[helperKey]error),
}
var helperCache = internal.NewFeatureCache(func(key helperKey) *internal.FeatureTest {
return &internal.FeatureTest{
Name: fmt.Sprintf("%s for program type %s", key.helper, key.typ),
Fn: func() error {
return haveProgramHelper(key.typ, key.helper)
},
}
})

// HaveProgramHelper probes the running kernel for the availability of the specified helper
// function to a specified program type.
Expand All @@ -164,28 +166,15 @@ var helperCache = struct {
// Only `nil` and `ebpf.ErrNotSupported` are conclusive.
//
// Probe results are cached and persist throughout any process capability changes.
func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) (err error) {
defer func() {
// This closure modifies a named return variable.
err = wrapProbeErrors(err)
}()

func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
if helper > helper.Max() {
return os.ErrInvalid
}

return haveProgramHelper(pt, helper)
return helperCache.Result(helperKey{pt, helper})
}

func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
helperCache.Lock()
defer helperCache.Unlock()

key := helperKey{pt, helper}
if err, ok := helperCache.results[key]; ok {
return err
}

if err := HaveProgramType(pt); err != nil {
return err
}
Expand All @@ -211,7 +200,12 @@ func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
spec.Flags = unix.BPF_F_SLEEPABLE
}

err := probeProgram(spec)
prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{
LogDisabled: true,
})
if err == nil {
prog.Close()
}

switch {
// EACCES occurs when attempting to create a program probe with a helper
Expand All @@ -228,6 +222,5 @@ func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
err = ebpf.ErrNotSupported
}

helperCache.results[key] = err
return err
}
8 changes: 0 additions & 8 deletions features/prog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,3 @@ func TestHaveProgramHelper(t *testing.T) {

}
}

func TestHaveProgramHelperUnsupported(t *testing.T) {
testutils.SkipIfNotSupported(t, HaveProgramType(ebpf.SocketFilter))

if err := haveProgramHelper(ebpf.SocketFilter, asm.BuiltinFunc(math.MaxInt32)); !errors.Is(err, ebpf.ErrNotSupported) {
t.Fatalf("Expected ebpf.ErrNotSupported but was: %v", err)
}
}
54 changes: 50 additions & 4 deletions internal/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,12 @@ func (ft *FeatureTest) execute() error {
}

if errors.Is(err, ErrNotSupported) {
v, err := NewVersion(ft.Version)
if err != nil {
return fmt.Errorf("feature %s: %w", ft.Name, err)
var v Version
if ft.Version != "" {
v, err = NewVersion(ft.Version)
if err != nil {
return fmt.Errorf("feature %s: %w", ft.Name, err)
}
}

ft.done = true
Expand All @@ -120,7 +123,8 @@ func (ft *FeatureTest) execute() error {

// FeatureMatrix groups multiple related feature tests into a map.
//
// Useful when there is a small number of discrete features.
// Useful when there is a small number of discrete features which are known
// at compile time.
//
// It must not be modified concurrently with calling [FeatureMatrix.Result].
type FeatureMatrix[K comparable] map[K]*FeatureTest
Expand All @@ -136,3 +140,45 @@ func (fm FeatureMatrix[K]) Result(key K) error {

return ft.execute()
}

// FeatureCache caches a potentially unlimited number of feature probes.
//
// Useful when there is a high cardinality for a feature test.
type FeatureCache[K comparable] struct {
mu sync.RWMutex
newTest func(K) *FeatureTest
features map[K]*FeatureTest
}

func NewFeatureCache[K comparable](newTest func(K) *FeatureTest) *FeatureCache[K] {
return &FeatureCache[K]{
newTest: newTest,
features: make(map[K]*FeatureTest),
}
}

func (fc *FeatureCache[K]) Result(key K) error {
// NB: Executing the feature test happens without fc.mu taken.
return fc.retrieve(key).execute()
}

func (fc *FeatureCache[K]) retrieve(key K) *FeatureTest {
fc.mu.RLock()
ft := fc.features[key]
fc.mu.RUnlock()

if ft != nil {
return ft
}

fc.mu.Lock()
defer fc.mu.Unlock()

if ft := fc.features[key]; ft != nil {
return ft
}

ft = fc.newTest(key)
fc.features[key] = ft
return ft
}

0 comments on commit f17b01b

Please sign in to comment.