github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/xact/xreg/xreg.go (about)

     1  // Package xreg provides registry and (renew, find) functions for AIS eXtended Actions (xactions).
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package xreg
     6  
     7  import (
     8  	"fmt"
     9  	"sort"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/NVIDIA/aistore/api/apc"
    14  	"github.com/NVIDIA/aistore/cmn"
    15  	"github.com/NVIDIA/aistore/cmn/atomic"
    16  	"github.com/NVIDIA/aistore/cmn/cos"
    17  	"github.com/NVIDIA/aistore/cmn/debug"
    18  	"github.com/NVIDIA/aistore/cmn/feat"
    19  	"github.com/NVIDIA/aistore/cmn/nlog"
    20  	"github.com/NVIDIA/aistore/core"
    21  	"github.com/NVIDIA/aistore/core/meta"
    22  	"github.com/NVIDIA/aistore/hk"
    23  	"github.com/NVIDIA/aistore/xact"
    24  )
    25  
    26  // TODO: some of these constants must be configurable or derived from the config
    27  const (
    28  	initialCap       = 256 // initial capacity
    29  	keepOldThreshold = 256 // keep so many
    30  
    31  	waitPrevAborted = 2 * time.Second
    32  	waitLimitedCoex = 3 * time.Second
    33  )
    34  
    35  type WPR int
    36  
    37  const (
    38  	WprAbort = iota + 1
    39  	WprUse
    40  	WprKeepAndStartNew
    41  )
    42  
    43  type (
    44  	Renewable interface {
    45  		New(args Args, bck *meta.Bck) Renewable // new xaction stub that can be `Start`-ed.
    46  		Start() error                           // starts an xaction, will be called when entry is stored into registry
    47  		Kind() string
    48  		Get() core.Xact
    49  		WhenPrevIsRunning(prevEntry Renewable) (action WPR, err error)
    50  		Bucket() *meta.Bck
    51  		UUID() string
    52  	}
    53  	// used in constructions
    54  	Args struct {
    55  		Custom any // Additional arguments that are specific for a given xact.
    56  		UUID   string
    57  	}
    58  	RenewBase struct {
    59  		Args
    60  		Bck *meta.Bck
    61  	}
    62  	// simplified non-JSON QueryMsg (internal AIS use)
    63  	Flt struct {
    64  		Bck         *meta.Bck
    65  		OnlyRunning *bool
    66  		ID          string
    67  		Kind        string
    68  		Buckets     []*meta.Bck
    69  	}
    70  )
    71  
    72  // private
    73  type (
    74  	// Represents result of renewing given xact.
    75  	RenewRes struct {
    76  		Entry Renewable // Depending on situation can be new or old entry.
    77  		Err   error     // Error that occurred during renewal.
    78  		UUID  string    // "" if a new entry has been created, ID of the existing xaction otherwise
    79  	}
    80  	// Selects subset of xactions to abort.
    81  	abortArgs struct {
    82  		err error // original cause (or reason), e.g. cmn.ErrUserAbort
    83  		// criteria
    84  		bcks   []*meta.Bck // run on a slice of buckets
    85  		scope  []int       // one of { ScopeG, ScopeB, ... } enum
    86  		kind   string      // all of a kind
    87  		newreb bool        // (rebalance is starting) vs (dtor.AbortRebRes)
    88  	}
    89  
    90  	entries struct {
    91  		active   []Renewable // running entries - finished entries are gradually removed
    92  		roActive []Renewable // read-only copy
    93  		all      []Renewable
    94  		mtx      sync.RWMutex
    95  	}
    96  	// All entries in the registry. The entries are periodically cleaned up
    97  	// to make sure that we don't keep old entries forever.
    98  	registry struct {
    99  		renewMtx    sync.RWMutex // TODO: revisit to optimiz out
   100  		entries     entries
   101  		bckXacts    map[string]Renewable
   102  		nonbckXacts map[string]Renewable
   103  		finDelta    atomic.Int64
   104  	}
   105  )
   106  
   107  // default global registry that keeps track of all running xactions
   108  // In addition, the registry retains already finished xactions subject to lazy cleanup via `hk`.
   109  var dreg *registry
   110  
   111  //////////////////////
   112  // xaction registry //
   113  //////////////////////
   114  
   115  func Init() {
   116  	dreg = newRegistry()
   117  	xact.IncFinished = dreg.incFinished
   118  }
   119  
   120  func TestReset() { dreg = newRegistry() } // tests only
   121  
   122  func newRegistry() (r *registry) {
   123  	return &registry{
   124  		entries: entries{
   125  			all:      make([]Renewable, 0, initialCap),
   126  			active:   make([]Renewable, 0, 32),
   127  			roActive: make([]Renewable, 0, 64),
   128  		},
   129  		bckXacts:    make(map[string]Renewable, 32),
   130  		nonbckXacts: make(map[string]Renewable, 32),
   131  	}
   132  }
   133  
   134  // register w/housekeeper periodic registry cleanups
   135  func RegWithHK() {
   136  	hk.Reg("x-old"+hk.NameSuffix, dreg.hkDelOld, 0)
   137  	hk.Reg("x-prune-active"+hk.NameSuffix, dreg.hkPruneActive, 0)
   138  }
   139  
   140  func GetXact(uuid string) (core.Xact, error) { return dreg.getXact(uuid) }
   141  
   142  func (r *registry) getXact(uuid string) (xctn core.Xact, err error) {
   143  	if !xact.IsValidUUID(uuid) {
   144  		err = fmt.Errorf("invalid UUID %q", uuid)
   145  		return
   146  	}
   147  	e := &r.entries
   148  	e.mtx.RLock()
   149  outer:
   150  	for _, entries := range [][]Renewable{e.active, e.all} { // tradeoff: fewer active, higher priority
   151  		for _, entry := range entries {
   152  			x := entry.Get()
   153  			if x != nil && x.ID() == uuid {
   154  				xctn = x
   155  				break outer
   156  			}
   157  		}
   158  	}
   159  	e.mtx.RUnlock()
   160  	return
   161  }
   162  
   163  func GetAllRunning(inout *core.AllRunningInOut, periodic bool) {
   164  	dreg.entries.getAllRunning(inout, periodic)
   165  }
   166  
   167  func (e *entries) getAllRunning(inout *core.AllRunningInOut, periodic bool) {
   168  	var roActive []Renewable
   169  	if periodic {
   170  		roActive = e.roActive
   171  		roActive = roActive[:len(e.active)]
   172  	} else {
   173  		roActive = make([]Renewable, len(e.active))
   174  	}
   175  	e.mtx.RLock()
   176  	copy(roActive, e.active)
   177  	e.mtx.RUnlock()
   178  
   179  	for _, entry := range roActive {
   180  		var (
   181  			xctn = entry.Get()
   182  			k    = xctn.Kind()
   183  		)
   184  		if inout.Kind != "" && inout.Kind != k {
   185  			continue
   186  		}
   187  		if !xctn.Running() {
   188  			continue
   189  		}
   190  		var (
   191  			xqn    = k + xact.LeftID + xctn.ID() + xact.RightID // e.g. "make-n-copies[fGhuvvn7t]"
   192  			isIdle bool
   193  		)
   194  		if inout.Idle != nil {
   195  			if _, ok := xctn.(xact.Demand); ok {
   196  				isIdle = xctn.Snap().IsIdle()
   197  			}
   198  		}
   199  		if isIdle {
   200  			inout.Idle = append(inout.Idle, xqn)
   201  		} else {
   202  			inout.Running = append(inout.Running, xqn)
   203  		}
   204  	}
   205  
   206  	sort.Strings(inout.Running)
   207  	sort.Strings(inout.Idle)
   208  }
   209  
   210  func GetRunning(flt Flt) Renewable { return dreg.getRunning(flt) }
   211  
   212  func (r *registry) getRunning(flt Flt) (entry Renewable) {
   213  	e := &r.entries
   214  	e.mtx.RLock()
   215  	entry = e.findRunning(flt)
   216  	e.mtx.RUnlock()
   217  	return
   218  }
   219  
   220  // NOTE: relies on the find() to walk in the newer --> older order
   221  func GetLatest(flt Flt) Renewable {
   222  	entry := dreg.entries.find(flt)
   223  	return entry
   224  }
   225  
   226  // AbortAllBuckets aborts all xactions that run with any of the provided bcks.
   227  // It not only stops the "bucket xactions" but possibly "task xactions" which
   228  // are running on given bucket.
   229  
   230  func AbortAllBuckets(err error, bcks ...*meta.Bck) {
   231  	dreg.abort(&abortArgs{bcks: bcks, err: err})
   232  }
   233  
   234  // AbortAll waits until abort of all xactions is finished
   235  // Every abort is done asynchronously
   236  func AbortAll(err error, scope ...int) {
   237  	dreg.abort(&abortArgs{scope: scope, err: err})
   238  }
   239  
   240  func AbortKind(err error, kind string) {
   241  	dreg.abort(&abortArgs{kind: kind, err: err})
   242  }
   243  
   244  func AbortByNewReb(err error) { dreg.abort(&abortArgs{err: err, newreb: true}) }
   245  
   246  func DoAbort(flt Flt, err error) {
   247  	switch {
   248  	case flt.ID != "":
   249  		xctn, err := dreg.getXact(flt.ID)
   250  		if xctn == nil || err != nil {
   251  			return
   252  		}
   253  		debug.Assertf(flt.Kind == "" || xctn.Kind() == flt.Kind, "wrong xaction kind: %s vs %q", xctn.Cname(), flt.Kind)
   254  		xctn.Abort(err)
   255  	case flt.Kind != "" && flt.Bck != nil:
   256  		dreg.abort(&abortArgs{kind: flt.Kind, bcks: []*meta.Bck{flt.Bck}, err: err})
   257  	case flt.Kind != "":
   258  		debug.Assert(xact.IsValidKind(flt.Kind), flt.Kind)
   259  		AbortKind(err, flt.Kind)
   260  	case flt.Bck != nil:
   261  		AbortAllBuckets(err, flt.Bck)
   262  	default:
   263  		AbortAll(err)
   264  	}
   265  }
   266  
   267  func GetSnap(flt Flt) ([]*core.Snap, error) {
   268  	var onlyRunning bool
   269  	if flt.OnlyRunning != nil {
   270  		onlyRunning = *flt.OnlyRunning
   271  	}
   272  	if flt.ID != "" {
   273  		xctn, err := dreg.getXact(flt.ID)
   274  		if err != nil {
   275  			return nil, err
   276  		}
   277  		if xctn != nil {
   278  			if onlyRunning && xctn.Finished() {
   279  				return nil, cmn.NewErrXactNotFoundError("[only-running vs " + xctn.String() + "]")
   280  			}
   281  			if flt.Kind != "" && xctn.Kind() != flt.Kind {
   282  				return nil, cmn.NewErrXactNotFoundError("[kind=" + flt.Kind + " vs " + xctn.String() + "]")
   283  			}
   284  			return []*core.Snap{xctn.Snap()}, nil
   285  		}
   286  		if onlyRunning || flt.Kind != apc.ActRebalance {
   287  			return nil, cmn.NewErrXactNotFoundError("ID=" + flt.ID)
   288  		}
   289  		// not running rebalance: include all finished (but not aborted) ones
   290  		// with ID at ot _after_ the specified
   291  		return dreg.matchingXactsStats(func(xctn core.Xact) bool {
   292  			cmp := xact.CompareRebIDs(xctn.ID(), flt.ID)
   293  			return cmp >= 0 && xctn.Finished() && !xctn.IsAborted()
   294  		}), nil
   295  	}
   296  	if flt.Bck != nil || flt.Kind != "" {
   297  		// Error checks
   298  		if flt.Kind != "" && !xact.IsValidKind(flt.Kind) {
   299  			return nil, cmn.NewErrXactNotFoundError(flt.Kind)
   300  		}
   301  		if flt.Bck != nil && !flt.Bck.HasProvider() {
   302  			return nil, fmt.Errorf("xaction %q: unknown provider for bucket %s", flt.Kind, flt.Bck.Name)
   303  		}
   304  
   305  		if onlyRunning {
   306  			matching := make([]*core.Snap, 0, 10)
   307  			if flt.Kind == "" {
   308  				dreg.entries.mtx.RLock()
   309  				for kind := range xact.Table {
   310  					entry := dreg.entries.findRunning(Flt{Kind: kind, Bck: flt.Bck})
   311  					if entry != nil {
   312  						matching = append(matching, entry.Get().Snap())
   313  					}
   314  				}
   315  				dreg.entries.mtx.RUnlock()
   316  			} else {
   317  				entry := dreg.getRunning(Flt{Kind: flt.Kind, Bck: flt.Bck})
   318  				if entry != nil {
   319  					matching = append(matching, entry.Get().Snap())
   320  				}
   321  			}
   322  			return matching, nil
   323  		}
   324  		return dreg.matchingXactsStats(flt.Matches), nil
   325  	}
   326  	return dreg.matchingXactsStats(flt.Matches), nil
   327  }
   328  
   329  func (r *registry) abort(args *abortArgs) {
   330  	r.entries.forEach(args.do)
   331  }
   332  
   333  func (args *abortArgs) do(entry Renewable) bool {
   334  	xctn := entry.Get()
   335  	if xctn.Finished() {
   336  		return true
   337  	}
   338  
   339  	var abort bool
   340  	switch {
   341  	case args.newreb:
   342  		debug.Assertf(args.scope == nil && args.kind == "", "scope %v, kind %q", args.scope, args.kind)
   343  		_, dtor, err := xact.GetDescriptor(xctn.Kind())
   344  		debug.AssertNoErr(err)
   345  		if dtor.AbortRebRes {
   346  			abort = true
   347  		}
   348  	case len(args.bcks) > 0:
   349  		debug.Assertf(args.scope == nil, "scope %v", args.scope)
   350  		for _, bck := range args.bcks {
   351  			if xctn.Bck() != nil && bck.Equal(xctn.Bck(), true /*sameID*/, true /*same backend*/) {
   352  				abort = true
   353  				break
   354  			}
   355  		}
   356  		if abort && args.kind != "" {
   357  			abort = args.kind == xctn.Kind()
   358  		}
   359  	case args.kind != "":
   360  		debug.Assertf(args.scope == nil && len(args.bcks) == 0, "scope %v, bcks %v", args.scope, args.bcks)
   361  		abort = args.kind == xctn.Kind()
   362  	default:
   363  		abort = args.scope == nil || xact.IsSameScope(xctn.Kind(), args.scope...)
   364  	}
   365  
   366  	if abort {
   367  		xctn.Abort(args.err)
   368  	}
   369  	return true
   370  }
   371  
   372  func (r *registry) matchingXactsStats(match func(xctn core.Xact) bool) []*core.Snap {
   373  	matchingEntries := make([]Renewable, 0, 20)
   374  	r.entries.forEach(func(entry Renewable) bool {
   375  		if !match(entry.Get()) {
   376  			return true
   377  		}
   378  		matchingEntries = append(matchingEntries, entry)
   379  		return true
   380  	})
   381  	// TODO: we cannot do this inside `forEach` because - nested locks
   382  	sts := make([]*core.Snap, 0, len(matchingEntries))
   383  	for _, entry := range matchingEntries {
   384  		sts = append(sts, entry.Get().Snap())
   385  	}
   386  	return sts
   387  }
   388  
   389  func (r *registry) incFinished() { r.finDelta.Inc() }
   390  
   391  func (r *registry) hkPruneActive() time.Duration {
   392  	if r.finDelta.Swap(0) == 0 {
   393  		return hk.PruneActiveIval
   394  	}
   395  	e := &r.entries
   396  	e.mtx.Lock()
   397  	l := len(e.active)
   398  	for i := 0; i < l; i++ {
   399  		entry := e.active[i]
   400  		if !entry.Get().Finished() {
   401  			continue
   402  		}
   403  		copy(e.active[i:], e.active[i+1:])
   404  		i--
   405  		l--
   406  		e.active = e.active[:l]
   407  	}
   408  	e.mtx.Unlock()
   409  	return hk.PruneActiveIval
   410  }
   411  
   412  func (r *registry) hkDelOld() time.Duration {
   413  	var (
   414  		toRemove  []string
   415  		numNonLso int
   416  		now       = time.Now()
   417  	)
   418  
   419  	r.entries.mtx.RLock()
   420  	l := len(r.entries.all)
   421  
   422  	// first, cleanup list-objects: walk older to newer while counting non-lso
   423  	for i := range l {
   424  		xctn := r.entries.all[i].Get()
   425  		if xctn.Kind() != apc.ActList {
   426  			numNonLso++
   427  			continue
   428  		}
   429  		if xctn.Finished() {
   430  			if sinceFin := now.Sub(xctn.EndTime()); sinceFin >= hk.OldAgeLso {
   431  				toRemove = append(toRemove, xctn.ID())
   432  			}
   433  		}
   434  	}
   435  	// all the rest: older to newer, while keeping at least `keepOldThreshold`
   436  	if numNonLso > keepOldThreshold {
   437  		var cnt int
   438  		for i := range l {
   439  			xctn := r.entries.all[i].Get()
   440  			if xctn.Kind() == apc.ActList {
   441  				continue
   442  			}
   443  			if xctn.Finished() {
   444  				if sinceFin := now.Sub(xctn.EndTime()); sinceFin >= hk.OldAgeX {
   445  					toRemove = append(toRemove, xctn.ID())
   446  					cnt++
   447  					if numNonLso-cnt <= keepOldThreshold {
   448  						break
   449  					}
   450  				}
   451  			}
   452  		}
   453  	}
   454  	r.entries.mtx.RUnlock()
   455  
   456  	if len(toRemove) == 0 {
   457  		return hk.DelOldIval
   458  	}
   459  
   460  	// cleanup
   461  	r.entries.mtx.Lock()
   462  	for _, id := range toRemove {
   463  		r.entries.del(id)
   464  	}
   465  	r.entries.mtx.Unlock()
   466  	return hk.DelOldIval
   467  }
   468  
   469  func (r *registry) renewByID(entry Renewable, bck *meta.Bck) (rns RenewRes) {
   470  	flt := Flt{ID: entry.UUID(), Kind: entry.Kind(), Bck: bck}
   471  	rns = r._renewFlt(entry, flt)
   472  	rns.beingRenewed()
   473  	return
   474  }
   475  
   476  func (r *registry) renew(entry Renewable, bck *meta.Bck, buckets ...*meta.Bck) (rns RenewRes) {
   477  	flt := Flt{Kind: entry.Kind(), Bck: bck, Buckets: buckets}
   478  	rns = r._renewFlt(entry, flt)
   479  	rns.beingRenewed()
   480  	return
   481  }
   482  
   483  func (r *registry) _renewFlt(entry Renewable, flt Flt) (rns RenewRes) {
   484  	// first, try to reuse under rlock
   485  	r.renewMtx.RLock()
   486  	if prevEntry := r.getRunning(flt); prevEntry != nil {
   487  		xprev := prevEntry.Get()
   488  		if usePrev(xprev, entry, flt) {
   489  			r.renewMtx.RUnlock()
   490  			return RenewRes{Entry: prevEntry, UUID: xprev.ID()}
   491  		}
   492  		if wpr, err := entry.WhenPrevIsRunning(prevEntry); wpr == WprUse || err != nil {
   493  			r.renewMtx.RUnlock()
   494  			if cmn.IsErrXactUsePrev(err) {
   495  				if wpr != WprUse {
   496  					nlog.Errorf("%v - not starting a new one of the same kind", err)
   497  				}
   498  			}
   499  			xctn := prevEntry.Get()
   500  			return RenewRes{Entry: prevEntry, Err: err, UUID: xctn.ID()}
   501  		}
   502  	}
   503  	r.renewMtx.RUnlock()
   504  
   505  	// second
   506  	r.renewMtx.Lock()
   507  	rns = r.renewLocked(entry, flt)
   508  	r.renewMtx.Unlock()
   509  	return
   510  }
   511  
   512  // reusing current (aka "previous") xaction: default policies
   513  func usePrev(xprev core.Xact, nentry Renewable, flt Flt) bool {
   514  	pkind, nkind := xprev.Kind(), nentry.Kind()
   515  	debug.Assertf(pkind == nkind && pkind != "", "%s != %s", pkind, nkind)
   516  	pdtor, ndtor := xact.Table[pkind], xact.Table[nkind]
   517  	debug.Assert(pdtor.Scope == ndtor.Scope)
   518  
   519  	// same ID
   520  	if xprev.ID() != "" && xprev.ID() == nentry.UUID() {
   521  		return true // yes, use prev
   522  	}
   523  	if _, ok := xprev.(xact.Demand); !ok {
   524  		return false // upon return call xaction-specific WhenPrevIsRunning()
   525  	}
   526  	//
   527  	// on-demand
   528  	//
   529  	if pdtor.Scope != xact.ScopeB {
   530  		return true
   531  	}
   532  	bck := flt.Bck
   533  	debug.Assert(!bck.IsEmpty())
   534  	if !bck.Equal(xprev.Bck(), true, true) {
   535  		return false
   536  	}
   537  	// on-demand (from-bucket, to-bucket)
   538  	from, to := xprev.FromTo()
   539  	if len(flt.Buckets) == 2 && from != nil && to != nil {
   540  		for _, bck := range flt.Buckets {
   541  			if !bck.Equal(from, true, true) && !bck.Equal(to, true, true) {
   542  				return false
   543  			}
   544  		}
   545  	}
   546  	return true
   547  }
   548  
   549  func (r *registry) renewLocked(entry Renewable, flt Flt) (rns RenewRes) {
   550  	var (
   551  		xprev core.Xact
   552  		wpr   WPR
   553  		err   error
   554  	)
   555  	if prevEntry := r.getRunning(flt); prevEntry != nil {
   556  		xprev = prevEntry.Get()
   557  		if usePrev(xprev, entry, flt) {
   558  			return RenewRes{Entry: prevEntry, UUID: xprev.ID()}
   559  		}
   560  		wpr, err = entry.WhenPrevIsRunning(prevEntry)
   561  		if wpr == WprUse || err != nil {
   562  			return RenewRes{Entry: prevEntry, Err: err, UUID: xprev.ID()}
   563  		}
   564  		debug.Assert(wpr == WprAbort || wpr == WprKeepAndStartNew)
   565  		if wpr == WprAbort {
   566  			xprev.Abort(cmn.ErrXactRenewAbort)
   567  			time.Sleep(waitPrevAborted)
   568  		}
   569  	}
   570  	if err = entry.Start(); err != nil {
   571  		return RenewRes{Err: err}
   572  	}
   573  	r.entries.add(entry)
   574  	return RenewRes{Entry: entry}
   575  }
   576  
   577  //////////////////////
   578  // registry entries //
   579  //////////////////////
   580  
   581  // NOTE: the caller must take rlock
   582  func (e *entries) findRunning(flt Flt) Renewable {
   583  	onl := true
   584  	flt.OnlyRunning = &onl
   585  	for _, entry := range e.active {
   586  		if flt.Matches(entry.Get()) {
   587  			return entry
   588  		}
   589  	}
   590  	return nil
   591  }
   592  
   593  // internal use, special case: Flt{Kind: kind}; NOTE: the caller must take rlock
   594  func (e *entries) findRunningKind(kind string) Renewable {
   595  	for _, entry := range e.active {
   596  		if entry.Kind() != kind {
   597  			continue
   598  		}
   599  		xctn := entry.Get()
   600  		if xctn.Running() {
   601  			return entry
   602  		}
   603  	}
   604  	return nil
   605  }
   606  
   607  func (e *entries) find(flt Flt) (entry Renewable) {
   608  	e.mtx.RLock()
   609  	entry = e.findUnlocked(flt)
   610  	e.mtx.RUnlock()
   611  	return
   612  }
   613  
   614  func (e *entries) findUnlocked(flt Flt) Renewable {
   615  	if flt.OnlyRunning != nil && *flt.OnlyRunning {
   616  		return e.findRunning(flt)
   617  	}
   618  	// walk in reverse as there is a greater chance
   619  	// the one we are looking for is at the end
   620  	for idx := len(e.all) - 1; idx >= 0; idx-- {
   621  		entry := e.all[idx]
   622  		if flt.Matches(entry.Get()) {
   623  			return entry
   624  		}
   625  	}
   626  	return nil
   627  }
   628  
   629  func (e *entries) forEach(matcher func(entry Renewable) bool) {
   630  	e.mtx.RLock()
   631  	defer e.mtx.RUnlock()
   632  	for _, entry := range e.all {
   633  		if !matcher(entry) {
   634  			return
   635  		}
   636  	}
   637  }
   638  
   639  // NOTE: is called under lock
   640  func (e *entries) del(id string) {
   641  	for idx, entry := range e.all {
   642  		xctn := entry.Get()
   643  		if xctn.ID() == id {
   644  			debug.Assert(xctn.Finished(), xctn.String())
   645  			nlen := len(e.all) - 1
   646  			e.all[idx] = e.all[nlen]
   647  			e.all = e.all[:nlen]
   648  			break
   649  		}
   650  	}
   651  	for idx, entry := range e.active {
   652  		xctn := entry.Get()
   653  		if xctn.ID() == id {
   654  			if !xctn.Finished() {
   655  				nlog.Errorln("Warning: premature HK call to del-old", xctn.String())
   656  				break
   657  			}
   658  			nlen := len(e.active) - 1
   659  			e.active[idx] = e.active[nlen]
   660  			e.active = e.active[:nlen]
   661  			break
   662  		}
   663  	}
   664  }
   665  
   666  func (e *entries) add(entry Renewable) {
   667  	e.mtx.Lock()
   668  	e.active = append(e.active, entry)
   669  	e.all = append(e.all, entry)
   670  	e.mtx.Unlock()
   671  
   672  	// grow
   673  	if cap(e.roActive) < len(e.active) {
   674  		e.roActive = make([]Renewable, 0, len(e.active)+len(e.active)>>1)
   675  	}
   676  }
   677  
   678  // LimitedCoexistence checks whether a given xaction that is about to start can, in fact, "coexist"
   679  // with those that are currently running. It's a piece of logic designed to centralize all decision-making
   680  // of that sort. Further comments below.
   681  
   682  func LimitedCoexistence(tsi *meta.Snode, bck *meta.Bck, action string, otherBck ...*meta.Bck) (err error) {
   683  	if cmn.Rom.Features().IsSet(feat.IgnoreLimitedCoexistence) {
   684  		return
   685  	}
   686  	const sleep = time.Second
   687  	for i := time.Duration(0); i <= waitLimitedCoex; i += sleep {
   688  		if err = dreg.limco(tsi, bck, action, otherBck...); err == nil {
   689  			break
   690  		}
   691  		time.Sleep(sleep)
   692  	}
   693  	return
   694  }
   695  
   696  //   - assorted admin-requested actions, in turn, trigger global rebalance
   697  //     e.g.: if copy-bucket or ETL is currently running we cannot start
   698  //     transitioning storage targets to maintenance
   699  //   - all supported xactions define "limited coexistence" via their respecive
   700  //     descriptors in xact.Table
   701  func (r *registry) limco(tsi *meta.Snode, bck *meta.Bck, action string, otherBck ...*meta.Bck) error {
   702  	var (
   703  		nd    *xact.Descriptor // the one that wants to run
   704  		admin bool             // admin-requested action that'd generate protential conflict
   705  	)
   706  	switch action {
   707  	case apc.ActStartMaintenance, apc.ActStopMaintenance, apc.ActShutdownNode, apc.ActDecommissionNode:
   708  		nd = &xact.Descriptor{}
   709  		admin = true
   710  	default:
   711  		d, ok := xact.Table[action]
   712  		if !ok {
   713  			return nil
   714  		}
   715  		nd = &d
   716  	}
   717  	var locked bool
   718  	for kind, d := range xact.Table {
   719  		// rebalance-vs-rebalance and resilver-vs-resilver sort it out between themselves
   720  		// (by preempting)
   721  		conflict := (d.ConflictRebRes && admin) ||
   722  			(d.Rebalance && nd.ConflictRebRes) || (d.Resilver && nd.ConflictRebRes)
   723  		if !conflict {
   724  			continue
   725  		}
   726  
   727  		// potential conflict becomes very real if the 'kind' is actually running
   728  		if !locked {
   729  			r.entries.mtx.RLock()
   730  			locked = true
   731  			defer r.entries.mtx.RUnlock()
   732  		}
   733  		entry := r.entries.findRunningKind(kind)
   734  		if entry == nil {
   735  			continue
   736  		}
   737  
   738  		// conflict confirmed
   739  		var b string
   740  		if bck != nil {
   741  			b = bck.String()
   742  		}
   743  		return cmn.NewErrLimitedCoexistence(tsi.String(), entry.Get().String(), action, b)
   744  	}
   745  
   746  	// finally, bucket rename (apc.ActMoveBck) is a special case -
   747  	// incompatible with any ConflictRebRes type operation _on the same_ bucket
   748  	if action != apc.ActMoveBck {
   749  		return nil
   750  	}
   751  	bck1, bck2 := bck, otherBck[0]
   752  	for _, entry := range r.entries.active {
   753  		xctn := entry.Get()
   754  		if !xctn.Running() {
   755  			continue
   756  		}
   757  		d, ok := xact.Table[xctn.Kind()]
   758  		debug.Assert(ok, xctn.Kind())
   759  		if !d.ConflictRebRes {
   760  			continue
   761  		}
   762  		from, to := xctn.FromTo()
   763  		if _eqAny(bck1, bck2, from, to) {
   764  			detail := bck1.String() + " => " + bck2.String()
   765  			return cmn.NewErrLimitedCoexistence(tsi.String(), entry.Get().String(), action, detail)
   766  		}
   767  	}
   768  	return nil
   769  }
   770  
   771  func _eqAny(bck1, bck2, from, to *meta.Bck) (eq bool) {
   772  	if from != nil {
   773  		if bck1.Equal(from, false, true) || bck2.Equal(from, false, true) {
   774  			return true
   775  		}
   776  	}
   777  	if to != nil {
   778  		eq = bck1.Equal(to, false, true) || bck2.Equal(to, false, true)
   779  	}
   780  	return
   781  }
   782  
   783  ///////////////
   784  // RenewBase //
   785  ///////////////
   786  
   787  func (r *RenewBase) Bucket() *meta.Bck { return r.Bck }
   788  func (r *RenewBase) UUID() string      { return r.Args.UUID }
   789  
   790  func (r *RenewBase) Str(kind string) string {
   791  	prefix := kind
   792  	if r.Bck != nil {
   793  		prefix += "@" + r.Bck.String()
   794  	}
   795  	return fmt.Sprintf("%s, ID=%q", prefix, r.UUID())
   796  }
   797  
   798  //////////////
   799  // RenewRes //
   800  //////////////
   801  
   802  func (rns *RenewRes) IsRunning() bool {
   803  	if rns.UUID == "" {
   804  		return false
   805  	}
   806  	return rns.Entry.Get().Running()
   807  }
   808  
   809  // make sure existing on-demand is active to prevent it from (idle) expiration
   810  // (see demand.go hkcb())
   811  func (rns *RenewRes) beingRenewed() {
   812  	if rns.Err != nil || !rns.IsRunning() {
   813  		return
   814  	}
   815  	xctn := rns.Entry.Get()
   816  	if xdmnd, ok := xctn.(xact.Demand); ok {
   817  		xdmnd.IncPending()
   818  		xdmnd.DecPending()
   819  	}
   820  }
   821  
   822  /////////
   823  // Flt //
   824  /////////
   825  
   826  func (flt *Flt) String() string {
   827  	msg := xact.QueryMsg{OnlyRunning: flt.OnlyRunning, Bck: flt.Bck.Clone(), ID: flt.ID, Kind: flt.Kind}
   828  	return msg.String()
   829  }
   830  
   831  func (flt Flt) Matches(xctn core.Xact) (yes bool) {
   832  	debug.Assert(xact.IsValidKind(xctn.Kind()), xctn.String())
   833  	// running?
   834  	if flt.OnlyRunning != nil {
   835  		if *flt.OnlyRunning != xctn.Running() {
   836  			return false
   837  		}
   838  	}
   839  	// same ID?
   840  	if flt.ID != "" {
   841  		debug.Assert(cos.IsValidUUID(flt.ID) || xact.IsValidRebID(flt.ID), flt.ID)
   842  		if yes = xctn.ID() == flt.ID; yes {
   843  			debug.Assert(xctn.Kind() == flt.Kind, xctn.String()+" vs same ID "+flt.String())
   844  		}
   845  		return
   846  	}
   847  	// kind?
   848  	if flt.Kind != "" {
   849  		debug.Assert(xact.IsValidKind(flt.Kind), flt.Kind)
   850  		if xctn.Kind() != flt.Kind {
   851  			return false
   852  		}
   853  	}
   854  	// bucket?
   855  	if xact.Table[xctn.Kind()].Scope != xact.ScopeB {
   856  		return true // non single-bucket x
   857  	}
   858  	if flt.Bck == nil {
   859  		debug.Assert(len(flt.Buckets) == 0)
   860  		return true // the filter's not filtering out
   861  	}
   862  	if len(flt.Buckets) > 0 {
   863  		debug.Assert(len(flt.Buckets) == 2)
   864  		from, to := xctn.FromTo()
   865  		if from != nil { // XactArch special case
   866  			debug.Assert(to != nil)
   867  			return from.Equal(flt.Buckets[0], false, false) && to.Equal(flt.Buckets[1], false, false)
   868  		}
   869  	}
   870  
   871  	return xctn.Bck().Equal(flt.Bck, true, true)
   872  }