github.com/searKing/golang/go@v1.2.117/x/dispatch/dispatch.go (about) 1 // Copyright 2020 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package dispatch 6 7 import ( 8 "context" 9 "errors" 10 "sync" 11 ) 12 13 type Reader interface { 14 Read(ctx context.Context) (msg any, err error) 15 } 16 type ReaderFunc func(ctx context.Context) (msg any, err error) 17 18 func (f ReaderFunc) Read(ctx context.Context) (msg any, err error) { 19 return f(ctx) 20 } 21 22 type Handler interface { 23 Handle(ctx context.Context, msg any) error 24 } 25 type HandlerFunc func(ctx context.Context, msg any) error 26 27 func (f HandlerFunc) Handle(ctx context.Context, msg any) error { 28 return f(ctx, msg) 29 } 30 31 // Dispatch is a middleman between the Reader and Processor. 32 type Dispatch struct { 33 reader Reader 34 handler Handler 35 handlerParallelChan chan struct{} 36 wg WaitGroup 37 ctx context.Context 38 } 39 40 func NewDispatch(reader Reader, handler Handler) *Dispatch { 41 return NewDispatch3(reader, handler, -1) 42 } 43 func NewDispatch3(reader Reader, handler Handler, concurrentMax int) *Dispatch { 44 45 dispatch := &Dispatch{ 46 reader: reader, 47 handler: handler, 48 } 49 if concurrentMax >= 0 { 50 dispatch.handlerParallelChan = make(chan struct{}, concurrentMax) 51 } 52 53 return dispatch 54 } 55 func (d *Dispatch) Context() context.Context { 56 if d.ctx != nil { 57 return d.ctx 58 } 59 return context.Background() 60 } 61 func (d *Dispatch) WithContext(ctx context.Context) *Dispatch { 62 if ctx == nil { 63 panic("nil context") 64 } 65 d2 := new(Dispatch) 66 *d2 = *d 67 d2.ctx = ctx 68 69 return d2 70 } 71 func (d *Dispatch) done() bool { 72 select { 73 case <-d.Context().Done(): 74 return true 75 default: 76 return false 77 } 78 } 79 80 func (d *Dispatch) AllowHandleInGroutine() bool { 81 return d.handlerParallelChan != nil 82 } 83 func (d *Dispatch) Read() (any, error) { 84 return d.reader.Read(d.Context()) 85 } 86 87 func (d *Dispatch) GetHandleGoroutine() bool { 88 if !d.AllowHandleInGroutine() { 89 panic("unexpected operation") 90 } 91 select { 92 case d.handlerParallelChan <- struct{}{}: 93 return true 94 case <-d.Context().Done(): // chan close 95 return false 96 } 97 } 98 func (d *Dispatch) PutHandleGoroutine() { 99 if !d.AllowHandleInGroutine() { 100 panic("unexpected operation") 101 } 102 select { 103 case <-d.handlerParallelChan: 104 default: 105 } 106 } 107 func (d *Dispatch) Handle(msg any) error { 108 fn := func(wg WaitGroup) error { 109 wg.Add(1) 110 defer wg.Done() 111 return d.handler.Handle(d.Context(), msg) 112 } 113 if !d.AllowHandleInGroutine() { 114 return fn(d.waitGroup()) 115 } 116 // Block if the number of handle goRoutines meets concurrentMax 117 if !d.GetHandleGoroutine() { 118 // Handle canceled 119 return errors.New("GetHandleGoroutine failed, Dispatch is canceled") 120 } 121 go func() { 122 defer d.PutHandleGoroutine() 123 fn((d.waitGroup())) 124 }() 125 return nil 126 } 127 128 // 遍历读取消息,并进行分发处理 129 func (d *Dispatch) Start() *Dispatch { 130 func(wg WaitGroup) { 131 wg.Add(1) 132 defer wg.Done() 133 for { 134 msg, err := d.Read() 135 if err != nil { 136 break 137 } 138 // just dispatch non-nil msg 139 if msg != nil { 140 d.Handle(msg) 141 } 142 143 // break if dispatcher is canceled or done 144 if d.done() { 145 break 146 } 147 } 148 149 }(d.waitGroup()) 150 return d 151 } 152 153 // make Dispatch joinable 154 // Join() blocks until all recv and handle workflows started after Join() is finished 155 // RECOMMECD : call Joinable() before Start() to join all workflows 156 func (d *Dispatch) Joinable() *Dispatch { 157 if d.wg == nil { 158 wg := &sync.WaitGroup{} 159 wg.Add(1) 160 d.wg = wg 161 } 162 return d 163 } 164 165 // make Dispatch unjoinable, as Join() return immediately when called 166 func (d *Dispatch) UnJoinable() *Dispatch { 167 d.waitGroup().Done() 168 if d.wg != nil { 169 d.wg = nil 170 } 171 return d 172 } 173 func (d *Dispatch) waitGroup() WaitGroup { 174 if d.wg != nil { 175 return d.wg 176 } 177 return nullWG 178 } 179 180 // wait until all recv and handle workflows finished, such as join in Thread 181 func (d *Dispatch) Join() *Dispatch { 182 func(wg WaitGroup) { 183 wg.Done() 184 wg.Wait() 185 }(d.waitGroup()) 186 return d 187 }