github.com/influx6/npkg@v0.8.8/natomic/signal.go (about)

     1  package natomic
     2  
     3  import (
     4  	"sync"
     5  	"sync/atomic"
     6  	"time"
     7  
     8  	"github.com/influx6/npkg/nerror"
     9  )
    10  
    11  //************************************************************************
    12  // Signals
    13  //************************************************************************
    14  
    15  // Signal defines a underline event representing some type and signal.
    16  //
    17  // The Type() return defines the unique means of identification
    18  // for this signal.
    19  type Signal interface {
    20  	Type() string
    21  	Target() string
    22  	Source() string
    23  }
    24  
    25  // SignalResponder defines an interface for sending signals
    26  // to it's implementer.
    27  type SignalResponder interface {
    28  	Respond(Signal)
    29  }
    30  
    31  // SignalRespondHandler defines a function type.
    32  type SignalRespondHandler func(Signal)
    33  
    34  // Respond implements the SignalResponder interface.
    35  func (sr SignalRespondHandler) Respond(s Signal) {
    36  	sr(s)
    37  }
    38  
    39  // SignalResponderNotification defines an interface defining
    40  // methods on a SignalResponder to deliver certain information
    41  // regarding it's addition, removal from a ResponderGroup.
    42  type SignalResponderNotification interface {
    43  	SignalResponder
    44  
    45  	OnAdded()
    46  	OnRemoved()
    47  }
    48  
    49  // SignalReceiver defines a host of signal delivery and
    50  // de-registration.
    51  type SignalReceiver interface {
    52  	SignalResponder
    53  
    54  	// Add adds new SignalResponder to the underline signal receiver.
    55  	// All signal provider to this SignalReceiver will be sent to the
    56  	// SignalResponder as well.
    57  	Add(SignalResponder) error
    58  
    59  	// Remove must remove the responder from the underline
    60  	// signal receiver.
    61  	Remove(SignalResponder) error
    62  }
    63  
    64  //*****************************************************
    65  // Atom
    66  //*****************************************************
    67  
    68  // Atom implements the Atom interface, implementing
    69  // the safe concurrent storing and reading of stored values
    70  // without the use of mutex and relying on the atomic.Value
    71  // construct, which is great for low-write and high-read usage.
    72  //
    73  // Atoms are concurrently safe to use both for getting/setting
    74  // value and adding interceptors.
    75  type Atom struct {
    76  	signals SignalReceiver
    77  	imx     sync.Mutex
    78  	store   *atomic.Value
    79  }
    80  
    81  // NewAtom returns a new instance of *Atom.
    82  func NewAtom(receiver SignalReceiver) *Atom {
    83  	var newStore atomic.Value
    84  	return &Atom{
    85  		signals: receiver,
    86  		store:   &newStore,
    87  	}
    88  }
    89  
    90  // Set attempts to set giving value into atom, if giving value is
    91  // not the same underline type as previous set calls, then an error is
    92  // returned.
    93  func (am *Atom) Set(val interface{}) error {
    94  	am.imx.Lock()
    95  	defer am.imx.Unlock()
    96  	am.store.Store(val)
    97  
    98  	if am.signals != nil {
    99  		am.signals.Respond(am)
   100  	}
   101  	return nil
   102  }
   103  
   104  // Source returns an empty string, has we have no source.
   105  func (am *Atom) Source() string {
   106  	return ""
   107  }
   108  
   109  // Target returns an empty string, has we have no target.
   110  func (am *Atom) Target() string {
   111  	return ""
   112  }
   113  
   114  // Type returns a giving string to represent the behaviour of
   115  // giving type.
   116  func (am *Atom) Type() string {
   117  	return "natomic.Atom"
   118  }
   119  
   120  // Read returns the giving value stored within giving atom.
   121  // It returns nil if no value was ever set.
   122  func (am *Atom) Read() interface{} {
   123  	return am.store.Load()
   124  }
   125  
   126  // Add adds a new SignalResponder into the atom responder
   127  // provider.
   128  func (am *Atom) Add(n SignalResponder) error {
   129  	if am.signals != nil {
   130  		return am.signals.Add(n)
   131  	}
   132  	return nil
   133  }
   134  
   135  // Remove removes giving SignalResponder from the atom responder
   136  // provider.
   137  func (am *Atom) Remove(n SignalResponder) error {
   138  	if am.signals != nil {
   139  		return am.signals.Remove(n)
   140  	}
   141  	return nil
   142  }
   143  
   144  // Respond delivers giving Signal to all interceptors
   145  // registered with giving atom.
   146  func (am *Atom) Respond(s Signal) {
   147  	if am.signals != nil {
   148  		am.signals.Respond(s)
   149  	}
   150  }
   151  
   152  //************************************************************************
   153  // ResponderGroup
   154  //************************************************************************
   155  
   156  // ResponderGroup defines a grouping and manager for SignalResponders, which manages
   157  // the addition, and removals, basically the management of the lifecycle of a set of
   158  // SignalResponders.
   159  type ResponderGroup struct {
   160  	sm         sync.Mutex
   161  	spawned    bool
   162  	waiter     sync.WaitGroup
   163  	closer     chan struct{}
   164  	actions    chan func()
   165  	responders map[SignalResponder]struct{}
   166  }
   167  
   168  // NewResponderGroup returns a new ResponderGroup instance.
   169  func NewResponderGroup() *ResponderGroup {
   170  	var rg ResponderGroup
   171  	rg.actions = make(chan func())
   172  	rg.closer = make(chan struct{})
   173  	rg.responders = map[SignalResponder]struct{}{}
   174  	return &rg
   175  }
   176  
   177  // Remove returns an error if it fails to deliver SignalResponder into
   178  // responder group.
   179  func (rg *ResponderGroup) Remove(r SignalResponder) error {
   180  	return rg.RemoveGuaranteed(r, nil)
   181  }
   182  
   183  // RemoveGuaranteed returns an error if it fails to deliver SignalResponder into
   184  // responder group.
   185  func (rg *ResponderGroup) RemoveGuaranteed(r SignalResponder, signal chan struct{}) error {
   186  	var action = func() {
   187  		if _, found := rg.responders[r]; found {
   188  			delete(rg.responders, r)
   189  			if irs, ok := r.(SignalResponderNotification); ok {
   190  				irs.OnRemoved()
   191  			}
   192  		}
   193  
   194  		if signal != nil {
   195  			signal <- struct{}{}
   196  		}
   197  	}
   198  
   199  	select {
   200  	case rg.actions <- action:
   201  		return nil
   202  	case <-time.After(time.Second):
   203  		return nerror.New("failed to remove responder")
   204  	}
   205  }
   206  
   207  // Add returns an error if it fails to deliver SignalResponder into
   208  // responder group.
   209  func (rg *ResponderGroup) Add(r SignalResponder) error {
   210  	return rg.AddGuaranteed(r, nil)
   211  }
   212  
   213  // AddGuaranteed returns an error if it fails to deliver SignalResponder into
   214  // responder group.
   215  func (rg *ResponderGroup) AddGuaranteed(r SignalResponder, signal chan struct{}) error {
   216  	var action = func() {
   217  		rg.responders[r] = struct{}{}
   218  		if irs, ok := r.(SignalResponderNotification); ok {
   219  			irs.OnAdded()
   220  		}
   221  
   222  		if signal != nil {
   223  			signal <- struct{}{}
   224  		}
   225  	}
   226  
   227  	select {
   228  	case rg.actions <- action:
   229  		return nil
   230  	case <-time.After(time.Second):
   231  		return nerror.New("failed to add responder")
   232  	}
   233  }
   234  
   235  // Respond notifies giving signal to all responders.
   236  func (rg *ResponderGroup) Respond(s Signal) {
   237  	rg.RespondGuaranteed(s, nil)
   238  }
   239  
   240  // RespondGuaranteed returns an error if it fails to deliver SignalResponder into
   241  // responder group.
   242  func (rg *ResponderGroup) RespondGuaranteed(s Signal, signal chan struct{}) {
   243  	var action = func() {
   244  		for responder := range rg.responders {
   245  			responder.Respond(s)
   246  		}
   247  		if signal != nil {
   248  			signal <- struct{}{}
   249  		}
   250  	}
   251  
   252  	rg.actions <- action
   253  }
   254  
   255  // Close closes giving responder group, removing all
   256  // it's subscription.
   257  func (rg *ResponderGroup) Close() {
   258  	rg.closer <- struct{}{}
   259  	rg.waiter.Wait()
   260  }
   261  
   262  // Start runs giving responder group.
   263  //
   264  // Start accepts possible many channels but takes the first one
   265  // and will deliver a signal to it to provide detail that it
   266  // as started.
   267  func (rg *ResponderGroup) Start(started ...chan struct{}) {
   268  	if rg.running() {
   269  		return
   270  	}
   271  
   272  	if rg.actions == nil {
   273  		rg.actions = make(chan func())
   274  	}
   275  	if rg.closer == nil {
   276  		rg.closer = make(chan struct{})
   277  	}
   278  	if rg.responders == nil {
   279  		rg.responders = map[SignalResponder]struct{}{}
   280  	}
   281  
   282  	if len(started) > 0 {
   283  		rg.manage(started[0])
   284  		return
   285  	}
   286  
   287  	rg.manage(nil)
   288  }
   289  
   290  // Wait blocks till giving ResponderGroup is closed.
   291  func (rg *ResponderGroup) Wait() {
   292  	rg.waiter.Wait()
   293  }
   294  
   295  func (rg *ResponderGroup) flip(t bool) {
   296  	rg.sm.Lock()
   297  	rg.spawned = t
   298  	rg.sm.Unlock()
   299  }
   300  
   301  func (rg *ResponderGroup) running() bool {
   302  	var running bool
   303  	rg.sm.Lock()
   304  	running = rg.spawned
   305  	rg.sm.Unlock()
   306  	return running
   307  }
   308  
   309  // manage setups the necessary goroutine for managing the underline
   310  // process for giving responder.
   311  //
   312  // If a channel is passed then the giving channel receives a
   313  // signal that giving goroutine as started.
   314  func (rg *ResponderGroup) manage(started chan struct{}) {
   315  	rg.flip(true)
   316  	rg.waiter.Add(1)
   317  
   318  	go func() {
   319  		defer rg.waiter.Done()
   320  		defer rg.flip(false)
   321  
   322  		if started != nil {
   323  			started <- struct{}{}
   324  		}
   325  
   326  		for {
   327  			select {
   328  			case <-rg.closer:
   329  				rg.responders = map[SignalResponder]struct{}{}
   330  				return
   331  			case action := <-rg.actions:
   332  				action()
   333  			}
   334  		}
   335  	}()
   336  }