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  }