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