github.com/pingcap/failpoint@v0.0.0-20240412033321-fd0796e60f86/failpoints.go (about) 1 // Copyright 2019 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Copyright 2016 CoreOS, Inc. 16 // 17 // Licensed under the Apache License, Version 2.0 (the "License"); 18 // you may not use this file except in compliance with the License. 19 // You may obtain a copy of the License at 20 // 21 // http://www.apache.org/licenses/LICENSE-2.0 22 // 23 // Unless required by applicable law or agreed to in writing, software 24 // distributed under the License is distributed on an "AS IS" BASIS, 25 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 // See the License for the specific language governing permissions and 27 // limitations under the License. 28 29 package failpoint 30 31 import ( 32 "context" 33 "fmt" 34 "os" 35 "sort" 36 "strings" 37 "sync" 38 39 "github.com/pingcap/errors" 40 ) 41 42 // FpError is the internal error of failpoint 43 type FpError error 44 45 var ( 46 // ErrNotExist represents a failpoint can not be found by specified name 47 ErrNotExist FpError = fmt.Errorf("failpoint: failpoint does not exist") 48 // ErrDisabled represents a failpoint is be disabled 49 ErrDisabled FpError = fmt.Errorf("failpoint: failpoint is disabled") 50 // ErrNoContext returns by EvalContext when the context is nil 51 ErrNoContext FpError = fmt.Errorf("failpoint: no context") 52 // ErrNoHook returns by EvalContext when there is no hook in the context 53 ErrNoHook FpError = fmt.Errorf("failpoint: no hook") 54 // ErrFiltered represents a failpoint is filtered by a hook function 55 ErrFiltered FpError = fmt.Errorf("failpoint: filtered by hook") 56 // ErrNotAllowed represents a failpoint can not be executed this time 57 ErrNotAllowed FpError = fmt.Errorf("failpoint: not allowed") 58 ) 59 60 func init() { 61 failpoints.reg = make(map[string]*Failpoint) 62 if s := os.Getenv("GO_FAILPOINTS"); len(s) > 0 { 63 // format is <FAILPOINT>=<TERMS>[;<FAILPOINT>=<TERMS>;...] 64 for _, fp := range strings.Split(s, ";") { 65 fpTerms := strings.Split(fp, "=") 66 if len(fpTerms) != 2 { 67 fmt.Printf("bad failpoint %q\n", fp) 68 os.Exit(1) 69 } 70 err := Enable(fpTerms[0], fpTerms[1]) 71 if err != nil { 72 fmt.Printf("bad failpoint %s\n", err) 73 os.Exit(1) 74 } 75 } 76 } 77 if s := os.Getenv("GO_FAILPOINTS_HTTP"); len(s) > 0 { 78 if err := serve(s); err != nil { 79 fmt.Println(err) 80 os.Exit(1) 81 } 82 } 83 } 84 85 // Failpoints manages multiple failpoints 86 type Failpoints struct { 87 mu sync.RWMutex 88 reg map[string]*Failpoint 89 } 90 91 // Enable a failpoint on failpath 92 func (fps *Failpoints) Enable(failpath, inTerms string) error { 93 fps.mu.Lock() 94 defer fps.mu.Unlock() 95 96 if fps.reg == nil { 97 fps.reg = make(map[string]*Failpoint) 98 } 99 100 fp := fps.reg[failpath] 101 if fp == nil { 102 fp = &Failpoint{} 103 fps.reg[failpath] = fp 104 } 105 err := fp.Enable(inTerms) 106 if err != nil { 107 return errors.Wrapf(err, "error on %s", failpath) 108 } 109 return nil 110 } 111 112 // EnableWith enables and locks the failpoint, the lock prevents 113 // the failpoint to be evaluated. It invokes the action while holding 114 // the lock. It is useful when enables a panic failpoint 115 // and does some post actions before the failpoint being evaluated. 116 func (fps *Failpoints) EnableWith(failpath, inTerms string, action func() error) error { 117 fps.mu.Lock() 118 defer fps.mu.Unlock() 119 120 if fps.reg == nil { 121 fps.reg = make(map[string]*Failpoint) 122 } 123 124 fp := fps.reg[failpath] 125 if fp == nil { 126 fp = &Failpoint{} 127 fps.reg[failpath] = fp 128 } 129 err := fp.EnableWith(inTerms, action) 130 if err != nil { 131 return errors.Wrapf(err, "error on %s", failpath) 132 } 133 return nil 134 } 135 136 // Disable a failpoint on failpath 137 func (fps *Failpoints) Disable(failpath string) error { 138 fps.mu.Lock() 139 defer fps.mu.Unlock() 140 141 fp := fps.reg[failpath] 142 if fp == nil { 143 return errors.Wrapf(ErrNotExist, "error on %s", failpath) 144 } 145 fp.Disable() 146 return nil 147 } 148 149 // Status gives the current setting for the failpoint 150 func (fps *Failpoints) Status(failpath string) (string, error) { 151 fps.mu.RLock() 152 fp := fps.reg[failpath] 153 fps.mu.RUnlock() 154 if fp == nil { 155 return "", errors.Wrapf(ErrNotExist, "error on %s", failpath) 156 } 157 fp.mu.RLock() 158 t := fp.t 159 fp.mu.RUnlock() 160 if t == nil { 161 return "", errors.Wrapf(ErrDisabled, "error on %s", failpath) 162 } 163 return t.desc, nil 164 } 165 166 // List returns all the failpoints information 167 func (fps *Failpoints) List() []string { 168 fps.mu.RLock() 169 ret := make([]string, 0, len(failpoints.reg)) 170 for fp := range fps.reg { 171 ret = append(ret, fp) 172 } 173 fps.mu.RUnlock() 174 sort.Strings(ret) 175 return ret 176 } 177 178 // EvalContext evaluates a failpoint's value, and calls hook if the context is 179 // not nil and contains hook function. It will return the evaluated value and 180 // true if the failpoint is active. Always returns false if ctx is nil 181 // or context does not contains a hook function 182 func (fps *Failpoints) EvalContext(ctx context.Context, failpath string) (Value, error) { 183 if ctx == nil { 184 return nil, errors.Wrapf(ErrNoContext, "error on %s", failpath) 185 } 186 hook, ok := ctx.Value(failpointCtxKey).(Hook) 187 if !ok { 188 return nil, errors.Wrapf(ErrNoHook, "error on %s", failpath) 189 } 190 if !hook(ctx, failpath) { 191 return nil, errors.Wrapf(ErrFiltered, "error on %s", failpath) 192 } 193 val, err := fps.Eval(failpath) 194 if err != nil { 195 return nil, errors.Wrapf(err, "error on %s", failpath) 196 } 197 return val, nil 198 } 199 200 // Eval evaluates a failpoint's value, It will return the evaluated value and 201 // true if the failpoint is active 202 func (fps *Failpoints) Eval(failpath string) (Value, error) { 203 fps.mu.RLock() 204 fp, found := fps.reg[failpath] 205 fps.mu.RUnlock() 206 if !found { 207 return nil, ErrNotExist 208 } 209 210 val, err := fp.Eval() 211 if err != nil { 212 return nil, err 213 } 214 return val, nil 215 } 216 217 // failpoints is the default 218 var failpoints Failpoints 219 220 // Enable sets a failpoint to a given failpoint description. 221 func Enable(failpath, inTerms string) error { 222 return failpoints.Enable(failpath, inTerms) 223 } 224 225 // EnableWith enables and locks the failpoint, the lock prevents 226 // the failpoint to be evaluated. It invokes the action while holding 227 // the lock. It is useful when enables a panic failpoint 228 // and does some post actions before the failpoint being evaluated. 229 func EnableWith(failpath, inTerms string, action func() error) error { 230 return failpoints.EnableWith(failpath, inTerms, action) 231 } 232 233 // Disable stops a failpoint from firing. 234 func Disable(failpath string) error { 235 return failpoints.Disable(failpath) 236 } 237 238 // Status gives the current setting for the failpoint 239 func Status(failpath string) (string, error) { 240 return failpoints.Status(failpath) 241 } 242 243 // List returns all the failpoints information 244 func List() []string { 245 return failpoints.List() 246 } 247 248 // WithHook binds a hook to a new context which is based on the `ctx` parameter 249 func WithHook(ctx context.Context, hook Hook) context.Context { 250 return context.WithValue(ctx, failpointCtxKey, hook) 251 } 252 253 // EvalContext evaluates a failpoint's value, and calls hook if the context is 254 // not nil and contains hook function. It will return the evaluated value and 255 // true if the failpoint is active. Always returns false if ctx is nil 256 // or context does not contains hook function 257 func EvalContext(ctx context.Context, failpath string) (Value, error) { 258 val, err := failpoints.EvalContext(ctx, failpath) 259 // The package level EvalContext usaully be injected into the users 260 // code, in which case the error can not be handled by the generated 261 // code. We print the error here. 262 if err, ok := errors.Cause(err).(FpError); !ok && err != nil { 263 fmt.Println(err) 264 } 265 return val, err 266 } 267 268 // Eval evaluates a failpoint's value, It will return the evaluated value and 269 // nil err if the failpoint is active 270 func Eval(failpath string) (Value, error) { 271 val, err := failpoints.Eval(failpath) 272 if err, ok := errors.Cause(err).(FpError); !ok && err != nil { 273 fmt.Println(err) 274 } 275 return val, err 276 }