github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/watchdog/generator.go (about)

     1  // Copyright 2020 Insolar Network Ltd.
     2  // All rights reserved.
     3  // This material is licensed under the Insolar License version 1.0,
     4  // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md.
     5  
     6  package watchdog
     7  
     8  import (
     9  	"math"
    10  	"runtime"
    11  	"sync/atomic"
    12  	"time"
    13  )
    14  
    15  type HeartbeatGeneratorFactory interface {
    16  	CreateGenerator(name string) *HeartbeatGenerator
    17  }
    18  
    19  func NewHeartbeatGenerator(id HeartbeatID, heartbeatPeriod time.Duration, out chan<- Heartbeat) HeartbeatGenerator {
    20  	return NewHeartbeatGeneratorWithRetries(id, heartbeatPeriod, 0, out)
    21  }
    22  
    23  func NewHeartbeatGeneratorWithRetries(id HeartbeatID, heartbeatPeriod time.Duration, retryCount uint8, out chan<- Heartbeat) HeartbeatGenerator {
    24  	attempts := retryCount
    25  	if out == nil {
    26  		attempts = 0
    27  	} else if attempts < math.MaxUint8 {
    28  		attempts++
    29  	}
    30  
    31  	period := uint32(0)
    32  	switch {
    33  	case heartbeatPeriod < 0:
    34  		panic("illegal value")
    35  	case heartbeatPeriod == 0:
    36  		break
    37  	case heartbeatPeriod <= time.Millisecond:
    38  		period = 1
    39  	default:
    40  		heartbeatPeriod /= time.Millisecond
    41  		if heartbeatPeriod > math.MaxUint32 {
    42  			period = math.MaxUint32
    43  		} else {
    44  			period = uint32(heartbeatPeriod)
    45  		}
    46  	}
    47  
    48  	return HeartbeatGenerator{id: id, heartbeatPeriod: period, sendAttempts: attempts, out: out}
    49  }
    50  
    51  type HeartbeatGenerator struct {
    52  	id              HeartbeatID
    53  	heartbeatPeriod uint32
    54  	atomicNano      int64
    55  	sendAttempts    uint8
    56  	//name string
    57  	out chan<- Heartbeat
    58  }
    59  
    60  func (g *HeartbeatGenerator) Heartbeat() {
    61  	g.ForcedHeartbeat(false)
    62  }
    63  
    64  func (g *HeartbeatGenerator) ForcedHeartbeat(forced bool) {
    65  
    66  	lastNano := atomic.LoadInt64(&g.atomicNano)
    67  	if lastNano == DisabledHeartbeat {
    68  		//closed channel or generator
    69  		return
    70  	}
    71  
    72  	currentNano := time.Now().UnixNano()
    73  	if lastNano != 0 && !forced && currentNano-lastNano < int64(g.heartbeatPeriod)*int64(time.Millisecond) {
    74  		return
    75  	}
    76  
    77  	if !atomic.CompareAndSwapInt64(&g.atomicNano, lastNano, currentNano) {
    78  		return // there is no need to retry in case of contention
    79  	}
    80  
    81  	if g.send(Heartbeat{From: g.id, PreviousUnixTime: lastNano, UpdateUnixTime: currentNano}) {
    82  		return
    83  	}
    84  
    85  	atomic.CompareAndSwapInt64(&g.atomicNano, currentNano, lastNano) // try to roll-back on failed send
    86  }
    87  
    88  func (g *HeartbeatGenerator) Cancel() {
    89  	for {
    90  		lastNano := atomic.LoadInt64(&g.atomicNano)
    91  		if lastNano == DisabledHeartbeat {
    92  			//closed channel or generator
    93  			return
    94  		}
    95  		if atomic.CompareAndSwapInt64(&g.atomicNano, lastNano, DisabledHeartbeat) {
    96  			g.send(Heartbeat{From: g.id, PreviousUnixTime: lastNano, UpdateUnixTime: DisabledHeartbeat})
    97  			return // there is no need to retry in case of contention
    98  		}
    99  	}
   100  }
   101  
   102  func (g *HeartbeatGenerator) send(beat Heartbeat) bool {
   103  	defer func() {
   104  		err := recover() // just in case of the closed channel
   105  		if err != nil {
   106  			g.Disable()
   107  		}
   108  	}()
   109  
   110  	if g.sendAttempts == 0 {
   111  		return true
   112  	}
   113  
   114  	for i := g.sendAttempts; i > 0; i-- {
   115  		select {
   116  		case g.out <- beat:
   117  			return true
   118  		default:
   119  			// avoid lock up
   120  			runtime.Gosched()
   121  		}
   122  	}
   123  	return false
   124  }
   125  
   126  func (g *HeartbeatGenerator) Disable() {
   127  	atomic.StoreInt64(&g.atomicNano, DisabledHeartbeat)
   128  }
   129  
   130  func (g *HeartbeatGenerator) IsEnabled() bool {
   131  	return atomic.LoadInt64(&g.atomicNano) != DisabledHeartbeat
   132  }