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 ®istry{ 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 }