vitess.io/vitess@v0.16.2/go/pools/numbered.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package pools
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"time"
    23  
    24  	"vitess.io/vitess/go/cache"
    25  )
    26  
    27  // Numbered allows you to manage resources by tracking them with numbers.
    28  // There are no interface restrictions on what you can track.
    29  type Numbered struct {
    30  	mu                   sync.Mutex
    31  	empty                *sync.Cond // Broadcast when pool becomes empty
    32  	resources            map[int64]*numberedWrapper
    33  	recentlyUnregistered *cache.LRUCache
    34  }
    35  
    36  type numberedWrapper struct {
    37  	val     any
    38  	inUse   bool
    39  	purpose string
    40  }
    41  
    42  type unregistered struct {
    43  	reason           string
    44  	timeUnregistered time.Time
    45  }
    46  
    47  // NewNumbered creates a new numbered
    48  func NewNumbered() *Numbered {
    49  	n := &Numbered{
    50  		resources: make(map[int64]*numberedWrapper),
    51  		recentlyUnregistered: cache.NewLRUCache(1000, func(_ any) int64 {
    52  			return 1
    53  		}),
    54  	}
    55  	n.empty = sync.NewCond(&n.mu)
    56  	return n
    57  }
    58  
    59  // Register starts tracking a resource by the supplied id.
    60  // It does not lock the object.
    61  // It returns an error if the id already exists.
    62  func (nu *Numbered) Register(id int64, val any) error {
    63  	// Optimistically assume we're not double registering.
    64  	resource := &numberedWrapper{
    65  		val: val,
    66  	}
    67  
    68  	nu.mu.Lock()
    69  	defer nu.mu.Unlock()
    70  
    71  	_, ok := nu.resources[id]
    72  	if ok {
    73  		return fmt.Errorf("already present")
    74  	}
    75  	nu.resources[id] = resource
    76  	return nil
    77  }
    78  
    79  // Unregister forgets the specified resource.  If the resource is not present, it's ignored.
    80  func (nu *Numbered) Unregister(id int64, reason string) {
    81  	success := nu.unregister(id)
    82  	if success {
    83  		nu.recentlyUnregistered.Set(
    84  			fmt.Sprintf("%v", id), &unregistered{reason: reason, timeUnregistered: time.Now()})
    85  	}
    86  }
    87  
    88  // unregister forgets the resource, if it exists. Returns whether or not the resource existed at
    89  // time of Unregister.
    90  func (nu *Numbered) unregister(id int64) bool {
    91  	nu.mu.Lock()
    92  	defer nu.mu.Unlock()
    93  
    94  	_, ok := nu.resources[id]
    95  	delete(nu.resources, id)
    96  	if len(nu.resources) == 0 {
    97  		nu.empty.Broadcast()
    98  	}
    99  	return ok
   100  }
   101  
   102  // Get locks the resource for use. It accepts a purpose as a string.
   103  // If it cannot be found, it returns a "not found" error. If in use,
   104  // it returns a "in use: purpose" error.
   105  func (nu *Numbered) Get(id int64, purpose string) (val any, err error) {
   106  	nu.mu.Lock()
   107  	defer nu.mu.Unlock()
   108  	nw, ok := nu.resources[id]
   109  	if !ok {
   110  		if val, ok := nu.recentlyUnregistered.Get(fmt.Sprintf("%v", id)); ok {
   111  			unreg := val.(*unregistered)
   112  			return nil, fmt.Errorf("ended at %v (%v)", unreg.timeUnregistered.Format("2006-01-02 15:04:05.000 MST"), unreg.reason)
   113  		}
   114  		return nil, fmt.Errorf("not found")
   115  	}
   116  	if nw.inUse {
   117  		return nil, fmt.Errorf("in use: %s", nw.purpose)
   118  	}
   119  	nw.inUse = true
   120  	nw.purpose = purpose
   121  	return nw.val, nil
   122  }
   123  
   124  // Put unlocks a resource for someone else to use.
   125  func (nu *Numbered) Put(id int64) bool {
   126  	nu.mu.Lock()
   127  	defer nu.mu.Unlock()
   128  	if nw, ok := nu.resources[id]; ok {
   129  		nw.inUse = false
   130  		nw.purpose = ""
   131  		return true
   132  	}
   133  	return false
   134  }
   135  
   136  // GetAll returns the list of all resources in the pool.
   137  func (nu *Numbered) GetAll() (vals []any) {
   138  	nu.mu.Lock()
   139  	defer nu.mu.Unlock()
   140  	vals = make([]any, 0, len(nu.resources))
   141  	for _, nw := range nu.resources {
   142  		vals = append(vals, nw.val)
   143  	}
   144  	return vals
   145  }
   146  
   147  // GetByFilter returns a list of resources that match the filter.
   148  // It does not return any resources that are already locked.
   149  func (nu *Numbered) GetByFilter(purpose string, match func(val any) bool) (vals []any) {
   150  	nu.mu.Lock()
   151  	defer nu.mu.Unlock()
   152  	for _, nw := range nu.resources {
   153  		if nw.inUse {
   154  			continue
   155  		}
   156  		if match(nw.val) {
   157  			nw.inUse = true
   158  			nw.purpose = purpose
   159  			vals = append(vals, nw.val)
   160  		}
   161  	}
   162  	return vals
   163  }
   164  
   165  // WaitForEmpty returns as soon as the pool becomes empty
   166  func (nu *Numbered) WaitForEmpty() {
   167  	nu.mu.Lock()
   168  	defer nu.mu.Unlock()
   169  	for len(nu.resources) != 0 {
   170  		nu.empty.Wait()
   171  	}
   172  }
   173  
   174  // StatsJSON returns stats in JSON format
   175  func (nu *Numbered) StatsJSON() string {
   176  	return fmt.Sprintf("{\"Size\": %v}", nu.Size())
   177  }
   178  
   179  // Size returns the current size
   180  func (nu *Numbered) Size() int64 {
   181  	nu.mu.Lock()
   182  	defer nu.mu.Unlock()
   183  	return int64(len(nu.resources))
   184  }