github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/utils/reftracker/reftracker.go (about)

     1  /*
     2   * Copyright (C) 2021 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package reftracker
    19  
    20  import (
    21  	"errors"
    22  	"sync"
    23  	"time"
    24  )
    25  
    26  // DefaultPatrolPeriod is an interval between collect loops
    27  const DefaultPatrolPeriod = 1 * time.Second
    28  
    29  var (
    30  	singletonOnce sync.Once
    31  	singletonInst *RefTracker
    32  )
    33  
    34  var (
    35  	// ErrNotFound is returned when incremented or decremented element is not
    36  	// found among tracked elements
    37  	ErrNotFound = errors.New("element not found")
    38  )
    39  
    40  // Singleton instantiates globally available reference tracker
    41  func Singleton() *RefTracker {
    42  	singletonOnce.Do(func() {
    43  		singletonInst = NewRefTracker(DefaultPatrolPeriod)
    44  	})
    45  
    46  	return singletonInst
    47  }
    48  
    49  // RefTracker is a collection of elements referenced by string keys.
    50  // Once element reference count hits zero it will be removed after TTL
    51  // unless new references will not appear.
    52  type RefTracker struct {
    53  	elemMux  sync.Mutex
    54  	elements map[string]*trackedElement
    55  	stop     chan struct{}
    56  	stopOnce sync.Once
    57  }
    58  
    59  type trackedElement struct {
    60  	lastReleased    time.Time
    61  	ttl             time.Duration
    62  	releaseCallback func()
    63  	refCount        int
    64  }
    65  
    66  // NewRefTracker starts RefTracker with
    67  func NewRefTracker(patrolPeriod time.Duration) *RefTracker {
    68  	rt := &RefTracker{
    69  		elements: make(map[string]*trackedElement),
    70  		stop:     make(chan struct{}),
    71  	}
    72  	go rt.patrolLoop(patrolPeriod)
    73  	return rt
    74  }
    75  
    76  func (rt *RefTracker) patrolLoop(patrolPeriod time.Duration) {
    77  	for {
    78  		select {
    79  		case <-rt.stop:
    80  			return
    81  		case <-time.After(patrolPeriod):
    82  			now := time.Now()
    83  			var toDelete []string
    84  
    85  			rt.elemMux.Lock()
    86  			for key, elem := range rt.elements {
    87  				if elem.refCount == 0 && now.Sub(elem.lastReleased) > elem.ttl {
    88  					toDelete = append(toDelete, key)
    89  					go elem.releaseCallback()
    90  				}
    91  			}
    92  
    93  			for _, key := range toDelete {
    94  				delete(rt.elements, key)
    95  			}
    96  			rt.elemMux.Unlock()
    97  		}
    98  	}
    99  }
   100  
   101  // Put introduces tracked element along with its TTL and release callback
   102  func (rt *RefTracker) Put(key string, ttl time.Duration, releaseCallback func()) {
   103  	elem := &trackedElement{
   104  		lastReleased:    time.Now(),
   105  		ttl:             ttl,
   106  		releaseCallback: releaseCallback,
   107  		refCount:        0,
   108  	}
   109  
   110  	rt.elemMux.Lock()
   111  	defer rt.elemMux.Unlock()
   112  
   113  	_, ok := rt.elements[key]
   114  	if !ok {
   115  		rt.elements[key] = elem
   116  	}
   117  }
   118  
   119  // Incr increments reference count of an element
   120  func (rt *RefTracker) Incr(key string) error {
   121  	rt.elemMux.Lock()
   122  	defer rt.elemMux.Unlock()
   123  
   124  	elem, ok := rt.elements[key]
   125  	if !ok {
   126  		return ErrNotFound
   127  	}
   128  
   129  	elem.refCount++
   130  
   131  	return nil
   132  }
   133  
   134  // Decr decrements reference count of an element
   135  func (rt *RefTracker) Decr(key string) error {
   136  	rt.elemMux.Lock()
   137  	defer rt.elemMux.Unlock()
   138  
   139  	elem, ok := rt.elements[key]
   140  	if !ok {
   141  		return ErrNotFound
   142  	}
   143  
   144  	elem.refCount--
   145  
   146  	if elem.refCount < 0 {
   147  		panic("negative reference count in RefTracker element!")
   148  	}
   149  
   150  	if elem.refCount == 0 {
   151  		elem.lastReleased = time.Now()
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  // Close stops patrol loop
   158  func (rt *RefTracker) Close() {
   159  	rt.stopOnce.Do(func() {
   160  		close(rt.stop)
   161  	})
   162  }