github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/test_stallers.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libkbfs
     6  
     7  import (
     8  	"math/rand"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/keybase/client/go/kbfs/data"
    13  	"github.com/keybase/client/go/kbfs/kbfsblock"
    14  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    15  	"github.com/keybase/client/go/kbfs/kbfsmd"
    16  	"github.com/keybase/client/go/kbfs/libcontext"
    17  	"github.com/keybase/client/go/kbfs/tlf"
    18  	"github.com/keybase/client/go/kbfs/tlfhandle"
    19  	"github.com/keybase/client/go/protocol/keybase1"
    20  	"golang.org/x/net/context"
    21  )
    22  
    23  type stallableOp string
    24  
    25  // StallableBlockOp defines an Op that is stallable using StallBlockOp
    26  type StallableBlockOp stallableOp
    27  
    28  // StallableMDOp defines an Op that is stallable using StallMDOp
    29  type StallableMDOp stallableOp
    30  
    31  // stallable Block Ops and MD Ops
    32  const (
    33  	StallableBlockGet StallableBlockOp = "Get"
    34  	StallableBlockPut StallableBlockOp = "Put"
    35  
    36  	StallableMDGetForTLF                    StallableMDOp = "GetForTLF"
    37  	StallableMDGetForTLFByTime              StallableMDOp = "GetForTLFByTime"
    38  	StallableMDGetLatestHandleForTLF        StallableMDOp = "GetLatestHandleForTLF"
    39  	StallableMDValidateLatestHandleNotFinal StallableMDOp = "ValidateLatestHandleNotFinal"
    40  	StallableMDGetUnmergedForTLF            StallableMDOp = "GetUnmergedForTLF"
    41  	StallableMDGetRange                     StallableMDOp = "GetRange"
    42  	StallableMDAfterGetRange                StallableMDOp = "AfterGetRange"
    43  	StallableMDGetUnmergedRange             StallableMDOp = "GetUnmergedRange"
    44  	StallableMDPut                          StallableMDOp = "Put"
    45  	StallableMDAfterPut                     StallableMDOp = "AfterPut"
    46  	StallableMDPutUnmerged                  StallableMDOp = "PutUnmerged"
    47  	StallableMDAfterPutUnmerged             StallableMDOp = "AfterPutUnmerged"
    48  	StallableMDPruneBranch                  StallableMDOp = "PruneBranch"
    49  	StallableMDResolveBranch                StallableMDOp = "ResolveBranch"
    50  )
    51  
    52  type stallKeyType uint64
    53  
    54  const stallKeyStallEverything stallKeyType = 0
    55  
    56  type naïveStallInfo struct {
    57  	onStalled               <-chan struct{}
    58  	unstall                 chan<- struct{}
    59  	oldBlockServer          BlockServer
    60  	oldMDOps                MDOps
    61  	oldJournalDelegateMDOps MDOps
    62  }
    63  
    64  // NaïveStaller is used to stall certain ops in BlockServer or
    65  // MDOps. Unlike StallBlockOp and StallMDOp which provides a way to
    66  // precisely control which particular op is stalled by passing in ctx
    67  // with corresponding stallKey, NaïveStaller simply stalls all
    68  // instances of specified op.
    69  type NaïveStaller struct {
    70  	config Config
    71  
    72  	mu             sync.RWMutex
    73  	blockOpsStalls map[StallableBlockOp]*naïveStallInfo
    74  	mdOpsStalls    map[StallableMDOp]*naïveStallInfo
    75  
    76  	// We are only supporting stalling one Op per kind at a time for now. If in
    77  	// the future a dsl test needs to stall different Ops, please see
    78  	// https://github.com/keybase/client/go/kbfs/pull/163 for an implementation.
    79  	blockStalled bool
    80  	mdStalled    bool
    81  }
    82  
    83  // NewNaïveStaller returns a new NaïveStaller
    84  func NewNaïveStaller(config Config) *NaïveStaller {
    85  	return &NaïveStaller{
    86  		config:         config,
    87  		blockOpsStalls: make(map[StallableBlockOp]*naïveStallInfo),
    88  		mdOpsStalls:    make(map[StallableMDOp]*naïveStallInfo),
    89  	}
    90  }
    91  
    92  func (s *NaïveStaller) getNaïveStallInfoForBlockOpOrBust(
    93  	stalledOp StallableBlockOp) *naïveStallInfo {
    94  	s.mu.RLock()
    95  	defer s.mu.RUnlock()
    96  	info, ok := s.blockOpsStalls[stalledOp]
    97  	if !ok {
    98  		panic("naïveStallInfo is not found." +
    99  			"This indicates incorrect use of NaïveStaller")
   100  	}
   101  	return info
   102  }
   103  
   104  func (s *NaïveStaller) getNaïveStallInfoForMDOpOrBust(
   105  	stalledOp StallableMDOp) *naïveStallInfo {
   106  	s.mu.RLock()
   107  	defer s.mu.RUnlock()
   108  	info, ok := s.mdOpsStalls[stalledOp]
   109  	if !ok {
   110  		panic("naïveStallInfo is not found." +
   111  			"This indicates incorrect use of NaïveStaller")
   112  	}
   113  	return info
   114  }
   115  
   116  // StallBlockOp wraps the internal BlockServer so that all subsequent stalledOp
   117  // will be stalled. This can be undone by calling UndoStallBlockOp.
   118  func (s *NaïveStaller) StallBlockOp(stalledOp StallableBlockOp, maxStalls int) {
   119  	s.mu.Lock()
   120  	defer s.mu.Unlock()
   121  	if s.blockStalled {
   122  		panic("incorrect use of NaïveStaller;" +
   123  			" only one stalled Op at a time is supported")
   124  	}
   125  	onStalledCh := make(chan struct{}, maxStalls)
   126  	unstallCh := make(chan struct{})
   127  	oldBlockServer := s.config.BlockServer()
   128  	s.config.SetBlockServer(&stallingBlockServer{
   129  		BlockServer: oldBlockServer,
   130  		stallOpName: stalledOp,
   131  		stallKey:    stallKeyStallEverything,
   132  		staller: staller{
   133  			stalled: onStalledCh,
   134  			unstall: unstallCh,
   135  		},
   136  	})
   137  	s.blockStalled = true
   138  	s.blockOpsStalls[stalledOp] = &naïveStallInfo{
   139  		onStalled:      onStalledCh,
   140  		unstall:        unstallCh,
   141  		oldBlockServer: oldBlockServer,
   142  	}
   143  }
   144  
   145  // StallMDOp wraps the internal MDOps so that all subsequent stalledOp
   146  // will be stalled. This can be undone by calling UndoStallMDOp.
   147  func (s *NaïveStaller) StallMDOp(stalledOp StallableMDOp, maxStalls int,
   148  	stallDelegate bool) {
   149  	s.mu.Lock()
   150  	defer s.mu.Unlock()
   151  	if s.mdStalled {
   152  		panic("incorrect use of NaïveStaller;" +
   153  			" only one stalled Op at a time is supported")
   154  	}
   155  	onStalledCh := make(chan struct{}, maxStalls)
   156  	unstallCh := make(chan struct{})
   157  	oldMDOps := s.config.MDOps()
   158  	var oldJDelegate MDOps
   159  	if jManager, err := GetJournalManager(s.config); err == nil && stallDelegate {
   160  		oldJDelegate = jManager.delegateMDOps
   161  		// Stall the delegate server as well
   162  		jManager.delegateMDOps = &stallingMDOps{
   163  			stallOpName: stalledOp,
   164  			stallKey:    stallKeyStallEverything,
   165  			staller: staller{
   166  				stalled: onStalledCh,
   167  				unstall: unstallCh,
   168  			},
   169  			delegate: jManager.delegateMDOps,
   170  		}
   171  		s.config.SetMDOps(jManager.mdOps())
   172  	} else {
   173  		s.config.SetMDOps(&stallingMDOps{
   174  			stallOpName: stalledOp,
   175  			stallKey:    stallKeyStallEverything,
   176  			staller: staller{
   177  				stalled: onStalledCh,
   178  				unstall: unstallCh,
   179  			},
   180  			delegate: oldMDOps,
   181  		})
   182  	}
   183  	s.mdStalled = true
   184  	s.mdOpsStalls[stalledOp] = &naïveStallInfo{
   185  		onStalled:               onStalledCh,
   186  		unstall:                 unstallCh,
   187  		oldMDOps:                oldMDOps,
   188  		oldJournalDelegateMDOps: oldJDelegate,
   189  	}
   190  }
   191  
   192  // WaitForStallBlockOp blocks until stalledOp is stalled. StallBlockOp should
   193  // have been called upon stalledOp, otherwise this would panic.
   194  func (s *NaïveStaller) WaitForStallBlockOp(stalledOp StallableBlockOp) {
   195  	<-s.getNaïveStallInfoForBlockOpOrBust(stalledOp).onStalled
   196  }
   197  
   198  // WaitForStallMDOp blocks until stalledOp is stalled. StallMDOp should
   199  // have been called upon stalledOp, otherwise this would panic.
   200  func (s *NaïveStaller) WaitForStallMDOp(stalledOp StallableMDOp) {
   201  	<-s.getNaïveStallInfoForMDOpOrBust(stalledOp).onStalled
   202  }
   203  
   204  // UnstallOneBlockOp unstalls exactly one stalled stalledOp. StallBlockOp
   205  // should have been called upon stalledOp, otherwise this would panic.
   206  func (s *NaïveStaller) UnstallOneBlockOp(stalledOp StallableBlockOp) {
   207  	s.getNaïveStallInfoForBlockOpOrBust(stalledOp).unstall <- struct{}{}
   208  }
   209  
   210  // UnstallOneMDOp unstalls exactly one stalled stalledOp. StallMDOp
   211  // should have been called upon stalledOp, otherwise this would panic.
   212  func (s *NaïveStaller) UnstallOneMDOp(stalledOp StallableMDOp) {
   213  	s.getNaïveStallInfoForMDOpOrBust(stalledOp).unstall <- struct{}{}
   214  }
   215  
   216  // UndoStallBlockOp reverts StallBlockOp so that future stalledOp are not
   217  // stalled anymore. It also unstalls any stalled stalledOp. StallBlockOp
   218  // should have been called upon stalledOp, otherwise this would panic.
   219  func (s *NaïveStaller) UndoStallBlockOp(stalledOp StallableBlockOp) {
   220  	ns := s.getNaïveStallInfoForBlockOpOrBust(stalledOp)
   221  	s.config.SetBlockServer(ns.oldBlockServer)
   222  	close(ns.unstall)
   223  	s.mu.Lock()
   224  	defer s.mu.Unlock()
   225  	s.blockStalled = false
   226  	delete(s.blockOpsStalls, stalledOp)
   227  }
   228  
   229  // UndoStallMDOp reverts StallMDOp so that future stalledOp are not
   230  // stalled anymore. It also unstalls any stalled stalledOp. StallMDOp
   231  // should have been called upon stalledOp, otherwise this would panic.
   232  func (s *NaïveStaller) UndoStallMDOp(stalledOp StallableMDOp) {
   233  	ns := s.getNaïveStallInfoForMDOpOrBust(stalledOp)
   234  	if jManager, err := GetJournalManager(s.config); err == nil &&
   235  		ns.oldJournalDelegateMDOps != nil {
   236  		jManager.delegateMDOps = ns.oldJournalDelegateMDOps
   237  	}
   238  	s.config.SetMDOps(ns.oldMDOps)
   239  	close(ns.unstall)
   240  	s.mu.Lock()
   241  	defer s.mu.Unlock()
   242  	s.mdStalled = false
   243  	delete(s.mdOpsStalls, stalledOp)
   244  }
   245  
   246  // StallBlockOp sets a wrapped BlockOps in config so that the specified Op, stalledOp,
   247  // is stalled. Caller should use the returned newCtx for subsequent operations
   248  // for the stall to be effective. onStalled is a channel to notify the caller
   249  // when the stall has happened. unstall is a channel for caller to unstall an
   250  // Op.
   251  func StallBlockOp(ctx context.Context, config Config,
   252  	stalledOp StallableBlockOp, maxStalls int) (
   253  	onStalled <-chan struct{}, unstall chan<- struct{}, newCtx context.Context) {
   254  	onStalledCh := make(chan struct{}, maxStalls)
   255  	unstallCh := make(chan struct{})
   256  	stallKey := newStallKey()
   257  	config.SetBlockServer(&stallingBlockServer{
   258  		BlockServer: config.BlockServer(),
   259  		stallOpName: stalledOp,
   260  		stallKey:    stallKey,
   261  		staller: staller{
   262  			stalled: onStalledCh,
   263  			unstall: unstallCh,
   264  		},
   265  	})
   266  	newCtx = libcontext.NewContextReplayable(ctx, func(ctx context.Context) context.Context {
   267  		return context.WithValue(ctx, stallKey, true)
   268  	})
   269  	return onStalledCh, unstallCh, newCtx
   270  }
   271  
   272  // StallMDOp sets a wrapped MDOps in config so that the specified Op,
   273  // stalledOp, is stalled. Caller should use the returned newCtx for subsequent
   274  // operations for the stall to be effective. onStalled is a channel to notify
   275  // the caller when the stall has happened. unstall is a channel for caller to
   276  // unstall an Op.
   277  func StallMDOp(ctx context.Context, config Config, stalledOp StallableMDOp,
   278  	maxStalls int) (
   279  	onStalled <-chan struct{}, unstall chan<- struct{}, newCtx context.Context) {
   280  	onStalledCh := make(chan struct{}, maxStalls)
   281  	unstallCh := make(chan struct{})
   282  	stallKey := newStallKey()
   283  	config.SetMDOps(&stallingMDOps{
   284  		stallOpName: stalledOp,
   285  		stallKey:    stallKey,
   286  		staller: staller{
   287  			stalled: onStalledCh,
   288  			unstall: unstallCh,
   289  		},
   290  		delegate: config.MDOps(),
   291  	})
   292  	newCtx = libcontext.NewContextReplayable(ctx, func(ctx context.Context) context.Context {
   293  		return context.WithValue(ctx, stallKey, true)
   294  	})
   295  	return onStalledCh, unstallCh, newCtx
   296  }
   297  
   298  func newStallKey() stallKeyType {
   299  	stallKey := stallKeyStallEverything
   300  	for stallKey == stallKeyStallEverything {
   301  		stallKey = stallKeyType(rand.Int63())
   302  	}
   303  	return stallKey
   304  }
   305  
   306  // staller is a pair of channels. Whenever something is to be
   307  // stalled, a value is sent on stalled (if not blocked), and then
   308  // unstall is waited on.
   309  type staller struct {
   310  	stalled chan<- struct{}
   311  	unstall <-chan struct{}
   312  }
   313  
   314  func maybeStall(ctx context.Context, opName stallableOp,
   315  	stallOpName stallableOp, stallKey stallKeyType,
   316  	staller staller) {
   317  	if opName != stallOpName {
   318  		return
   319  	}
   320  
   321  	if stallKey != stallKeyStallEverything {
   322  		if v, ok := ctx.Value(stallKey).(bool); !ok || !v {
   323  			return
   324  		}
   325  	}
   326  
   327  	select {
   328  	case staller.stalled <- struct{}{}:
   329  	default:
   330  	}
   331  	<-staller.unstall
   332  }
   333  
   334  // runWithContextCheck checks ctx.Done() before and after running action. If
   335  // either ctx.Done() check has error, ctx's error is returned. Otherwise,
   336  // action's returned value is returned.
   337  func runWithContextCheck(ctx context.Context, action func(ctx context.Context) error) error {
   338  	select {
   339  	case <-ctx.Done():
   340  		return ctx.Err()
   341  	default:
   342  	}
   343  	err := action(ctx)
   344  	select {
   345  	case <-ctx.Done():
   346  		return ctx.Err()
   347  	default:
   348  	}
   349  	return err
   350  }
   351  
   352  // stallingBlockServer is an implementation of BlockServer whose
   353  // operations sometimes stall. In particular, if the operation name
   354  // matches stallOpName, and ctx.Value(stallKey) is a key in the
   355  // corresponding staller is used to stall the operation.
   356  type stallingBlockServer struct {
   357  	BlockServer
   358  	stallOpName StallableBlockOp
   359  	// stallKey is a key for switching on/off stalling. If it's present in ctx,
   360  	// and equal to `true`, the operation is stalled. This allows us to use the
   361  	// ctx to control stallings
   362  	stallKey stallKeyType
   363  	staller  staller
   364  }
   365  
   366  var _ BlockServer = (*stallingBlockServer)(nil)
   367  
   368  func (f *stallingBlockServer) maybeStall(ctx context.Context, opName StallableBlockOp) {
   369  	maybeStall(ctx, stallableOp(opName), stallableOp(f.stallOpName),
   370  		f.stallKey, f.staller)
   371  }
   372  
   373  func (f *stallingBlockServer) Get(
   374  	ctx context.Context, tlfID tlf.ID, id kbfsblock.ID,
   375  	bctx kbfsblock.Context, cacheType DiskBlockCacheType) (
   376  	buf []byte, serverHalf kbfscrypto.BlockCryptKeyServerHalf, err error) {
   377  	f.maybeStall(ctx, StallableBlockGet)
   378  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   379  		var errGet error
   380  		buf, serverHalf, errGet = f.BlockServer.Get(
   381  			ctx, tlfID, id, bctx, cacheType)
   382  		return errGet
   383  	})
   384  	return buf, serverHalf, err
   385  }
   386  
   387  func (f *stallingBlockServer) Put(
   388  	ctx context.Context, tlfID tlf.ID, id kbfsblock.ID,
   389  	bctx kbfsblock.Context, buf []byte,
   390  	serverHalf kbfscrypto.BlockCryptKeyServerHalf,
   391  	cacheType DiskBlockCacheType) error {
   392  	f.maybeStall(ctx, StallableBlockPut)
   393  	return runWithContextCheck(ctx, func(ctx context.Context) error {
   394  		return f.BlockServer.Put(
   395  			ctx, tlfID, id, bctx, buf, serverHalf, cacheType)
   396  	})
   397  }
   398  
   399  // stallingMDOps is an implementation of MDOps whose operations
   400  // sometimes stall. In particular, if the operation name matches
   401  // stallOpName, and ctx.Value(stallKey) is a key in the corresponding
   402  // staller is used to stall the operation.
   403  type stallingMDOps struct {
   404  	stallOpName StallableMDOp
   405  	// stallKey is a key for switching on/off stalling. If it's present in ctx,
   406  	// and equal to `true`, the operation is stalled. This allows us to use the
   407  	// ctx to control stallings
   408  	stallKey stallKeyType
   409  	staller  staller
   410  	delegate MDOps
   411  }
   412  
   413  var _ MDOps = (*stallingMDOps)(nil)
   414  
   415  func (m *stallingMDOps) maybeStall(ctx context.Context, opName StallableMDOp) {
   416  	maybeStall(ctx, stallableOp(opName), stallableOp(m.stallOpName),
   417  		m.stallKey, m.staller)
   418  }
   419  
   420  func (m *stallingMDOps) GetIDForHandle(
   421  	ctx context.Context, handle *tlfhandle.Handle) (tlfID tlf.ID, err error) {
   422  	return m.delegate.GetIDForHandle(ctx, handle)
   423  }
   424  
   425  func (m *stallingMDOps) GetForTLF(ctx context.Context, id tlf.ID,
   426  	lockBeforeGet *keybase1.LockID) (md ImmutableRootMetadata, err error) {
   427  	m.maybeStall(ctx, StallableMDGetForTLF)
   428  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   429  		var errGetForTLF error
   430  		md, errGetForTLF = m.delegate.GetForTLF(ctx, id, lockBeforeGet)
   431  		return errGetForTLF
   432  	})
   433  	return md, err
   434  }
   435  
   436  func (m *stallingMDOps) GetForTLFByTime(
   437  	ctx context.Context, id tlf.ID, serverTime time.Time) (
   438  	md ImmutableRootMetadata, err error) {
   439  	m.maybeStall(ctx, StallableMDGetForTLFByTime)
   440  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   441  		var errGetForTLF error
   442  		md, errGetForTLF = m.delegate.GetForTLFByTime(ctx, id, serverTime)
   443  		return errGetForTLF
   444  	})
   445  	return md, err
   446  }
   447  
   448  func (m *stallingMDOps) GetLatestHandleForTLF(ctx context.Context, id tlf.ID) (
   449  	h tlf.Handle, err error) {
   450  	m.maybeStall(ctx, StallableMDGetLatestHandleForTLF)
   451  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   452  		var errGetLatestHandleForTLF error
   453  		h, errGetLatestHandleForTLF = m.delegate.GetLatestHandleForTLF(
   454  			ctx, id)
   455  		return errGetLatestHandleForTLF
   456  	})
   457  	return h, err
   458  }
   459  
   460  func (m *stallingMDOps) ValidateLatestHandleNotFinal(
   461  	ctx context.Context, h *tlfhandle.Handle) (b bool, err error) {
   462  	m.maybeStall(ctx, StallableMDValidateLatestHandleNotFinal)
   463  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   464  		var errValidateLatestHandleNotFinal error
   465  		b, errValidateLatestHandleNotFinal =
   466  			m.delegate.ValidateLatestHandleNotFinal(ctx, h)
   467  		return errValidateLatestHandleNotFinal
   468  	})
   469  	return b, err
   470  }
   471  
   472  func (m *stallingMDOps) GetUnmergedForTLF(ctx context.Context, id tlf.ID,
   473  	bid kbfsmd.BranchID) (md ImmutableRootMetadata, err error) {
   474  	m.maybeStall(ctx, StallableMDGetUnmergedForTLF)
   475  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   476  		var errGetUnmergedForTLF error
   477  		md, errGetUnmergedForTLF = m.delegate.GetUnmergedForTLF(ctx, id, bid)
   478  		return errGetUnmergedForTLF
   479  	})
   480  	return md, err
   481  }
   482  
   483  func (m *stallingMDOps) GetRange(ctx context.Context, id tlf.ID,
   484  	start, stop kbfsmd.Revision, lockBeforeGet *keybase1.LockID) (
   485  	mds []ImmutableRootMetadata, err error) {
   486  	m.maybeStall(ctx, StallableMDGetRange)
   487  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   488  		var errGetRange error
   489  		mds, errGetRange = m.delegate.GetRange(
   490  			ctx, id, start, stop, lockBeforeGet)
   491  		m.maybeStall(ctx, StallableMDAfterGetRange)
   492  		return errGetRange
   493  	})
   494  	return mds, err
   495  }
   496  
   497  func (m *stallingMDOps) GetUnmergedRange(ctx context.Context, id tlf.ID,
   498  	bid kbfsmd.BranchID, start, stop kbfsmd.Revision) (mds []ImmutableRootMetadata, err error) {
   499  	m.maybeStall(ctx, StallableMDGetUnmergedRange)
   500  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   501  		var errGetUnmergedRange error
   502  		mds, errGetUnmergedRange = m.delegate.GetUnmergedRange(
   503  			ctx, id, bid, start, stop)
   504  		return errGetUnmergedRange
   505  	})
   506  	return mds, err
   507  }
   508  
   509  func (m *stallingMDOps) Put(
   510  	ctx context.Context, md *RootMetadata, verifyingKey kbfscrypto.VerifyingKey,
   511  	lockContext *keybase1.LockContext, priority keybase1.MDPriority,
   512  	bps data.BlockPutState) (irmd ImmutableRootMetadata, err error) {
   513  	m.maybeStall(ctx, StallableMDPut)
   514  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   515  		irmd, err = m.delegate.Put(
   516  			ctx, md, verifyingKey, lockContext, priority, bps)
   517  		m.maybeStall(ctx, StallableMDAfterPut)
   518  		return err
   519  	})
   520  	return irmd, err
   521  }
   522  
   523  func (m *stallingMDOps) PutUnmerged(
   524  	ctx context.Context, md *RootMetadata,
   525  	verifyingKey kbfscrypto.VerifyingKey, bps data.BlockPutState) (
   526  	irmd ImmutableRootMetadata, err error) {
   527  	m.maybeStall(ctx, StallableMDPutUnmerged)
   528  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   529  		irmd, err = m.delegate.PutUnmerged(ctx, md, verifyingKey, bps)
   530  		m.maybeStall(ctx, StallableMDAfterPutUnmerged)
   531  		return err
   532  	})
   533  	return irmd, err
   534  }
   535  
   536  func (m *stallingMDOps) PruneBranch(
   537  	ctx context.Context, id tlf.ID, bid kbfsmd.BranchID) error {
   538  	m.maybeStall(ctx, StallableMDPruneBranch)
   539  	return runWithContextCheck(ctx, func(ctx context.Context) error {
   540  		return m.delegate.PruneBranch(ctx, id, bid)
   541  	})
   542  }
   543  
   544  func (m *stallingMDOps) ResolveBranch(
   545  	ctx context.Context, id tlf.ID, bid kbfsmd.BranchID,
   546  	blocksToDelete []kbfsblock.ID, rmd *RootMetadata,
   547  	verifyingKey kbfscrypto.VerifyingKey, bps data.BlockPutState) (
   548  	irmd ImmutableRootMetadata, err error) {
   549  	m.maybeStall(ctx, StallableMDResolveBranch)
   550  	err = runWithContextCheck(ctx, func(ctx context.Context) error {
   551  		irmd, err = m.delegate.ResolveBranch(
   552  			ctx, id, bid, blocksToDelete, rmd, verifyingKey, bps)
   553  		return err
   554  	})
   555  	return irmd, err
   556  }