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  }