gooseberry: github.com/voicera/gooseberry/polling Index | Examples | Files

package polling

import "github.com/voicera/gooseberry/polling"

Package polling provides cost-effective ways to reduce cost of polling resources. For example: instead of busy-waiting on a service endpoint that may or may not have a payload to process, wrapping said call as a Receiver.Receive() call and using a PollingChannel to wrap said receiver is provides a better poller that may use contingent on Bernoulli trials cyclic exponential backoff between calls.

For example,

polling.NewPoller(receiver, 0.95, time.Millisecond, time.Minute)

creates a poller that keeps sending along (via the poller's send-only channel) payloads received from the specified receiver as long as they keep arriving. When the receiver is empty-handed (denoted by a flag on the Receive method's return value), the pollers runs a Bernoulli trial with a probability of 0.95 to determine whether to back off (95% of the time), or call Receive() again immediately (a costly busy-waiting; some IaaS providers, like AWS, may charge by the call). When backing off, the poller waits between empty-handed calls; it starts with a 1ms delay (seed), then 2ms, then 4ms, etc. The exponential delay is capped at 1 minute (cap), after which it starts from the seed again.

The contingent nature of the cyclic exponential backoff between calls (thanks to Bernoulli sampling) allows the poller to break out of the cycle to check for new payloads – intermittently, which is useful when the delay intervals are long.

Index

Examples

Package Files

conditions.go doc.go poller.go relaxer.go

type Poller Uses

type Poller interface {
    // Channel gets the underlying channel that wraps the resource being polled.
    // By default, sends block until the other side (the receiver) is ready.
    Channel() <-chan interface{}

    // GetName returns name of the poller.
    GetName() string

    // Start starts polling for new payloads to arrive on the receiving end.
    // This method is non-idempotent.
    Start()

    // Stop signals the poller to stop polling; it also closes the channel.
    // This method is non-idempotent.
    Stop()
}

Poller represents the resource being polled as a send-only channel. Ideally, this would be a generic type (see https://golang.org/doc/faq#generics).

Code:

package main

import (
    "errors"
    "fmt"
    "math/rand"
    "time"
)

type counter struct{ count int }
type alwaysEmptyHandedReceiver counter
type neverEmptyHandedReceiver counter
type threeSidedDieReceiver counter

func (*alwaysEmptyHandedReceiver) Receive() (payload interface{}, found bool, err error) {
    return
}

func (receiver *neverEmptyHandedReceiver) Receive() (interface{}, bool, error) {
    receiver.count++
    return receiver.count, true, nil
}

func (receiver *threeSidedDieReceiver) Receive() (interface{}, bool, error) {
    receiver.count++
    cast := rand.Float64()
    if cast < 1.0/3 {
        return receiver.count, true, nil
    } else if cast < 2.0/3 {
        return receiver.count, false, nil
    } else {
        return nil, false, errors.New("failed successfully")
    }
}

func main() {
    rand.Seed(0) // to produce the same sequence of pseudo-random numbers every time
    receivers := []Receiver{&alwaysEmptyHandedReceiver{}, &neverEmptyHandedReceiver{}, &threeSidedDieReceiver{}}

    for r, receiver := range receivers {
        poller, err := NewBernoulliExponentialBackoffPoller(
            receiver, "test", 0.5, time.Nanosecond, time.Millisecond)
        if err != nil {
            panic(err)
        }
        payloads := []interface{}{}

        go poller.Start()

        for i := 0; r > 0 && i < 13; i++ {
            payloads = append(payloads, <-poller.Channel())
        }

        poller.Stop()
        _, receiving := <-poller.Channel()
        fmt.Printf("Stopped; is channel receiving from %T? %t\n", receiver, receiving)
        fmt.Println("Payloads received:", payloads)
        fmt.Println()
    }
}

func NewBernoulliExponentialBackoffPoller Uses

func NewBernoulliExponentialBackoffPoller(
    receiver Receiver, entityName string, probability float64, seed, cap time.Duration) (Poller, error)

NewBernoulliExponentialBackoffPoller creates a polling channel that uses Bernoulli trials with cyclic exponential backoff between empty-handed calls.

type Receiver Uses

type Receiver interface {
    // Receive receives from the resource being polled, returning
    // the received payload (if any), a flag to indicate whether or not
    // a payload was found, and an error if encountered.
    Receive() (interface{}, bool, error)
}

Receiver represents the simple act of receiving payloads (e.g., messages) from the resource being polled.

Package polling imports 4 packages (graph). Updated 2018-04-02. Refresh now. Tools for package owners.