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  }