36533f7c3f
Fixes for new geth version
198 lines
4.8 KiB
Go
198 lines
4.8 KiB
Go
package mfs
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
cid "github.com/ipfs/go-cid"
|
|
)
|
|
|
|
// PubFunc is the user-defined function that determines exactly what
|
|
// logic entails "publishing" a `Cid` value.
|
|
type PubFunc func(context.Context, cid.Cid) error
|
|
|
|
// Republisher manages when to publish a given entry.
|
|
type Republisher struct {
|
|
TimeoutLong time.Duration
|
|
TimeoutShort time.Duration
|
|
RetryTimeout time.Duration
|
|
pubfunc PubFunc
|
|
|
|
update chan cid.Cid
|
|
immediatePublish chan chan struct{}
|
|
|
|
ctx context.Context
|
|
cancel func()
|
|
}
|
|
|
|
// NewRepublisher creates a new Republisher object to republish the given root
|
|
// using the given short and long time intervals.
|
|
func NewRepublisher(ctx context.Context, pf PubFunc, tshort, tlong time.Duration) *Republisher {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
return &Republisher{
|
|
TimeoutShort: tshort,
|
|
TimeoutLong: tlong,
|
|
RetryTimeout: tlong,
|
|
update: make(chan cid.Cid, 1),
|
|
pubfunc: pf,
|
|
immediatePublish: make(chan chan struct{}),
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
}
|
|
|
|
// WaitPub waits for the current value to be published (or returns early
|
|
// if it already has).
|
|
func (rp *Republisher) WaitPub(ctx context.Context) error {
|
|
wait := make(chan struct{})
|
|
select {
|
|
case rp.immediatePublish <- wait:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
select {
|
|
case <-wait:
|
|
return nil
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
|
|
func (rp *Republisher) Close() error {
|
|
// TODO(steb): Wait for `Run` to stop
|
|
err := rp.WaitPub(rp.ctx)
|
|
rp.cancel()
|
|
return err
|
|
}
|
|
|
|
// Update the current value. The value will be published after a delay but each
|
|
// consecutive call to Update may extend this delay up to TimeoutLong.
|
|
func (rp *Republisher) Update(c cid.Cid) {
|
|
select {
|
|
case <-rp.update:
|
|
select {
|
|
case rp.update <- c:
|
|
default:
|
|
// Don't try again. If we hit this case, there's a
|
|
// concurrent publish and we can safely let that
|
|
// concurrent publish win.
|
|
}
|
|
case rp.update <- c:
|
|
}
|
|
}
|
|
|
|
// Run contains the core logic of the `Republisher`. It calls the user-defined
|
|
// `pubfunc` function whenever the `Cid` value is updated to a *new* value. The
|
|
// complexity comes from the fact that `pubfunc` may be slow so we need to batch
|
|
// updates.
|
|
//
|
|
// Algorithm:
|
|
// 1. When we receive the first update after publishing, we set a `longer` timer.
|
|
// 2. When we receive any update, we reset the `quick` timer.
|
|
// 3. If either the `quick` timeout or the `longer` timeout elapses,
|
|
// we call `publish` with the latest updated value.
|
|
//
|
|
// The `longer` timer ensures that we delay publishing by at most
|
|
// `TimeoutLong`. The `quick` timer allows us to publish sooner if
|
|
// it looks like there are no more updates coming down the pipe.
|
|
//
|
|
// Note: If a publish fails, we retry repeatedly every TimeoutRetry.
|
|
func (rp *Republisher) Run(lastPublished cid.Cid) {
|
|
quick := time.NewTimer(0)
|
|
if !quick.Stop() {
|
|
<-quick.C
|
|
}
|
|
longer := time.NewTimer(0)
|
|
if !longer.Stop() {
|
|
<-longer.C
|
|
}
|
|
|
|
var toPublish cid.Cid
|
|
for rp.ctx.Err() == nil {
|
|
var waiter chan struct{}
|
|
|
|
select {
|
|
case <-rp.ctx.Done():
|
|
return
|
|
case newValue := <-rp.update:
|
|
// Skip already published values.
|
|
if lastPublished.Equals(newValue) {
|
|
// Break to the end of the switch to cleanup any
|
|
// timers.
|
|
toPublish = cid.Undef
|
|
break
|
|
}
|
|
|
|
// If we aren't already waiting to publish something,
|
|
// reset the long timeout.
|
|
if !toPublish.Defined() {
|
|
longer.Reset(rp.TimeoutLong)
|
|
}
|
|
|
|
// Always reset the short timeout.
|
|
quick.Reset(rp.TimeoutShort)
|
|
|
|
// Finally, set the new value to publish.
|
|
toPublish = newValue
|
|
continue
|
|
case waiter = <-rp.immediatePublish:
|
|
// Make sure to grab the *latest* value to publish.
|
|
select {
|
|
case toPublish = <-rp.update:
|
|
default:
|
|
}
|
|
|
|
// Avoid publishing duplicate values
|
|
if lastPublished.Equals(toPublish) {
|
|
toPublish = cid.Undef
|
|
}
|
|
case <-quick.C:
|
|
case <-longer.C:
|
|
}
|
|
|
|
// Cleanup, publish, and close waiters.
|
|
|
|
// 1. Stop any timers. Don't use the `if !t.Stop() { ... }`
|
|
// idiom as these timers may not be running.
|
|
|
|
quick.Stop()
|
|
select {
|
|
case <-quick.C:
|
|
default:
|
|
}
|
|
|
|
longer.Stop()
|
|
select {
|
|
case <-longer.C:
|
|
default:
|
|
}
|
|
|
|
// 2. If we have a value to publish, publish it now.
|
|
if toPublish.Defined() {
|
|
for {
|
|
err := rp.pubfunc(rp.ctx, toPublish)
|
|
if err == nil {
|
|
break
|
|
}
|
|
// Keep retrying until we succeed or we abort.
|
|
// TODO(steb): We could try pulling new values
|
|
// off `update` but that's not critical (and
|
|
// complicates this code a bit). We'll pull off
|
|
// a new value on the next loop through.
|
|
select {
|
|
case <-time.After(rp.RetryTimeout):
|
|
case <-rp.ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
lastPublished = toPublish
|
|
toPublish = cid.Undef
|
|
}
|
|
|
|
// 3. Trigger anything waiting in `WaitPub`.
|
|
if waiter != nil {
|
|
close(waiter)
|
|
}
|
|
}
|
|
}
|