feature.go 4.3 KB

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