aboutsummaryrefslogtreecommitdiff
path: root/fanin/fanin.go
diff options
context:
space:
mode:
authorsangtran <tranquangsang12.7@gmail.com>2026-09-03 23:10:33 +0700
committersangtran <tranquangsang12.7@gmail.com>2026-09-03 23:10:33 +0700
commit02123df8cc15896f97c1580e49d8e2a991e37ddd (patch)
treed0c00bc3f65e5f5493a9dff8034c80640cb12960 /fanin/fanin.go
signatureVerified sangtran <tranquangsang12.7@gmail.com> Key: 8DD79823D235E1BB ultimate trust
downloadquacker-02123df8cc15896f97c1580e49d8e2a991e37ddd.tar.gz
quacker-02123df8cc15896f97c1580e49d8e2a991e37ddd.zip
Verified Moving all legacy quacker source code from github to cgitHEADmain
Diffstat (limited to 'fanin/fanin.go')
-rw-r--r--fanin/fanin.go142
1 files changed, 142 insertions, 0 deletions
diff --git a/fanin/fanin.go b/fanin/fanin.go
new file mode 100644
index 0000000..913927f
--- /dev/null
+++ b/fanin/fanin.go
@@ -0,0 +1,142 @@
1// Package fanin merges multiple input channels into a single output channel.
2package fanin
3
4import (
5 "context"
6 "fmt"
7 "sync"
8 "sync/atomic"
9)
10
11// FanInObserver defines callbacks for monitoring fan-in lifecycle events.
12type FanInObserver interface {
13 OnInputAdded()
14 OnInputClosed()
15}
16
17// FanInConfig holds configuration options for creating a FanIn instance.
18type FanInConfig struct {
19 BufferSize int
20 Observer FanInObserver
21}
22
23// Option is a functional option for configuring a FanIn instance.
24type Option func(*FanInConfig)
25
26// FanIn merges multiple input channels of type T into a single output channel.
27type FanIn[T any] struct {
28 cfg *FanInConfig
29 mu sync.Mutex
30 inputs []<-chan T
31 running atomic.Bool
32 done chan struct{}
33}
34
35// NewFanIn creates a new FanIn instance with the provided options.
36func NewFanIn[T any](opts ...Option) (*FanIn[T], error) {
37 cfg := &FanInConfig{}
38
39 for _, opt := range opts {
40 opt(cfg)
41 }
42
43 if cfg.BufferSize < 0 {
44 return nil, fmt.Errorf("fanin: buffer size must not be negative, got %d", cfg.BufferSize)
45 }
46
47 return &FanIn[T]{
48 cfg: cfg,
49 }, nil
50}
51
52// WithBufferSize sets the buffer capacity for the merged output channel.
53func WithBufferSize(size int) Option {
54 return func(f *FanInConfig) {
55 f.BufferSize = size
56 }
57}
58
59// WithObserver attaches a FanInObserver to receive lifecycle event callbacks.
60func WithObserver(observer FanInObserver) Option {
61 return func(f *FanInConfig) {
62 f.Observer = observer
63 }
64}
65
66// Add registers an input channel to be merged into the output.
67// It must be called before Run, and is safe for concurrent use.
68func (f *FanIn[T]) Add(ch <-chan T) {
69 if ch == nil {
70 panic("fanin: cannot add nil channel")
71 }
72
73 if f.running.Load() {
74 panic("fanin: Add() called after Run()")
75 }
76
77 f.mu.Lock()
78 defer f.mu.Unlock()
79
80 f.inputs = append(f.inputs, ch)
81
82 if f.cfg.Observer != nil {
83 safeObserve(f.cfg.Observer.OnInputAdded)
84 }
85}
86
87// Run starts merging the registered input channels into a single output channel.
88func (f *FanIn[T]) Run(ctx context.Context) <-chan T {
89 if !f.running.CompareAndSwap(false, true) {
90 panic("fanin: Run() called multiple times")
91 }
92
93 f.mu.Lock()
94 inputs := append([]<-chan T(nil), f.inputs...)
95 out := make(chan T, f.cfg.BufferSize)
96 f.done = make(chan struct{})
97 f.mu.Unlock()
98
99 var wg sync.WaitGroup
100 for _, ch := range inputs {
101 wg.Go(func() {
102 for {
103 select {
104 case v, ok := <-ch:
105 if !ok {
106 if f.cfg.Observer != nil {
107 safeObserve(f.cfg.Observer.OnInputClosed)
108 }
109 return
110 }
111 select {
112 case out <- v:
113 case <-ctx.Done():
114 return
115 }
116 case <-ctx.Done():
117 return
118 }
119 }
120 })
121 }
122
123 go func() {
124 wg.Wait()
125 close(out)
126 close(f.done)
127 }()
128
129 return out
130}
131
132func safeObserve(fn func()) {
133 defer func() { recover() }()
134 fn()
135}
136
137// Done returns a channel that is closed when all inputs are processed or the context is cancelled.
138func (f *FanIn[T]) Done() <-chan struct{} {
139 f.mu.Lock()
140 defer f.mu.Unlock()
141 return f.done
142}