github.com/searKing/golang/go@v1.2.117/sync/subject.go (about)

     1  // Copyright 2022 The searKing Author. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package sync
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"sync"
    11  
    12  	"github.com/searKing/golang/go/errors"
    13  	"github.com/searKing/golang/go/pragma"
    14  	"github.com/searKing/golang/go/sync/atomic"
    15  )
    16  
    17  // Subject implements a condition variable like with channel, a rendezvous point
    18  // for goroutines waiting for or announcing the occurrence
    19  // of an event.
    20  //
    21  // The caller typically cannot assume that the condition is true when
    22  // Subscribe chan returns. Instead, the caller should Wait in a loop:
    23  //
    24  //	time.After(timeout, c.PublishBroadcast()) // for timeout or periodic event
    25  //	c.PublishBroadcast() // for async notify event directly
    26  //	eventC, cancel := c.Subscribe()
    27  //	for !condition() {
    28  //	    select{
    29  //	    case event, closed := <- eventC:
    30  //	        ... make use of event ...
    31  //	    }
    32  //	}
    33  //	... make use of condition ...
    34  type Subject struct {
    35  	noCopy pragma.DoNotCopy
    36  
    37  	mu          sync.Mutex
    38  	subscribers map[*subscriber]struct{}
    39  
    40  	inShutdown atomic.Bool // true when when server is in shutdown
    41  }
    42  
    43  type subscriber struct {
    44  	mu   sync.Mutex // guard close of channel msgC
    45  	msgC chan any
    46  
    47  	once  sync.Once
    48  	doneC chan struct{} // closed when subscriber is in shutdown, like removed.
    49  }
    50  
    51  func (s *subscriber) Shutdown() {
    52  	if s == nil {
    53  		return
    54  	}
    55  	s.once.Do(func() {
    56  		close(s.doneC)
    57  		s.mu.Lock()
    58  		defer s.mu.Unlock()
    59  		close(s.msgC)
    60  	})
    61  }
    62  
    63  // publish wakes a listener waiting on c to consume the event.
    64  // event will be dropped if ctx is Done before event is received.
    65  func (s *subscriber) publish(ctx context.Context, event any) error {
    66  	// guard of msgC's close
    67  	s.mu.Lock()
    68  	defer s.mu.Unlock()
    69  	select {
    70  	case <-ctx.Done():
    71  		// event dropped because of publisher
    72  		return ctx.Err()
    73  	case <-s.doneC:
    74  		// event dropped because of subscriber
    75  		return fmt.Errorf("event dropped because of subscriber unsubscribed")
    76  	default:
    77  	}
    78  
    79  	select {
    80  	case <-ctx.Done():
    81  		// event dropped because of publisher
    82  		return ctx.Err()
    83  	case <-s.doneC:
    84  		// event dropped because of subscriber
    85  		return fmt.Errorf("event dropped because of subscriber unsubscribed")
    86  	case s.msgC <- event:
    87  		// event consumed
    88  		return nil
    89  	}
    90  }
    91  
    92  // Subscribe returns a channel that's closed when awoken by PublishSignal or PublishBroadcast.
    93  // never be canceled. Successive calls to Subscribe return different values.
    94  // The close of the Subscribe channel may happen asynchronously,
    95  // after the cancel function returns.
    96  func (s *Subject) Subscribe() (<-chan any, context.CancelFunc) {
    97  	listener := &subscriber{
    98  		msgC:  make(chan any),
    99  		doneC: make(chan struct{}),
   100  	}
   101  	s.trackChannel(listener, true)
   102  	return listener.msgC, func() {
   103  		s.trackChannel(listener, false)
   104  	}
   105  }
   106  
   107  // PublishSignal wakes one listener waiting on c, if there is any.
   108  // PublishSignal blocks until event is received or dropped.
   109  func (s *Subject) PublishSignal(ctx context.Context, event any) error {
   110  	s.mu.Lock()
   111  	defer s.mu.Unlock()
   112  	var wg sync.WaitGroup
   113  	var errs []error
   114  	for listener := range s.subscribers {
   115  		wg.Add(1)
   116  		go func(listener *subscriber) {
   117  			defer wg.Done()
   118  			err := listener.publish(ctx, event)
   119  			if err != nil {
   120  				errs = append(errs, err)
   121  			}
   122  		}(listener)
   123  		break
   124  	}
   125  	wg.Wait()
   126  	return errors.Multi(errs...)
   127  }
   128  
   129  // PublishBroadcast wakes all listeners waiting on c.
   130  // PublishBroadcast blocks until event is received or dropped.
   131  // event will be dropped if ctx is Done before event is received.
   132  func (s *Subject) PublishBroadcast(ctx context.Context, event any) error {
   133  	var wg sync.WaitGroup
   134  	var errs []error
   135  	func() {
   136  		s.mu.Lock()
   137  		defer s.mu.Unlock()
   138  		for listener := range s.subscribers {
   139  			wg.Add(1)
   140  			go func(listener *subscriber) {
   141  				defer wg.Done()
   142  				err := listener.publish(ctx, event)
   143  				if err != nil {
   144  					errs = append(errs, err)
   145  				}
   146  			}(listener)
   147  		}
   148  	}()
   149  	wg.Wait()
   150  	return errors.Multi(errs...)
   151  }
   152  
   153  func (s *Subject) trackChannel(c *subscriber, add bool) {
   154  	func() {
   155  		s.mu.Lock()
   156  		defer s.mu.Unlock()
   157  		if s.subscribers == nil {
   158  			s.subscribers = make(map[*subscriber]struct{})
   159  		}
   160  		_, has := s.subscribers[c]
   161  		if has {
   162  			if add {
   163  				return
   164  			}
   165  			delete(s.subscribers, c)
   166  			return
   167  		}
   168  		if add {
   169  			s.subscribers[c] = struct{}{}
   170  		}
   171  	}()
   172  	if !add {
   173  		c.Shutdown()
   174  	}
   175  }