-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfbp.go
More file actions
150 lines (133 loc) · 3.38 KB
/
Copy pathfbp.go
File metadata and controls
150 lines (133 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package pipeline
import (
"context"
"errors"
"fmt"
"sync"
)
// Port is a named typed connection point on a Component. Connections carry
// `any`; component implementations type-assert. Buffer sets the channel
// capacity (the backpressure bound — a slow downstream blocks the upstream once
// the buffer fills).
type Port struct {
Name string
ch chan any
closed bool
mu sync.Mutex
}
// NewPort creates a port with the given bounded buffer.
func NewPort(name string, buffer int) *Port {
if buffer < 0 {
buffer = 0
}
return &Port{Name: name, ch: make(chan any, buffer)}
}
// Send pushes a value, blocking when the bound is reached (backpressure) until
// space is available or the context is cancelled.
func (p *Port) Send(ctx context.Context, v any) error {
select {
case <-ctx.Done():
return ctx.Err()
case p.ch <- v:
return nil
}
}
// Recv pulls a value; ok=false when the port is closed and drained.
func (p *Port) Recv(ctx context.Context) (v any, ok bool, err error) {
select {
case <-ctx.Done():
return nil, false, ctx.Err()
case v, ok := <-p.ch:
return v, ok, nil
}
}
// Close closes the underlying channel (idempotent).
func (p *Port) Close() {
p.mu.Lock()
defer p.mu.Unlock()
if !p.closed {
close(p.ch)
p.closed = true
}
}
// Component is a unit in an FBP network: it reads from its input ports and
// writes to its output ports inside Run, returning when its work is complete.
type Component interface {
Name() string
Run(ctx context.Context) error
}
// Network wires components together and runs them concurrently. A slow consumer
// applies backpressure to its producer through the bounded ports.
type Network struct {
components []Component
conns []connection
}
type connection struct {
from *Port
to *Port
}
// NewNetwork creates an empty network.
func NewNetwork() *Network { return &Network{} }
// Add registers a component.
func (n *Network) Add(c Component) *Network {
n.components = append(n.components, c)
return n
}
// Connect declares that everything sent on `from` should be forwarded to `to`.
func (n *Network) Connect(from, to *Port) error {
if from == nil || to == nil {
return errors.New("pipeline: nil port in Connect")
}
n.conns = append(n.conns, connection{from: from, to: to})
return nil
}
// Run starts every component plus a forwarder per connection, and blocks until
// all complete (or the first error / cancellation). Forwarders propagate
// backpressure: forwarding blocks when `to` is full.
func (n *Network) Run(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
var wg sync.WaitGroup
errCh := make(chan error, len(n.components)+len(n.conns))
// Connection forwarders.
for _, c := range n.conns {
wg.Add(1)
go func(c connection) {
defer wg.Done()
for {
v, ok, err := c.from.Recv(ctx)
if err != nil {
errCh <- err
return
}
if !ok {
c.to.Close()
return
}
if err := c.to.Send(ctx, v); err != nil {
errCh <- err
return
}
}
}(c)
}
// Components.
for _, comp := range n.components {
wg.Add(1)
go func(comp Component) {
defer wg.Done()
if err := comp.Run(ctx); err != nil {
errCh <- fmt.Errorf("component %s: %w", comp.Name(), err)
cancel()
}
}(comp)
}
wg.Wait()
close(errCh)
for err := range errCh {
if err != nil && !errors.Is(err, context.Canceled) {
return err
}
}
return ctx.Err()
}