github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/pkg/notify/notify.go (about)

     1  // Copyright 2020 PingCAP, Inc.
     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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package notify
    15  
    16  import (
    17  	"sync"
    18  	"time"
    19  
    20  	"github.com/pingcap/ticdc/pkg/errors"
    21  )
    22  
    23  // Notifier provides a one-to-many notification mechanism
    24  type Notifier struct {
    25  	receivers []struct {
    26  		rec   *Receiver
    27  		index int
    28  	}
    29  	maxIndex int
    30  	mu       sync.RWMutex
    31  	closed   bool
    32  }
    33  
    34  // Notify sends a signal to the Receivers
    35  func (n *Notifier) Notify() {
    36  	n.mu.RLock()
    37  	defer n.mu.RUnlock()
    38  	for _, receiver := range n.receivers {
    39  		receiver.rec.signalNonBlocking()
    40  	}
    41  }
    42  
    43  // Receiver is a receiver of notifier, including the receiver channel and stop receiver function.
    44  type Receiver struct {
    45  	C       <-chan struct{}
    46  	c       chan struct{}
    47  	Stop    func()
    48  	ticker  *time.Ticker
    49  	closeCh chan struct{}
    50  }
    51  
    52  // returns true if the receiverCh should be closed
    53  func (r *Receiver) signalNonBlocking() bool {
    54  	select {
    55  	case <-r.closeCh:
    56  		return true
    57  	case r.c <- struct{}{}:
    58  	default:
    59  	}
    60  	return false
    61  }
    62  
    63  func (r *Receiver) signalTickLoop() {
    64  	go func() {
    65  	loop:
    66  		for {
    67  			select {
    68  			case <-r.closeCh:
    69  				break
    70  			case <-r.ticker.C:
    71  			}
    72  			exit := r.signalNonBlocking()
    73  			if exit {
    74  				break loop
    75  			}
    76  		}
    77  		close(r.c)
    78  	}()
    79  }
    80  
    81  // NewReceiver creates a receiver
    82  // returns a channel to receive notifications and a function to close this receiver
    83  func (n *Notifier) NewReceiver(tickTime time.Duration) (*Receiver, error) {
    84  	n.mu.Lock()
    85  	defer n.mu.Unlock()
    86  	if n.closed {
    87  		return nil, errors.ErrOperateOnClosedNotifier.GenWithStackByArgs()
    88  	}
    89  	currentIndex := n.maxIndex
    90  	n.maxIndex++
    91  	receiverCh := make(chan struct{}, 1)
    92  	closeCh := make(chan struct{})
    93  	var ticker *time.Ticker
    94  	if tickTime > 0 {
    95  		ticker = time.NewTicker(tickTime)
    96  	}
    97  	rec := &Receiver{
    98  		C: receiverCh,
    99  		c: receiverCh,
   100  		Stop: func() {
   101  			n.remove(currentIndex)
   102  		},
   103  		ticker:  ticker,
   104  		closeCh: closeCh,
   105  	}
   106  	if tickTime > 0 {
   107  		rec.signalTickLoop()
   108  	}
   109  	n.receivers = append(n.receivers, struct {
   110  		rec   *Receiver
   111  		index int
   112  	}{rec: rec, index: currentIndex})
   113  	return rec, nil
   114  }
   115  
   116  func (n *Notifier) remove(index int) {
   117  	n.mu.Lock()
   118  	defer n.mu.Unlock()
   119  	for i, receiver := range n.receivers {
   120  		if receiver.index == index {
   121  			n.receivers = append(n.receivers[:i], n.receivers[i+1:]...)
   122  			close(receiver.rec.closeCh)
   123  			if receiver.rec.ticker != nil {
   124  				receiver.rec.ticker.Stop()
   125  			}
   126  			break
   127  		}
   128  	}
   129  }
   130  
   131  // Close closes the notify and stops all receiver in this notifier
   132  // Note we must `Close` the notifier if we can't ensure each receiver of this
   133  // notifier is called `Stop` in order to prevent goroutine leak.
   134  func (n *Notifier) Close() {
   135  	n.mu.Lock()
   136  	defer n.mu.Unlock()
   137  	for _, receiver := range n.receivers {
   138  		if receiver.rec.ticker != nil {
   139  			receiver.rec.ticker.Stop()
   140  		}
   141  		close(receiver.rec.closeCh)
   142  	}
   143  	n.receivers = nil
   144  	n.closed = true
   145  }