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  }