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

     1  // Package ais provides core functionality for the AIStore object storage.
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package ais
     6  
     7  import (
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"net/http"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/NVIDIA/aistore/api/apc"
    16  	"github.com/NVIDIA/aistore/cmn"
    17  	"github.com/NVIDIA/aistore/cmn/cos"
    18  	"github.com/NVIDIA/aistore/cmn/debug"
    19  	"github.com/NVIDIA/aistore/cmn/mono"
    20  	"github.com/NVIDIA/aistore/cmn/nlog"
    21  	"github.com/NVIDIA/aistore/core"
    22  	"github.com/NVIDIA/aistore/core/meta"
    23  	"github.com/NVIDIA/aistore/ext/dload"
    24  	"github.com/NVIDIA/aistore/hk"
    25  	"github.com/NVIDIA/aistore/nl"
    26  	"github.com/NVIDIA/aistore/xact"
    27  	"github.com/NVIDIA/aistore/xact/xreg"
    28  	jsoniter "github.com/json-iterator/go"
    29  )
    30  
    31  // Notification "receiver"
    32  
    33  const notifsName = "p-notifs"
    34  
    35  type (
    36  	listeners struct {
    37  		m map[string]nl.Listener // [UUID => NotifListener]
    38  		sync.RWMutex
    39  	}
    40  
    41  	notifs struct {
    42  		p   *proxy
    43  		nls *listeners // running
    44  		fin *listeners // finished
    45  
    46  		added    []nl.Listener // reusable slice of `nl` to add to `nls`
    47  		removed  []nl.Listener // reusable slice of `nl` to remove from `nls`
    48  		finished []nl.Listener // reusable slice of `nl` to add to `fin`
    49  
    50  		smapVer int64
    51  		mu      sync.Mutex
    52  	}
    53  	jsonNotifs struct {
    54  		Running  []*notifListenMsg `json:"running"`
    55  		Finished []*notifListenMsg `json:"finished"`
    56  	}
    57  
    58  	nlFilter xreg.Flt
    59  
    60  	// receiver to start listening
    61  	notifListenMsg struct {
    62  		nl nl.Listener
    63  	}
    64  	jsonNL struct {
    65  		Type string              `json:"type"`
    66  		NL   jsoniter.RawMessage `json:"nl"`
    67  	}
    68  )
    69  
    70  // interface guard
    71  var (
    72  	_ meta.Slistener = (*notifs)(nil)
    73  
    74  	_ json.Marshaler   = (*notifs)(nil)
    75  	_ json.Unmarshaler = (*notifs)(nil)
    76  
    77  	_ json.Marshaler   = (*notifListenMsg)(nil)
    78  	_ json.Unmarshaler = (*notifListenMsg)(nil)
    79  )
    80  
    81  ////////////
    82  // notifs //
    83  ////////////
    84  
    85  func (n *notifs) init(p *proxy) {
    86  	n.p = p
    87  	n.nls = newListeners()
    88  	n.fin = newListeners()
    89  
    90  	n.added = make([]nl.Listener, 16)
    91  	n.removed = make([]nl.Listener, 16)
    92  	n.finished = make([]nl.Listener, 16)
    93  
    94  	hk.Reg(notifsName+hk.NameSuffix, n.housekeep, hk.PruneActiveIval)
    95  	n.p.Sowner().Listeners().Reg(n)
    96  }
    97  
    98  // handle other nodes' notifications
    99  // verb /v1/notifs/[progress|finished] - apc.Progress and apc.Finished, respectively
   100  func (n *notifs) handler(w http.ResponseWriter, r *http.Request) {
   101  	var (
   102  		notifMsg = &core.NotifMsg{}
   103  		nl       nl.Listener
   104  		uuid     string
   105  		tid      = r.Header.Get(apc.HdrCallerID) // sender node ID
   106  	)
   107  	if r.Method != http.MethodPost {
   108  		cmn.WriteErr405(w, r, http.MethodPost)
   109  		return
   110  	}
   111  	apiItems, err := n.p.parseURL(w, r, apc.URLPathNotifs.L, 1, false)
   112  	if err != nil {
   113  		return
   114  	}
   115  
   116  	if apiItems[0] != apc.Progress && apiItems[0] != apc.Finished {
   117  		n.p.writeErrf(w, r, "Invalid route /notifs/%s", apiItems[0])
   118  		return
   119  	}
   120  	if cmn.ReadJSON(w, r, notifMsg) != nil {
   121  		return
   122  	}
   123  
   124  	// NOTE: the sender is asynchronous - ignores the response -
   125  	// which is why we consider `not-found`, `already-finished`,
   126  	// and `unknown-notifier` benign non-error conditions
   127  	uuid = notifMsg.UUID
   128  	if !withRetry(cmn.Rom.CplaneOperation(), func() bool {
   129  		nl = n.entry(uuid)
   130  		return nl != nil
   131  	}) {
   132  		return
   133  	}
   134  
   135  	var (
   136  		srcs    = nl.Notifiers()
   137  		tsi, ok = srcs[tid]
   138  	)
   139  	if !ok {
   140  		return
   141  	}
   142  	//
   143  	// NotifListener and notifMsg must have the same type
   144  	//
   145  	nl.RLock()
   146  	if nl.HasFinished(tsi) {
   147  		n.p.writeErrSilentf(w, r, http.StatusBadRequest,
   148  			"%s: duplicate %s from %s, %s", n.p.si, notifMsg, tid, nl)
   149  		nl.RUnlock()
   150  		return
   151  	}
   152  	nl.RUnlock()
   153  
   154  	switch apiItems[0] {
   155  	case apc.Progress:
   156  		nl.Lock()
   157  		n._progress(nl, tsi, notifMsg)
   158  		nl.Unlock()
   159  	case apc.Finished:
   160  		n._finished(nl, tsi, notifMsg)
   161  	} // default not needed - cannot happen
   162  }
   163  
   164  func (*notifs) _progress(nl nl.Listener, tsi *meta.Snode, msg *core.NotifMsg) {
   165  	if msg.ErrMsg != "" {
   166  		nl.AddErr(errors.New(msg.ErrMsg))
   167  	}
   168  	// when defined, `data must be valid encoded stats
   169  	if msg.Data != nil {
   170  		stats, _, _, err := nl.UnmarshalStats(msg.Data)
   171  		debug.AssertNoErr(err)
   172  		nl.SetStats(tsi.ID(), stats)
   173  	}
   174  }
   175  
   176  func (n *notifs) _finished(nl nl.Listener, tsi *meta.Snode, msg *core.NotifMsg) {
   177  	var (
   178  		srcErr  error
   179  		done    bool
   180  		aborted = msg.AbortedX
   181  	)
   182  	nl.Lock()
   183  	if msg.Data != nil {
   184  		// ditto
   185  		stats, _, abortedSnap, err := nl.UnmarshalStats(msg.Data)
   186  		debug.AssertNoErr(err)
   187  		nl.SetStats(tsi.ID(), stats)
   188  
   189  		if abortedSnap != msg.AbortedX && cmn.Rom.FastV(4, cos.SmoduleAIS) {
   190  			nlog.Infof("Warning: %s: %t vs %t [%s]", msg, abortedSnap, msg.AbortedX, nl.String())
   191  		}
   192  		aborted = aborted || abortedSnap
   193  	}
   194  	if msg.ErrMsg != "" {
   195  		srcErr = errors.New(msg.ErrMsg)
   196  	}
   197  	done = n.markFinished(nl, tsi, srcErr, aborted)
   198  	nl.Unlock()
   199  
   200  	if done {
   201  		n.done(nl)
   202  	}
   203  }
   204  
   205  // start listening
   206  func (n *notifs) add(nl nl.Listener) (err error) {
   207  	debug.Assert(xact.IsValidUUID(nl.UUID()))
   208  	if nl.ActiveCount() == 0 {
   209  		return fmt.Errorf("cannot add %q with no active notifiers", nl)
   210  	}
   211  	if exists := n.nls.add(nl, false /*locked*/); exists {
   212  		return
   213  	}
   214  	nl.SetAddedTime()
   215  	if cmn.Rom.FastV(5, cos.SmoduleAIS) {
   216  		nlog.Infoln("add", nl.Name())
   217  	}
   218  	return
   219  }
   220  
   221  func (n *notifs) del(nl nl.Listener, locked bool) (ok bool) {
   222  	ok = n.nls.del(nl, locked /*locked*/)
   223  	if ok && cmn.Rom.FastV(5, cos.SmoduleAIS) {
   224  		nlog.Infoln("del", nl.Name())
   225  	}
   226  	return
   227  }
   228  
   229  func (n *notifs) entry(uuid string) nl.Listener {
   230  	entry, exists := n.nls.entry(uuid)
   231  	if exists {
   232  		return entry
   233  	}
   234  	entry, exists = n.fin.entry(uuid)
   235  	if exists {
   236  		return entry
   237  	}
   238  	return nil
   239  }
   240  
   241  func (n *notifs) find(flt nlFilter) (nl nl.Listener) {
   242  	if flt.ID != "" {
   243  		return n.entry(flt.ID)
   244  	}
   245  	nl = n.nls.find(flt)
   246  	if nl != nil || (flt.OnlyRunning != nil && *flt.OnlyRunning) {
   247  		return nl
   248  	}
   249  	nl = n.fin.find(flt)
   250  	return nl
   251  }
   252  
   253  func (n *notifs) findAll(flt nlFilter) (nls []nl.Listener) {
   254  	if flt.ID != "" {
   255  		if nl := n.entry(flt.ID); nl != nil {
   256  			nls = append(nls, nl)
   257  		}
   258  		return
   259  	}
   260  	nls = n.nls.findAll(flt)
   261  	if flt.OnlyRunning != nil && *flt.OnlyRunning {
   262  		return
   263  	}
   264  	if s2 := n.fin.findAll(flt); len(s2) > 0 {
   265  		nls = append(nls, s2...)
   266  	}
   267  	return
   268  }
   269  
   270  func (n *notifs) size() (size int) {
   271  	n.nls.RLock()
   272  	n.fin.RLock()
   273  	size = n.nls.len() + n.fin.len()
   274  	n.fin.RUnlock()
   275  	n.nls.RUnlock()
   276  	return
   277  }
   278  
   279  // PRECONDITION: `nl` should be under lock.
   280  func (*notifs) markFinished(nl nl.Listener, tsi *meta.Snode, srcErr error, aborted bool) (done bool) {
   281  	nl.MarkFinished(tsi)
   282  	if aborted {
   283  		nl.SetAborted()
   284  		if srcErr == nil {
   285  			detail := fmt.Sprintf("%s from %s", nl, tsi.StringEx())
   286  			srcErr = cmn.NewErrAborted(nl.String(), detail, nil)
   287  		}
   288  	}
   289  	if srcErr != nil {
   290  		nl.AddErr(srcErr)
   291  	}
   292  	return nl.ActiveCount() == 0 || aborted
   293  }
   294  
   295  func (n *notifs) done(nl nl.Listener) {
   296  	if !n.del(nl, false /*locked*/) {
   297  		// `nl` already removed from active map
   298  		return
   299  	}
   300  	n.fin.add(nl, false /*locked*/)
   301  
   302  	if nl.Aborted() {
   303  		smap := n.p.owner.smap.get()
   304  		// abort via primary to eliminate redundant intra-cluster messaging-and-handling
   305  		// TODO: confirm & load-balance
   306  		doSend := true
   307  		if smap.Primary != nil { // nil in unit tests
   308  			debug.Assert(n.p.SID() != smap.Primary.ID() || smap.IsPrimary(n.p.si))
   309  			doSend = smap.IsPrimary(n.p.si) ||
   310  				!smap.IsIC(smap.Primary) // never happens but ok
   311  		}
   312  		if doSend {
   313  			// NOTE: we accept finished notifications even after
   314  			// `nl` is aborted. Handle locks carefully.
   315  			args := allocBcArgs()
   316  			args.req = abortReq(nl)
   317  			args.network = cmn.NetIntraControl
   318  			args.timeout = cmn.Rom.MaxKeepalive()
   319  			args.nodes = []meta.NodeMap{nl.Notifiers()}
   320  			args.nodeCount = len(args.nodes[0])
   321  			args.smap = smap
   322  			args.async = true
   323  			_ = n.p.bcastNodes(args) // args.async: result is already discarded/freed
   324  			freeBcArgs(args)
   325  		}
   326  	}
   327  	nl.Callback(nl, time.Now().UnixNano())
   328  }
   329  
   330  func abortReq(nl nl.Listener) cmn.HreqArgs {
   331  	if nl.Kind() == apc.ActDownload {
   332  		// downloader implements abort via http.MethodDelete
   333  		// and different messaging
   334  		return dload.AbortReq(nl.UUID() /*job ID*/)
   335  	}
   336  	msg := apc.ActMsg{
   337  		Action: apc.ActXactStop,
   338  		Name:   cmn.ErrXactICNotifAbort.Error(),
   339  		Value:  xact.ArgsMsg{ID: nl.UUID() /*xid*/, Kind: nl.Kind()},
   340  	}
   341  	args := cmn.HreqArgs{Method: http.MethodPut}
   342  	args.Body = cos.MustMarshal(msg)
   343  	args.Path = apc.URLPathXactions.S
   344  	return args
   345  }
   346  
   347  //
   348  // housekeeping
   349  //
   350  
   351  func (n *notifs) housekeep() time.Duration {
   352  	now := time.Now().UnixNano()
   353  	n.fin.Lock()
   354  	for _, nl := range n.fin.m {
   355  		timeout := hk.DelOldIval
   356  		if nl.Kind() == apc.ActList {
   357  			timeout = hk.OldAgeLsoNotif
   358  		}
   359  		if time.Duration(now-nl.EndTime()) > timeout {
   360  			n.fin.del(nl, true /*locked*/)
   361  		}
   362  	}
   363  	n.fin.Unlock()
   364  
   365  	n.nls.RLock() // TODO: atomic instead
   366  	if n.nls.len() == 0 {
   367  		n.nls.RUnlock()
   368  		return hk.PruneActiveIval
   369  	}
   370  	tempn := make(map[string]nl.Listener, n.nls.len())
   371  	for uuid, nl := range n.nls.m {
   372  		tempn[uuid] = nl
   373  	}
   374  	n.nls.RUnlock()
   375  	for _, nl := range tempn {
   376  		n.bcastGetStats(nl, hk.PruneActiveIval)
   377  	}
   378  	// cleanup temp cloned notifs
   379  	clear(tempn)
   380  
   381  	return hk.PruneActiveIval
   382  }
   383  
   384  // conditional: query targets iff they delayed updating
   385  func (n *notifs) bcastGetStats(nl nl.Listener, dur time.Duration) {
   386  	var (
   387  		config           = cmn.GCO.Get()
   388  		progressInterval = config.Periodic.NotifTime.D()
   389  		done             bool
   390  	)
   391  	nl.RLock()
   392  	nodesTardy, syncRequired := nl.NodesTardy(dur)
   393  	nl.RUnlock()
   394  	if !syncRequired {
   395  		return
   396  	}
   397  	args := allocBcArgs()
   398  	args.network = cmn.NetIntraControl
   399  	args.timeout = config.Timeout.MaxHostBusy.D()
   400  	args.req = nl.QueryArgs() // nodes to fetch stats from
   401  	args.nodes = []meta.NodeMap{nodesTardy}
   402  	args.nodeCount = len(args.nodes[0])
   403  	args.smap = n.p.owner.smap.get()
   404  	debug.Assert(args.nodeCount > 0) // Ensure that there is at least one node to fetch.
   405  
   406  	results := n.p.bcastNodes(args)
   407  	freeBcArgs(args)
   408  	for _, res := range results {
   409  		if res.err == nil {
   410  			stats, finished, aborted, err := nl.UnmarshalStats(res.bytes)
   411  			if err != nil {
   412  				nlog.Errorf("%s: failed to parse stats from %s: %v", n.p, res.si.StringEx(), err)
   413  				continue
   414  			}
   415  			nl.Lock()
   416  			if finished {
   417  				done = done || n.markFinished(nl, res.si, nil, aborted)
   418  			}
   419  			nl.SetStats(res.si.ID(), stats)
   420  			nl.Unlock()
   421  		} else if res.status == http.StatusNotFound {
   422  			if mono.Since(nl.AddedTime()) < progressInterval {
   423  				// likely didn't start yet - skipping
   424  				continue
   425  			}
   426  			err := fmt.Errorf("%s: %s not found at %s", n.p.si, nl, res.si.StringEx())
   427  			nl.Lock()
   428  			done = done || n.markFinished(nl, res.si, err, true) // NOTE: not-found at one ==> all done
   429  			nl.Unlock()
   430  		} else if cmn.Rom.FastV(4, cos.SmoduleAIS) {
   431  			nlog.Errorf("%s: %s, node %s: %v", n.p, nl, res.si.StringEx(), res.unwrap())
   432  		}
   433  	}
   434  	freeBcastRes(results)
   435  	if done {
   436  		n.done(nl)
   437  	}
   438  }
   439  
   440  func (n *notifs) getOwner(uuid string) (o string, exists bool) {
   441  	var nl nl.Listener
   442  	if nl = n.entry(uuid); nl != nil {
   443  		exists = true
   444  		o = nl.GetOwner()
   445  	}
   446  	return
   447  }
   448  
   449  // TODO: consider Smap versioning per NotifListener
   450  func (n *notifs) ListenSmapChanged() {
   451  	if !n.p.ClusterStarted() {
   452  		return
   453  	}
   454  	smap := n.p.owner.smap.get()
   455  	if n.smapVer >= smap.Version {
   456  		return
   457  	}
   458  	n.smapVer = smap.Version
   459  
   460  	n.nls.RLock()
   461  	if n.nls.len() == 0 {
   462  		n.nls.RUnlock()
   463  		return
   464  	}
   465  	var (
   466  		remnl = make(map[string]nl.Listener)
   467  		remid = make(cos.StrKVs)
   468  	)
   469  	for uuid, nl := range n.nls.m {
   470  		nl.RLock()
   471  		for sid := range nl.ActiveNotifiers() {
   472  			if node := smap.GetActiveNode(sid); node == nil {
   473  				remnl[uuid] = nl
   474  				remid[uuid] = sid
   475  				break
   476  			}
   477  		}
   478  		nl.RUnlock()
   479  	}
   480  	n.nls.RUnlock()
   481  	if len(remnl) == 0 {
   482  		return
   483  	}
   484  	now := time.Now().UnixNano()
   485  
   486  repeat:
   487  	for uuid, nl := range remnl {
   488  		sid := remid[uuid]
   489  		if nl.Kind() == apc.ActRebalance && nl.Cause() != "" { // for the cause, see ais/rebmeta
   490  			nlog.Infof("Warning: %s: %s is out, ignore 'smap-changed'", nl.String(), sid)
   491  			delete(remnl, uuid)
   492  			goto repeat
   493  		}
   494  		err := &errNodeNotFound{"abort " + nl.String() + " via 'smap-changed':", sid, n.p.si, smap}
   495  		nl.Lock()
   496  		nl.AddErr(err)
   497  		nl.SetAborted()
   498  		nl.Unlock()
   499  	}
   500  	if len(remnl) == 0 {
   501  		return
   502  	}
   503  
   504  	// cleanup and callback w/ nl.Err
   505  	n.fin.Lock()
   506  	for uuid, nl := range remnl {
   507  		debug.Assert(nl.UUID() == uuid)
   508  		n.fin.add(nl, true /*locked*/)
   509  	}
   510  	n.fin.Unlock()
   511  	n.nls.Lock()
   512  	for _, nl := range remnl {
   513  		n.del(nl, true /*locked*/)
   514  	}
   515  	n.nls.Unlock()
   516  
   517  	for _, nl := range remnl {
   518  		nl.Callback(nl, now)
   519  	}
   520  	// cleanup
   521  	clear(remnl)
   522  	clear(remid)
   523  }
   524  
   525  func (n *notifs) MarshalJSON() (data []byte, err error) {
   526  	t := jsonNotifs{}
   527  	n.nls.RLock()
   528  	n.fin.RLock()
   529  	if n.nls.len() == 0 && n.fin.len() == 0 {
   530  		n.fin.RUnlock()
   531  		n.nls.RUnlock()
   532  		return
   533  	}
   534  	t.Running = make([]*notifListenMsg, 0, n.nls.len())
   535  	t.Finished = make([]*notifListenMsg, 0, n.fin.len())
   536  	for _, nl := range n.nls.m {
   537  		t.Running = append(t.Running, newNLMsg(nl))
   538  	}
   539  	n.nls.RUnlock()
   540  
   541  	for _, nl := range n.fin.m {
   542  		t.Finished = append(t.Finished, newNLMsg(nl))
   543  	}
   544  	n.fin.RUnlock()
   545  
   546  	return jsoniter.Marshal(t)
   547  }
   548  
   549  func (n *notifs) UnmarshalJSON(data []byte) (err error) {
   550  	if len(data) == 0 {
   551  		return
   552  	}
   553  	t := jsonNotifs{}
   554  	if err = jsoniter.Unmarshal(data, &t); err != nil {
   555  		return
   556  	}
   557  	if len(t.Running) == 0 && len(t.Finished) == 0 {
   558  		return
   559  	}
   560  	n.mu.Lock()
   561  	n.apply(&t)
   562  	n.mu.Unlock()
   563  	return
   564  }
   565  
   566  // Identify the diff in ownership table and populate `added`, `removed` and `finished` slices
   567  // (under lock)
   568  func (n *notifs) apply(t *jsonNotifs) {
   569  	added, removed, finished := n.added[:0], n.removed[:0], n.finished[:0]
   570  	n.nls.RLock()
   571  	n.fin.RLock()
   572  	for _, m := range t.Running {
   573  		if n.fin.exists(m.nl.UUID()) || n.nls.exists(m.nl.UUID()) {
   574  			continue
   575  		}
   576  		added = append(added, m.nl)
   577  	}
   578  
   579  	for _, m := range t.Finished {
   580  		if n.fin.exists(m.nl.UUID()) {
   581  			continue
   582  		}
   583  		if n.nls.exists(m.nl.UUID()) {
   584  			removed = append(removed, m.nl)
   585  		}
   586  		finished = append(finished, m.nl)
   587  	}
   588  	n.fin.RUnlock()
   589  	n.nls.RUnlock()
   590  
   591  	if len(removed) == 0 && len(added) == 0 {
   592  		goto fin
   593  	}
   594  
   595  	// Add/Remove `nl` - `n.nls`.
   596  	n.nls.Lock()
   597  	for _, nl := range added {
   598  		n.nls.add(nl, true /*locked*/)
   599  	}
   600  	for _, nl := range removed {
   601  		n.nls.del(nl, true /*locked*/)
   602  	}
   603  	n.nls.Unlock()
   604  
   605  fin:
   606  	if len(finished) == 0 {
   607  		return
   608  	}
   609  
   610  	n.fin.Lock()
   611  	// Add `nl` to `n.fin`.
   612  	for _, nl := range finished {
   613  		n.fin.add(nl, true /*locked*/)
   614  	}
   615  	n.fin.Unlock()
   616  
   617  	// Call the Callback for each `nl` marking it finished.
   618  	now := time.Now().UnixNano()
   619  	for _, nl := range finished {
   620  		nl.Callback(nl, now)
   621  	}
   622  }
   623  
   624  func (n *notifs) String() string {
   625  	l, f := n.nls.len(), n.fin.len() // not r-locking
   626  	return fmt.Sprintf("%s (nls=%d, fin=%d)", notifsName, l, f)
   627  }
   628  
   629  ///////////////
   630  // listeners //
   631  ///////////////
   632  
   633  func newListeners() *listeners { return &listeners{m: make(map[string]nl.Listener, 64)} }
   634  func (l *listeners) len() int  { return len(l.m) }
   635  
   636  func (l *listeners) entry(uuid string) (entry nl.Listener, exists bool) {
   637  	l.RLock()
   638  	entry, exists = l.m[uuid]
   639  	l.RUnlock()
   640  	return
   641  }
   642  
   643  func (l *listeners) add(nl nl.Listener, locked bool) (exists bool) {
   644  	if !locked {
   645  		l.Lock()
   646  	}
   647  	if _, exists = l.m[nl.UUID()]; !exists {
   648  		l.m[nl.UUID()] = nl
   649  	}
   650  	if !locked {
   651  		l.Unlock()
   652  	}
   653  	return
   654  }
   655  
   656  func (l *listeners) del(nl nl.Listener, locked bool) (ok bool) {
   657  	if !locked {
   658  		l.Lock()
   659  	} else {
   660  		debug.AssertRWMutexLocked(&l.RWMutex)
   661  	}
   662  	if _, ok = l.m[nl.UUID()]; ok {
   663  		delete(l.m, nl.UUID())
   664  	}
   665  	if !locked {
   666  		l.Unlock()
   667  	}
   668  	return
   669  }
   670  
   671  // PRECONDITION: `l` should be under lock.
   672  func (l *listeners) exists(uuid string) (ok bool) {
   673  	_, ok = l.m[uuid]
   674  	return
   675  }
   676  
   677  // Returns a listener that matches the filter condition.
   678  // - returns the first one that's still running, if exists
   679  // - otherwise, returns the one that finished most recently
   680  // (compare with the below)
   681  func (l *listeners) find(flt nlFilter) (nl nl.Listener) {
   682  	var ftime int64
   683  	l.RLock()
   684  	for _, listener := range l.m {
   685  		if !flt.match(listener) {
   686  			continue
   687  		}
   688  		et := listener.EndTime()
   689  		if ftime != 0 && et < ftime {
   690  			debug.Assert(listener.Finished())
   691  			continue
   692  		}
   693  		nl = listener
   694  		if !listener.Finished() {
   695  			break
   696  		}
   697  		ftime = et
   698  	}
   699  	l.RUnlock()
   700  	return
   701  }
   702  
   703  // returns all matches
   704  func (l *listeners) findAll(flt nlFilter) (nls []nl.Listener) {
   705  	l.RLock()
   706  	for _, listener := range l.m {
   707  		if flt.match(listener) {
   708  			nls = append(nls, listener)
   709  		}
   710  	}
   711  	l.RUnlock()
   712  	return
   713  }
   714  
   715  ////////////////////
   716  // notifListenMsg //
   717  ////////////////////
   718  
   719  func newNLMsg(nl nl.Listener) *notifListenMsg {
   720  	return &notifListenMsg{nl: nl}
   721  }
   722  
   723  func (n *notifListenMsg) MarshalJSON() (data []byte, err error) {
   724  	n.nl.RLock()
   725  	msg := jsonNL{Type: n.nl.Kind(), NL: cos.MustMarshal(n.nl)}
   726  	n.nl.RUnlock()
   727  	return jsoniter.Marshal(msg)
   728  }
   729  
   730  func (n *notifListenMsg) UnmarshalJSON(data []byte) (err error) {
   731  	t := jsonNL{}
   732  	if err = jsoniter.Unmarshal(data, &t); err != nil {
   733  		return
   734  	}
   735  	if dload.IsType(t.Type) {
   736  		n.nl = &dload.NotifDownloadListerner{}
   737  	} else {
   738  		n.nl = &xact.NotifXactListener{}
   739  	}
   740  	return jsoniter.Unmarshal(t.NL, &n.nl)
   741  }
   742  
   743  //
   744  // Notification listener filter (nlFilter)
   745  //
   746  
   747  func (nf *nlFilter) match(nl nl.Listener) bool {
   748  	if nl.UUID() == nf.ID {
   749  		return true
   750  	}
   751  	if nf.Kind == "" || nl.Kind() == nf.Kind {
   752  		if nf.Bck == nil || nf.Bck.IsEmpty() {
   753  			return true
   754  		}
   755  		for _, bck := range nl.Bcks() {
   756  			qbck := (*cmn.QueryBcks)(nf.Bck.Bucket())
   757  			if qbck.Contains(bck) {
   758  				return true
   759  			}
   760  		}
   761  	}
   762  	return false
   763  }
   764  
   765  // yet another call-retrying utility (TODO: unify)
   766  
   767  func withRetry(timeout time.Duration, cond func() bool) (ok bool) {
   768  	sleep := cos.ProbingFrequency(timeout)
   769  	for elapsed := time.Duration(0); elapsed < timeout; elapsed += sleep {
   770  		if ok = cond(); ok {
   771  			break
   772  		}
   773  		time.Sleep(sleep)
   774  	}
   775  	return
   776  }