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  }