bastion 0.55.3
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A beat out of turn, for every pairing on this box.
|
||||
//
|
||||
// Each running pairing beats its own Host on a 60-second ticker, and every
|
||||
// beat carries the box's pairings as `peers` with the state of each (peers.go).
|
||||
// That is how every Host on the machine learns that a pairing went active or was
|
||||
// revoked. A pairing_grant_sync or pairing_grant_drop changes that state on disk
|
||||
// at once, but nothing beat: every Host kept the old state for up to a minute.
|
||||
// So a pairing approved a second after a beat still read as pending for the
|
||||
// next minute, and a Host asking to connect to it in that time was refused. The
|
||||
// anchor Host can read its own confirmed grant, but no other Host on the
|
||||
// machine has one, and a revoke reached the other Hosts no sooner.
|
||||
//
|
||||
// So a change to a pairing's state asks every running pairing's beat loop to beat
|
||||
// now. The signal carries nothing: the beat reads the pairings from disk as it
|
||||
// always does, so it reports the state the change wrote. One pending signal per
|
||||
// loop is kept and the rest are dropped, so a burst of changes is one extra beat
|
||||
// per Host, never one per change.
|
||||
|
||||
var beatNow = struct {
|
||||
sync.Mutex
|
||||
subs map[chan struct{}]struct{}
|
||||
}{subs: map[chan struct{}]struct{}{}}
|
||||
|
||||
// subscribeBeatNow registers one beat loop. The channel holds at most one
|
||||
// pending signal; stop unregisters it.
|
||||
func subscribeBeatNow() (<-chan struct{}, func()) {
|
||||
ch := make(chan struct{}, 1)
|
||||
beatNow.Lock()
|
||||
beatNow.subs[ch] = struct{}{}
|
||||
beatNow.Unlock()
|
||||
return ch, func() {
|
||||
beatNow.Lock()
|
||||
delete(beatNow.subs, ch)
|
||||
beatNow.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// kickBeats asks every registered beat loop to beat now. It never blocks: a
|
||||
// loop that already has a signal pending, or is mid-beat, beats once more.
|
||||
func kickBeats() {
|
||||
beatNow.Lock()
|
||||
defer beatNow.Unlock()
|
||||
for ch := range beatNow.subs {
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runBeatLoop beats every `every`, and also whenever kickBeats asks. beforeTick
|
||||
// runs before a ticked beat only (the tunnel's path failover, which belongs to
|
||||
// the clock, not to a pairing change); it may be nil. It returns when ctx ends.
|
||||
func runBeatLoop(ctx context.Context, every time.Duration, beforeTick, beat func()) {
|
||||
kick, stop := subscribeBeatNow()
|
||||
defer stop()
|
||||
t := time.NewTicker(every)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
if beforeTick != nil {
|
||||
beforeTick()
|
||||
}
|
||||
beat()
|
||||
case <-kick:
|
||||
beat()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user