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 }