github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/syncevent/broadcaster.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package syncevent 16 17 import ( 18 "github.com/nicocha30/gvisor-ligolo/pkg/sync" 19 ) 20 21 // Broadcaster is an implementation of Source that supports any number of 22 // subscribed Receivers. 23 // 24 // The zero value of Broadcaster is valid and has no subscribed Receivers. 25 // Broadcaster is not copyable by value. 26 // 27 // All Broadcaster methods may be called concurrently from multiple goroutines. 28 type Broadcaster struct { 29 // Broadcaster is implemented as a hash table where keys are assigned by 30 // the Broadcaster and returned as SubscriptionIDs, making it safe to use 31 // the identity function for hashing. The hash table resolves collisions 32 // using linear probing and features Robin Hood insertion and backward 33 // shift deletion in order to support a relatively high load factor 34 // efficiently, which matters since the cost of Broadcast is linear in the 35 // size of the table. 36 37 // mu protects the following fields. 38 mu sync.Mutex 39 40 // Invariants: len(table) is 0 or a power of 2. 41 table []broadcasterSlot 42 43 // load is the number of entries in table with receiver != nil. 44 load int 45 46 lastID SubscriptionID 47 } 48 49 type broadcasterSlot struct { 50 // Invariants: If receiver == nil, then filter == NoEvents and id == 0. 51 // Otherwise, id != 0. 52 receiver *Receiver 53 filter Set 54 id SubscriptionID 55 } 56 57 const ( 58 broadcasterMinNonZeroTableSize = 2 // must be a power of 2 > 1 59 60 broadcasterMaxLoadNum = 13 61 broadcasterMaxLoadDen = 16 62 ) 63 64 // SubscribeEvents implements Source.SubscribeEvents. 65 func (b *Broadcaster) SubscribeEvents(r *Receiver, filter Set) SubscriptionID { 66 b.mu.Lock() 67 68 // Assign an ID for this subscription. 69 b.lastID++ 70 id := b.lastID 71 72 // Expand the table if over the maximum load factor: 73 // 74 // load / len(b.table) > broadcasterMaxLoadNum / broadcasterMaxLoadDen 75 // load * broadcasterMaxLoadDen > broadcasterMaxLoadNum * len(b.table) 76 b.load++ 77 if (b.load * broadcasterMaxLoadDen) > (broadcasterMaxLoadNum * len(b.table)) { 78 // Double the number of slots in the new table. 79 newlen := broadcasterMinNonZeroTableSize 80 if len(b.table) != 0 { 81 newlen = 2 * len(b.table) 82 } 83 if newlen <= cap(b.table) { 84 // Reuse excess capacity in the current table, moving entries not 85 // already in their first-probed positions to better ones. 86 newtable := b.table[:newlen] 87 newmask := uint64(newlen - 1) 88 for i := range b.table { 89 if b.table[i].receiver != nil && uint64(b.table[i].id)&newmask != uint64(i) { 90 entry := b.table[i] 91 b.table[i] = broadcasterSlot{} 92 broadcasterTableInsert(newtable, entry.id, entry.receiver, entry.filter) 93 } 94 } 95 b.table = newtable 96 } else { 97 newtable := make([]broadcasterSlot, newlen) 98 // Copy existing entries to the new table. 99 for i := range b.table { 100 if b.table[i].receiver != nil { 101 broadcasterTableInsert(newtable, b.table[i].id, b.table[i].receiver, b.table[i].filter) 102 } 103 } 104 // Switch to the new table. 105 b.table = newtable 106 } 107 } 108 109 broadcasterTableInsert(b.table, id, r, filter) 110 b.mu.Unlock() 111 return id 112 } 113 114 // Preconditions: 115 // - table must not be full. 116 // - len(table) is a power of 2. 117 func broadcasterTableInsert(table []broadcasterSlot, id SubscriptionID, r *Receiver, filter Set) { 118 entry := broadcasterSlot{ 119 receiver: r, 120 filter: filter, 121 id: id, 122 } 123 mask := uint64(len(table) - 1) 124 i := uint64(id) & mask 125 disp := uint64(0) 126 for { 127 if table[i].receiver == nil { 128 table[i] = entry 129 return 130 } 131 // If we've been displaced farther from our first-probed slot than the 132 // element stored in this one, swap elements and switch to inserting 133 // the replaced one. (This is Robin Hood insertion.) 134 slotDisp := (i - uint64(table[i].id)) & mask 135 if disp > slotDisp { 136 table[i], entry = entry, table[i] 137 disp = slotDisp 138 } 139 i = (i + 1) & mask 140 disp++ 141 } 142 } 143 144 // UnsubscribeEvents implements Source.UnsubscribeEvents. 145 func (b *Broadcaster) UnsubscribeEvents(id SubscriptionID) { 146 b.mu.Lock() 147 148 mask := uint64(len(b.table) - 1) 149 i := uint64(id) & mask 150 for { 151 if b.table[i].id == id { 152 // Found the element to remove. Move all subsequent elements 153 // backward until we either find an empty slot, or an element that 154 // is already in its first-probed slot. (This is backward shift 155 // deletion.) 156 for { 157 next := (i + 1) & mask 158 if b.table[next].receiver == nil { 159 break 160 } 161 if uint64(b.table[next].id)&mask == next { 162 break 163 } 164 b.table[i] = b.table[next] 165 i = next 166 } 167 b.table[i] = broadcasterSlot{} 168 break 169 } 170 i = (i + 1) & mask 171 } 172 173 // If a table 1/4 of the current size would still be at or under the 174 // maximum load factor (i.e. the current table size is at least two 175 // expansions bigger than necessary), halve the size of the table to reduce 176 // the cost of Broadcast. Since we are concerned with iteration time and 177 // not memory usage, reuse the existing slice to reduce future allocations 178 // from table re-expansion. 179 b.load-- 180 if len(b.table) > broadcasterMinNonZeroTableSize && (b.load*(4*broadcasterMaxLoadDen)) <= (broadcasterMaxLoadNum*len(b.table)) { 181 newlen := len(b.table) / 2 182 newtable := b.table[:newlen] 183 for i := newlen; i < len(b.table); i++ { 184 if b.table[i].receiver != nil { 185 broadcasterTableInsert(newtable, b.table[i].id, b.table[i].receiver, b.table[i].filter) 186 b.table[i] = broadcasterSlot{} 187 } 188 } 189 b.table = newtable 190 } 191 192 b.mu.Unlock() 193 } 194 195 // Broadcast notifies all Receivers subscribed to the Broadcaster of the subset 196 // of events to which they subscribed. The order in which Receivers are 197 // notified is unspecified. 198 func (b *Broadcaster) Broadcast(events Set) { 199 b.mu.Lock() 200 for i := range b.table { 201 if intersection := events & b.table[i].filter; intersection != 0 { 202 // We don't need to check if broadcasterSlot.receiver is nil, since 203 // if it is then broadcasterSlot.filter is 0. 204 b.table[i].receiver.Notify(intersection) 205 } 206 } 207 b.mu.Unlock() 208 } 209 210 // FilteredEvents returns the set of events for which Broadcast will notify at 211 // least one Receiver, i.e. the union of filters for all subscribed Receivers. 212 func (b *Broadcaster) FilteredEvents() Set { 213 var es Set 214 b.mu.Lock() 215 for i := range b.table { 216 es |= b.table[i].filter 217 } 218 b.mu.Unlock() 219 return es 220 }