ipld-eth-server/vendor/github.com/ipfs/go-mfs/repub.go
Elizabeth Engelman 36533f7c3f Update vendor directory and make necessary code changes
Fixes for new geth version
2019-09-25 16:32:27 -05:00

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)
}
}
}