github.com/xmidt-org/webpa-common@v1.11.9/device/registry.go (about)

     1  package device
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  
     7  	"github.com/go-kit/kit/log"
     8  	"github.com/xmidt-org/webpa-common/xmetrics"
     9  )
    10  
    11  var errDeviceLimitReached = errors.New("Device limit reached")
    12  
    13  type registryOptions struct {
    14  	Logger          log.Logger
    15  	Limit           int
    16  	InitialCapacity int
    17  	Measures        Measures
    18  }
    19  
    20  // registry is the internal lookup map for devices.  it is bounded by an optional maximum number
    21  // of connected devices.
    22  type registry struct {
    23  	logger          log.Logger
    24  	lock            sync.RWMutex
    25  	limit           int
    26  	initialCapacity int
    27  	data            map[ID]*device
    28  
    29  	count        xmetrics.Setter
    30  	limitReached xmetrics.Incrementer
    31  	connect      xmetrics.Incrementer
    32  	disconnect   xmetrics.Adder
    33  	duplicates   xmetrics.Incrementer
    34  }
    35  
    36  func newRegistry(o registryOptions) *registry {
    37  	if o.InitialCapacity < 1 {
    38  		o.InitialCapacity = 10
    39  	}
    40  
    41  	return &registry{
    42  		logger:          o.Logger,
    43  		initialCapacity: o.InitialCapacity,
    44  		data:            make(map[ID]*device, o.InitialCapacity),
    45  		limit:           o.Limit,
    46  		count:           o.Measures.Device,
    47  		limitReached:    o.Measures.LimitReached,
    48  		connect:         o.Measures.Connect,
    49  		disconnect:      o.Measures.Disconnect,
    50  		duplicates:      o.Measures.Duplicates,
    51  	}
    52  }
    53  
    54  // len returns the size of this registry
    55  func (r *registry) len() int {
    56  	r.lock.RLock()
    57  	l := len(r.data)
    58  	r.lock.RUnlock()
    59  
    60  	return l
    61  }
    62  
    63  // add uses a factory function to create a new device atomically with modifying
    64  // the registry
    65  func (r *registry) add(newDevice *device) error {
    66  	id := newDevice.ID()
    67  	r.lock.Lock()
    68  
    69  	existing := r.data[id]
    70  	if existing == nil && r.limit > 0 && (len(r.data)+1) > r.limit {
    71  		// adding this would result in exceeding the limit
    72  		r.lock.Unlock()
    73  		r.limitReached.Inc()
    74  		r.disconnect.Add(1.0)
    75  		newDevice.requestClose(CloseReason{Err: errDeviceLimitReached, Text: "device-limit-reached"})
    76  		return errDeviceLimitReached
    77  	}
    78  
    79  	// this will either leave the count the same or add 1 to it ...
    80  	r.data[id] = newDevice
    81  	r.count.Set(float64(len(r.data)))
    82  	r.lock.Unlock()
    83  
    84  	if existing != nil {
    85  		r.disconnect.Add(1.0)
    86  		r.duplicates.Inc()
    87  		newDevice.Statistics().AddDuplications(existing.Statistics().Duplications() + 1)
    88  		existing.requestClose(CloseReason{Text: "duplicate"})
    89  	}
    90  
    91  	r.connect.Inc()
    92  	return nil
    93  }
    94  
    95  func (r *registry) remove(id ID, reason CloseReason) (*device, bool) {
    96  	r.lock.Lock()
    97  	existing, ok := r.data[id]
    98  	if ok {
    99  		delete(r.data, id)
   100  	}
   101  
   102  	r.count.Set(float64(len(r.data)))
   103  	r.lock.Unlock()
   104  
   105  	if existing != nil {
   106  		r.disconnect.Add(1.0)
   107  		existing.requestClose(reason)
   108  	}
   109  
   110  	return existing, ok
   111  }
   112  
   113  func (r *registry) removeIf(f func(d *device) (CloseReason, bool)) int {
   114  	// first, gather up all the devices that match the predicate
   115  	matched := make([]*device, 0, 100)
   116  	reasons := make([]CloseReason, 0, 100)
   117  
   118  	r.lock.RLock()
   119  	for _, d := range r.data {
   120  		if reason, ok := f(d); ok {
   121  			matched = append(matched, d)
   122  			reasons = append(reasons, reason)
   123  		}
   124  	}
   125  
   126  	r.lock.RUnlock()
   127  
   128  	if len(matched) == 0 {
   129  		return 0
   130  	}
   131  
   132  	// now, remove each device one at a time, releasing the write
   133  	// lock in between
   134  	count := 0
   135  	for i, d := range matched {
   136  		r.lock.Lock()
   137  
   138  		// allow for barging
   139  		_, ok := r.data[d.ID()]
   140  		if ok {
   141  			delete(r.data, d.ID())
   142  			r.count.Set(float64(len(r.data)))
   143  		}
   144  
   145  		r.lock.Unlock()
   146  
   147  		if ok {
   148  			count++
   149  			d.requestClose(reasons[i])
   150  		}
   151  	}
   152  
   153  	if count > 0 {
   154  		r.disconnect.Add(float64(count))
   155  	}
   156  
   157  	return count
   158  }
   159  
   160  func (r *registry) removeAll(reason CloseReason) int {
   161  	r.lock.Lock()
   162  	original := r.data
   163  	r.data = make(map[ID]*device, r.initialCapacity)
   164  	r.count.Set(0.0)
   165  	r.lock.Unlock()
   166  
   167  	count := len(original)
   168  	for _, d := range original {
   169  		d.requestClose(reason)
   170  	}
   171  
   172  	r.disconnect.Add(float64(count))
   173  	return count
   174  }
   175  
   176  func (r *registry) visit(f func(d *device) bool) int {
   177  	defer r.lock.RUnlock()
   178  	r.lock.RLock()
   179  
   180  	visited := 0
   181  	for _, d := range r.data {
   182  		visited++
   183  		if !f(d) {
   184  			break
   185  		}
   186  	}
   187  
   188  	return visited
   189  }
   190  
   191  func (r *registry) get(id ID) (*device, bool) {
   192  	r.lock.RLock()
   193  	existing, ok := r.data[id]
   194  	r.lock.RUnlock()
   195  
   196  	return existing, ok
   197  }