Documentation
¶
Overview ¶
Package flow is an interface-driven library for concurrent pipelines: you implement the ends — Producer, Processor and Consumer (or pass a closure via the *Func adapters) — and flow owns the concurrency, ordering, back-pressure and fail-fast cancellation.
Inspired by Elixir's GenStage, but with push+blocking back-pressure and streaming reconstruction of the global input order.
Example (StructConsumer) ¶
Ends can be structs when they carry state; the consumer is called from a single goroutine, so no locking is needed.
package main
import (
"context"
"fmt"
"github.com/pabloos/flow"
)
// sum is a stateful Consumer implemented on a struct rather than a closure.
type sum struct{ total int }
func (s *sum) Consume(ctx context.Context, n int) error {
s.total += n
return nil
}
func main() {
total := &sum{}
err := flow.Run(context.Background(),
flow.Slice(1, 2, 3, 4),
flow.Map(func(n int) int { return n }),
total,
flow.Workers(4),
)
if err != nil {
fmt.Println("error:", err)
}
fmt.Println(total.total)
}
Output: 10
Index ¶
- func Run[I, O any](ctx context.Context, p Producer[I], proc Processor[I, O], c Consumer[O], ...) error
- func RunPartitioned[I, O any, K comparable](ctx context.Context, p Producer[I], proc Processor[I, O], c Consumer[O], ...) error
- type Consumer
- type ConsumerFunc
- type Counter
- type Histogram
- type Meter
- type Option
- type Processor
- func Filter[T any](pred func(T) bool) Processor[T, T]
- func FlatMap[I, O any](fn func(I) []O) Processor[I, O]
- func Map[I, O any](fn func(I) O) Processor[I, O]
- func Then[A, B, C any](p1 Processor[A, B], p2 Processor[B, C]) Processor[A, C]
- func TryFilter[T any](pred func(T) (bool, error)) Processor[T, T]
- func TryFlatMap[I, O any](fn func(I) ([]O, error)) Processor[I, O]
- func TryMap[I, O any](fn func(I) (O, error)) Processor[I, O]
- type ProcessorFunc
- type Producer
- type ProducerFunc
- type Report
- type Stage
- type StageLatency
- type WorkerStat
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Run ¶
func Run[I, O any](ctx context.Context, p Producer[I], proc Processor[I, O], c Consumer[O], opts ...Option) error
Run wires producer -> pool of processors -> consumer and owns the concurrency, ordering, back-pressure and fail-fast cancellation. It returns the first error from any end, or nil.
Example ¶
The basic shape: a producer, a processor, a consumer, run in parallel with the output order preserved.
package main
import (
"context"
"fmt"
"github.com/pabloos/flow"
)
func main() {
err := flow.Run(context.Background(),
flow.Slice(1, 2, 3, 4),
flow.Map(func(n int) int { return n * 2 }),
flow.Each(func(n int) error {
fmt.Println(n)
return nil
}),
flow.Workers(4),
flow.Ordered(),
)
if err != nil {
fmt.Println("error:", err)
}
}
Output: 2 4 6 8
func RunPartitioned ¶ added in v0.2.0
func RunPartitioned[I, O any, K comparable](ctx context.Context, p Producer[I], proc Processor[I, O], c Consumer[O], key func(I) K, opts ...Option) error
RunPartitioned is like Run but routes every input to a worker by key: all values with the same key go to the same worker and are processed in order, so a given key is never processed concurrently. Use it for per-key ordering or for processors that keep per-key state.
Combine with Ordered to also restore the global input order at the consumer; on its own, RunPartitioned only guarantees order within each key.
Example ¶
RunPartitioned keeps each key's values on one worker, in order. With Ordered the global input order is restored too.
package main
import (
"context"
"fmt"
"github.com/pabloos/flow"
)
func main() {
type event struct {
user string
n int
}
events := []event{{"a", 1}, {"b", 1}, {"a", 2}, {"b", 2}}
err := flow.RunPartitioned(context.Background(),
flow.Slice(events...),
flow.Map(func(e event) string { return fmt.Sprintf("%s#%d", e.user, e.n) }),
flow.Each(func(s string) error {
fmt.Println(s)
return nil
}),
func(e event) string { return e.user },
flow.Workers(4),
flow.Ordered(),
)
if err != nil {
fmt.Println("error:", err)
}
}
Output: a#1 b#1 a#2 b#2
Types ¶
type Consumer ¶
Consumer receives each output value. It is never called concurrently, so implementations need no synchronization.
type ConsumerFunc ¶
type Counter ¶ added in v0.5.0
type Counter interface {
Add(n int64)
}
Counter is a monotonically increasing count.
type Meter ¶ added in v0.5.0
Meter is flow's streaming-metrics extension point. Implement it in a separate module — so the core stays zero-dependency — to forward live measurements to a backend such as Prometheus or OpenTelemetry, and enable it with WithMeter.
The engine asks the Meter for its instruments once when a run starts, then updates them on the hot path. Instrument methods are called from many worker goroutines at once, so implementations must be safe for concurrent use.
Experimental: the observability API may change in a future v0.x release.
type Option ¶
type Option func(*config)
Option configures a Run.
func Observe ¶ added in v0.3.0
Observe fills dst with a performance Report after the run: per-stage timings, counts and a diagnosed bottleneck. Opt-in — it adds timing to the hot path, so leave it off in production.
Experimental: the observability API (Observe and Report) may change in a future v0.x release.
func Ordered ¶
func Ordered() Option
Ordered reconstructs the original input order at the consumer, even with a worker pool. Without it, outputs are consumed in arrival order.
func Prefetch ¶ added in v0.2.0
Prefetch sets how many items may be queued ahead of the workers (the in-flight window), letting the producer run ahead instead of lock-stepping with the pool. Default 0 (unbuffered: strict lock-step).
type Processor ¶
Processor turns one input value into zero or more outputs via emit. Emitting once is Map, emitting conditionally is Filter, emitting many is FlatMap.
func Then ¶
Then composes two processors with no channel between them: it is plain function composition through emit, so multi-stage pipelines run in-process inside each worker.
Example ¶
A realistic multi-stage pipeline composed with Then: parse, filter, double.
package main
import (
"context"
"fmt"
"strconv"
"github.com/pabloos/flow"
)
func main() {
pipeline := flow.Then(
flow.TryMap(strconv.Atoi), // string -> int
flow.Then(
flow.Filter(func(n int) bool { return n > 0 }), // keep positives
flow.Map(func(n int) int { return n * 2 }), // double
),
)
var out []int
err := flow.Run(context.Background(),
flow.Slice("1", "-2", "3", "4"),
pipeline,
flow.Into(&out),
flow.Workers(4),
flow.Ordered(),
)
if err != nil {
fmt.Println("error:", err)
}
fmt.Println(out)
}
Output: [2 6 8]
func TryFlatMap ¶
TryFlatMap is FlatMap with a fallible expansion.
type ProcessorFunc ¶
Example ¶
One Processor can expand each input into several outputs (FlatMap) by emitting more than once.
package main
import (
"context"
"fmt"
"strings"
"github.com/pabloos/flow"
)
func main() {
split := flow.ProcessorFunc[string, string](func(ctx context.Context, line string, emit func(string) error) error {
for _, w := range strings.Fields(line) {
if err := emit(w); err != nil {
return err
}
}
return nil
})
var words []string
err := flow.Run(context.Background(),
flow.Slice("hello world", "goodbye"),
split,
flow.Into(&words),
flow.Ordered(),
)
if err != nil {
fmt.Println("error:", err)
}
fmt.Println(words)
}
Output: [hello world goodbye]
type Producer ¶
Producer emits values into the pipeline via emit until it is done or errors.
type ProducerFunc ¶
type Report ¶ added in v0.3.0
type Report struct {
Wall time.Duration
Workers int
Produced int64 // values emitted by the producer
Processed int64 // inputs handled by the pool
Emitted int64 // outputs produced by the pool (fan-out with FlatMap)
Consumed int64 // values delivered to the consumer
ProducerBlocked time.Duration // producer time blocked emitting (downstream full)
WorkerBusy time.Duration // time inside Process (summed)
WorkerIdle time.Duration // time waiting for input (producer slow)
WorkerBlocked time.Duration // time blocked delivering (consumer slow)
ConsumerBusy time.Duration // time inside Consume
ProcessLatency StageLatency // per-item Process latency distribution
PerWorker []WorkerStat // per-worker breakdown
Bottleneck Stage
Advice string
}
Report is a snapshot of a run's performance, filled in when Observe is set. Aggregate processor timings are summed across all workers; PerWorker holds the per-worker breakdown.
Experimental: this type may change in a future v0.x release.
type StageLatency ¶ added in v0.4.0
StageLatency holds the per-item latency distribution of Process. Percentiles are computed from a bounded reservoir sample, so they are approximate; Max is exact.
type WorkerStat ¶ added in v0.4.0
WorkerStat is one worker's share of the work, for spotting imbalance.