github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/internal/feature.go (about) 1 package internal 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 ) 8 9 // ErrNotSupported indicates that a feature is not supported by the current kernel. 10 var ErrNotSupported = errors.New("not supported") 11 12 // UnsupportedFeatureError is returned by FeatureTest() functions. 13 type UnsupportedFeatureError struct { 14 // The minimum Linux mainline version required for this feature. 15 // Used for the error string, and for sanity checking during testing. 16 MinimumVersion Version 17 18 // The name of the feature that isn't supported. 19 Name string 20 } 21 22 func (ufe *UnsupportedFeatureError) Error() string { 23 if ufe.MinimumVersion.Unspecified() { 24 return fmt.Sprintf("%s not supported", ufe.Name) 25 } 26 return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion) 27 } 28 29 // Is indicates that UnsupportedFeatureError is ErrNotSupported. 30 func (ufe *UnsupportedFeatureError) Is(target error) bool { 31 return target == ErrNotSupported 32 } 33 34 // FeatureTest caches the result of a [FeatureTestFn]. 35 // 36 // Fields should not be modified after creation. 37 type FeatureTest struct { 38 // The name of the feature being detected. 39 Name string 40 // Version in the form Major.Minor[.Patch]. 41 Version string 42 // The feature test itself. 43 Fn FeatureTestFn 44 45 mu sync.RWMutex 46 done bool 47 result error 48 } 49 50 // FeatureTestFn is used to determine whether the kernel supports 51 // a certain feature. 52 // 53 // The return values have the following semantics: 54 // 55 // err == ErrNotSupported: the feature is not available 56 // err == nil: the feature is available 57 // err != nil: the test couldn't be executed 58 type FeatureTestFn func() error 59 60 // NewFeatureTest is a convenient way to create a single [FeatureTest]. 61 func NewFeatureTest(name, version string, fn FeatureTestFn) func() error { 62 ft := &FeatureTest{ 63 Name: name, 64 Version: version, 65 Fn: fn, 66 } 67 68 return ft.execute 69 } 70 71 // execute the feature test. 72 // 73 // The result is cached if the test is conclusive. 74 // 75 // See [FeatureTestFn] for the meaning of the returned error. 76 func (ft *FeatureTest) execute() error { 77 ft.mu.RLock() 78 result, done := ft.result, ft.done 79 ft.mu.RUnlock() 80 81 if done { 82 return result 83 } 84 85 ft.mu.Lock() 86 defer ft.mu.Unlock() 87 88 // The test may have been executed by another caller while we were 89 // waiting to acquire ft.mu. 90 if ft.done { 91 return ft.result 92 } 93 94 err := ft.Fn() 95 if err == nil { 96 ft.done = true 97 return nil 98 } 99 100 if errors.Is(err, ErrNotSupported) { 101 var v Version 102 if ft.Version != "" { 103 v, err = NewVersion(ft.Version) 104 if err != nil { 105 return fmt.Errorf("feature %s: %w", ft.Name, err) 106 } 107 } 108 109 ft.done = true 110 ft.result = &UnsupportedFeatureError{ 111 MinimumVersion: v, 112 Name: ft.Name, 113 } 114 115 return ft.result 116 } 117 118 // We couldn't execute the feature test to a point 119 // where it could make a determination. 120 // Don't cache the result, just return it. 121 return fmt.Errorf("detect support for %s: %w", ft.Name, err) 122 } 123 124 // FeatureMatrix groups multiple related feature tests into a map. 125 // 126 // Useful when there is a small number of discrete features which are known 127 // at compile time. 128 // 129 // It must not be modified concurrently with calling [FeatureMatrix.Result]. 130 type FeatureMatrix[K comparable] map[K]*FeatureTest 131 132 // Result returns the outcome of the feature test for the given key. 133 // 134 // It's safe to call this function concurrently. 135 func (fm FeatureMatrix[K]) Result(key K) error { 136 ft, ok := fm[key] 137 if !ok { 138 return fmt.Errorf("no feature probe for %v", key) 139 } 140 141 return ft.execute() 142 } 143 144 // FeatureCache caches a potentially unlimited number of feature probes. 145 // 146 // Useful when there is a high cardinality for a feature test. 147 type FeatureCache[K comparable] struct { 148 mu sync.RWMutex 149 newTest func(K) *FeatureTest 150 features map[K]*FeatureTest 151 } 152 153 func NewFeatureCache[K comparable](newTest func(K) *FeatureTest) *FeatureCache[K] { 154 return &FeatureCache[K]{ 155 newTest: newTest, 156 features: make(map[K]*FeatureTest), 157 } 158 } 159 160 func (fc *FeatureCache[K]) Result(key K) error { 161 // NB: Executing the feature test happens without fc.mu taken. 162 return fc.retrieve(key).execute() 163 } 164 165 func (fc *FeatureCache[K]) retrieve(key K) *FeatureTest { 166 fc.mu.RLock() 167 ft := fc.features[key] 168 fc.mu.RUnlock() 169 170 if ft != nil { 171 return ft 172 } 173 174 fc.mu.Lock() 175 defer fc.mu.Unlock() 176 177 if ft := fc.features[key]; ft != nil { 178 return ft 179 } 180 181 ft = fc.newTest(key) 182 fc.features[key] = ft 183 return ft 184 }