gitee.com/liuxuezhan/go-micro-v1.18.0@v1.0.0/sync/task/broker/broker.go (about) 1 // Package broker provides a distributed task manager built on the micro broker 2 package broker 3 4 import ( 5 "context" 6 "errors" 7 "fmt" 8 "math/rand" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/google/uuid" 14 "gitee.com/liuxuezhan/go-micro-v1.18.0/broker" 15 "gitee.com/liuxuezhan/go-micro-v1.18.0/sync/task" 16 ) 17 18 type brokerKey struct{} 19 20 // Task is a broker task 21 type Task struct { 22 // a micro broker 23 Broker broker.Broker 24 // Options 25 Options task.Options 26 27 mtx sync.RWMutex 28 status string 29 } 30 31 func returnError(err error, ch chan error) { 32 select { 33 case ch <- err: 34 default: 35 } 36 } 37 38 func (t *Task) Run(c task.Command) error { 39 // connect 40 t.Broker.Connect() 41 // unique id for this runner 42 id := uuid.New().String() 43 // topic of the command 44 topic := fmt.Sprintf("task.%s", c.Name) 45 46 // global error 47 errCh := make(chan error, t.Options.Pool) 48 49 // subscribe for distributed work 50 workFn := func(p broker.Event) error { 51 msg := p.Message() 52 53 // get command name 54 command := msg.Header["Command"] 55 56 // check the command is what we expect 57 if command != c.Name { 58 returnError(errors.New("received unknown command: "+command), errCh) 59 return nil 60 } 61 62 // new task created 63 switch msg.Header["Status"] { 64 case "start": 65 // artificially delay start of processing 66 time.Sleep(time.Millisecond * time.Duration(10+rand.Intn(100))) 67 68 // execute the function 69 err := c.Func() 70 71 status := "done" 72 errors := "" 73 74 if err != nil { 75 status = "error" 76 errors = err.Error() 77 } 78 79 // create response 80 msg := &broker.Message{ 81 Header: map[string]string{ 82 "Command": c.Name, 83 "Error": errors, 84 "Id": id, 85 "Status": status, 86 "Timestamp": fmt.Sprintf("%d", time.Now().Unix()), 87 }, 88 // Body is nil, may be used in future 89 } 90 91 // publish end of task 92 if err := t.Broker.Publish(topic, msg); err != nil { 93 returnError(err, errCh) 94 } 95 } 96 97 return nil 98 } 99 100 // subscribe for the pool size 101 for i := 0; i < t.Options.Pool; i++ { 102 // subscribe to work 103 subWork, err := t.Broker.Subscribe(topic, workFn, broker.Queue(fmt.Sprintf("work.%d", i))) 104 if err != nil { 105 return err 106 } 107 108 // unsubscribe on completion 109 defer subWork.Unsubscribe() 110 } 111 112 // subscribe to all status messages 113 subStatus, err := t.Broker.Subscribe(topic, func(p broker.Event) error { 114 msg := p.Message() 115 116 // get command name 117 command := msg.Header["Command"] 118 119 // check the command is what we expect 120 if command != c.Name { 121 return nil 122 } 123 124 // check task status 125 switch msg.Header["Status"] { 126 // task is complete 127 case "done": 128 errCh <- nil 129 // someone failed 130 case "error": 131 returnError(errors.New(msg.Header["Error"]), errCh) 132 } 133 134 return nil 135 }) 136 if err != nil { 137 return err 138 } 139 defer subStatus.Unsubscribe() 140 141 // a new task 142 msg := &broker.Message{ 143 Header: map[string]string{ 144 "Command": c.Name, 145 "Id": id, 146 "Status": "start", 147 "Timestamp": fmt.Sprintf("%d", time.Now().Unix()), 148 }, 149 } 150 151 // artificially delay the start of the task 152 time.Sleep(time.Millisecond * time.Duration(10+rand.Intn(100))) 153 154 // publish the task 155 if err := t.Broker.Publish(topic, msg); err != nil { 156 return err 157 } 158 159 var gerrors []string 160 161 // wait for all responses 162 for i := 0; i < t.Options.Pool; i++ { 163 // check errors 164 err := <-errCh 165 166 // append to errors 167 if err != nil { 168 gerrors = append(gerrors, err.Error()) 169 } 170 } 171 172 // return the errors 173 if len(gerrors) > 0 { 174 return errors.New("errors: " + strings.Join(gerrors, "\n")) 175 } 176 177 return nil 178 } 179 180 func (t *Task) Status() string { 181 t.mtx.RLock() 182 defer t.mtx.RUnlock() 183 return t.status 184 } 185 186 // Broker sets the micro broker 187 func WithBroker(b broker.Broker) task.Option { 188 return func(o *task.Options) { 189 if o.Context == nil { 190 o.Context = context.Background() 191 } 192 o.Context = context.WithValue(o.Context, brokerKey{}, b) 193 } 194 } 195 196 // NewTask returns a new broker task 197 func NewTask(opts ...task.Option) task.Task { 198 options := task.Options{ 199 Context: context.Background(), 200 } 201 202 for _, o := range opts { 203 o(&options) 204 } 205 206 if options.Pool == 0 { 207 options.Pool = 1 208 } 209 210 b, ok := options.Context.Value(brokerKey{}).(broker.Broker) 211 if !ok { 212 b = broker.DefaultBroker 213 } 214 215 return &Task{ 216 Broker: b, 217 Options: options, 218 } 219 }