github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/pubsub/pubsub.go (about)

     1  // Copyright (c) 2015-2024 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package pubsub
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/json"
    23  	"fmt"
    24  	"sync"
    25  	"sync/atomic"
    26  )
    27  
    28  // GetByteBuffer returns a byte buffer from the pool.
    29  var GetByteBuffer = func() []byte {
    30  	return make([]byte, 0, 4096)
    31  }
    32  
    33  // Sub - subscriber entity.
    34  type Sub[T Maskable] struct {
    35  	ch     chan T
    36  	types  Mask
    37  	filter func(entry T) bool
    38  }
    39  
    40  // PubSub holds publishers and subscribers
    41  type PubSub[T Maskable, M Maskable] struct {
    42  	// atomics, keep at top:
    43  	types          uint64
    44  	numSubscribers int32
    45  	maxSubscribers int32
    46  
    47  	// not atomics:
    48  	subs []*Sub[T]
    49  	sync.RWMutex
    50  }
    51  
    52  // Publish message to the subscribers.
    53  // Note that publish is always non-blocking send so that we don't block on slow receivers.
    54  // Hence receivers should use buffered channel so as not to miss the published events.
    55  func (ps *PubSub[T, M]) Publish(item T) {
    56  	ps.RLock()
    57  	defer ps.RUnlock()
    58  	for _, sub := range ps.subs {
    59  		if sub.types.Contains(Mask(item.Mask())) && (sub.filter == nil || sub.filter(item)) {
    60  			select {
    61  			case sub.ch <- item:
    62  			default:
    63  			}
    64  		}
    65  	}
    66  }
    67  
    68  // Subscribe - Adds a subscriber to pubsub system
    69  func (ps *PubSub[T, M]) Subscribe(mask M, subCh chan T, doneCh <-chan struct{}, filter func(entry T) bool) error {
    70  	totalSubs := atomic.AddInt32(&ps.numSubscribers, 1)
    71  	if ps.maxSubscribers > 0 && totalSubs > ps.maxSubscribers {
    72  		atomic.AddInt32(&ps.numSubscribers, -1)
    73  		return fmt.Errorf("the limit of `%d` subscribers is reached", ps.maxSubscribers)
    74  	}
    75  	ps.Lock()
    76  	defer ps.Unlock()
    77  
    78  	sub := &Sub[T]{ch: subCh, types: Mask(mask.Mask()), filter: filter}
    79  	ps.subs = append(ps.subs, sub)
    80  
    81  	// We hold a lock, so we are safe to update
    82  	combined := Mask(atomic.LoadUint64(&ps.types))
    83  	combined.Merge(Mask(mask.Mask()))
    84  	atomic.StoreUint64(&ps.types, uint64(combined))
    85  
    86  	go func() {
    87  		<-doneCh
    88  
    89  		ps.Lock()
    90  		defer ps.Unlock()
    91  		var remainTypes Mask
    92  		for i, s := range ps.subs {
    93  			if s == sub {
    94  				ps.subs = append(ps.subs[:i], ps.subs[i+1:]...)
    95  			} else {
    96  				remainTypes.Merge(s.types)
    97  			}
    98  		}
    99  		atomic.StoreUint64(&ps.types, uint64(remainTypes))
   100  		atomic.AddInt32(&ps.numSubscribers, -1)
   101  	}()
   102  
   103  	return nil
   104  }
   105  
   106  // SubscribeJSON - Adds a subscriber to pubsub system and returns results with JSON encoding.
   107  func (ps *PubSub[T, M]) SubscribeJSON(mask M, subCh chan<- []byte, doneCh <-chan struct{}, filter func(entry T) bool, wg *sync.WaitGroup) error {
   108  	totalSubs := atomic.AddInt32(&ps.numSubscribers, 1)
   109  	if ps.maxSubscribers > 0 && totalSubs > ps.maxSubscribers {
   110  		atomic.AddInt32(&ps.numSubscribers, -1)
   111  		return fmt.Errorf("the limit of `%d` subscribers is reached", ps.maxSubscribers)
   112  	}
   113  	ps.Lock()
   114  	defer ps.Unlock()
   115  	subChT := make(chan T, 10000)
   116  	sub := &Sub[T]{ch: subChT, types: Mask(mask.Mask()), filter: filter}
   117  	ps.subs = append(ps.subs, sub)
   118  
   119  	// We hold a lock, so we are safe to update
   120  	combined := Mask(atomic.LoadUint64(&ps.types))
   121  	combined.Merge(Mask(mask.Mask()))
   122  	atomic.StoreUint64(&ps.types, uint64(combined))
   123  	if wg != nil {
   124  		wg.Add(1)
   125  	}
   126  	go func() {
   127  		defer func() {
   128  			if wg != nil {
   129  				wg.Done()
   130  			}
   131  			// Clean up and de-register the subscriber
   132  			ps.Lock()
   133  			defer ps.Unlock()
   134  			var remainTypes Mask
   135  			for i, s := range ps.subs {
   136  				if s == sub {
   137  					ps.subs = append(ps.subs[:i], ps.subs[i+1:]...)
   138  				} else {
   139  					remainTypes.Merge(s.types)
   140  				}
   141  			}
   142  			atomic.StoreUint64(&ps.types, uint64(remainTypes))
   143  			atomic.AddInt32(&ps.numSubscribers, -1)
   144  		}()
   145  
   146  		// Read from subChT and write to subCh
   147  		var buf bytes.Buffer
   148  		enc := json.NewEncoder(&buf)
   149  		for {
   150  			select {
   151  			case <-doneCh:
   152  				return
   153  			case v, ok := <-subChT:
   154  				if !ok {
   155  					return
   156  				}
   157  				buf.Reset()
   158  				err := enc.Encode(v)
   159  				if err != nil {
   160  					return
   161  				}
   162  
   163  				select {
   164  				case subCh <- append(GetByteBuffer()[:0], buf.Bytes()...):
   165  					continue
   166  				case <-doneCh:
   167  					return
   168  				}
   169  			}
   170  		}
   171  	}()
   172  
   173  	return nil
   174  }
   175  
   176  // NumSubscribers returns the number of current subscribers,
   177  // The mask is checked against the active subscribed types,
   178  // and 0 will be returned if nobody is subscribed for the type(s).
   179  func (ps *PubSub[T, M]) NumSubscribers(mask M) int32 {
   180  	types := Mask(atomic.LoadUint64(&ps.types))
   181  	if !types.Overlaps(Mask(mask.Mask())) {
   182  		return 0
   183  	}
   184  	return atomic.LoadInt32(&ps.numSubscribers)
   185  }
   186  
   187  // Subscribers returns the number of current subscribers for all types.
   188  func (ps *PubSub[T, M]) Subscribers() int32 {
   189  	return atomic.LoadInt32(&ps.numSubscribers)
   190  }
   191  
   192  // New inits a PubSub system with a limit of maximum
   193  // subscribers unless zero is specified
   194  func New[T Maskable, M Maskable](maxSubscribers int32) *PubSub[T, M] {
   195  	return &PubSub[T, M]{maxSubscribers: maxSubscribers}
   196  }