github.com/ergo-services/ergo@v1.999.224/gen/stage_dispatcher.go (about)

     1  package gen
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  
     7  	"github.com/ergo-services/ergo/etf"
     8  	"github.com/ergo-services/ergo/lib"
     9  )
    10  
    11  // StageDispatcherBehavior defined interface for the dispatcher
    12  // implementation. To create a custom dispatcher you should implement this interface
    13  // and use it in StageOptions as a Dispatcher
    14  type StageDispatcherBehavior interface {
    15  	// InitStageDispatcher(opts)
    16  	Init(opts StageOptions) (state interface{})
    17  
    18  	// Ask called every time a consumer sends demand
    19  	Ask(state interface{}, subscription StageSubscription, count uint)
    20  
    21  	// Cancel called every time a subscription is cancelled or the consumer goes down.
    22  	Cancel(state interface{}, subscription StageSubscription)
    23  
    24  	// Dispatch called every time a producer wants to dispatch an event.
    25  	Dispatch(state interface{}, events etf.List) []StageDispatchItem
    26  
    27  	// Subscribe called every time the producer gets a new subscriber
    28  	Subscribe(state interface{}, subscription StageSubscription, opts StageSubscribeOptions) error
    29  }
    30  
    31  // StageDispatcher
    32  type StageDispatcher int
    33  type dispatcherDemand struct{}
    34  type dispatcherBroadcast struct{}
    35  type dispatcherPartition struct {
    36  	n    uint
    37  	hash func(etf.Term) int
    38  }
    39  
    40  // CreateStageDispatcherDemand creates a dispatcher that sends batches
    41  // to the highest demand. This is the default dispatcher used
    42  // by Stage. In order to avoid greedy consumers, it is recommended
    43  // that all consumers have exactly the same maximum demand.
    44  func CreateStageDispatcherDemand() StageDispatcherBehavior {
    45  	return &dispatcherDemand{}
    46  }
    47  
    48  // CreateStageDispatcherBroadcast creates a dispatcher that accumulates
    49  // demand from all consumers before broadcasting events to all of them.
    50  // This dispatcher guarantees that events are dispatched to
    51  // all consumers without exceeding the demand of any given consumer.
    52  // The demand is only sent upstream once all consumers ask for data.
    53  func CreateStageDispatcherBroadcast() StageDispatcherBehavior {
    54  	return &dispatcherBroadcast{}
    55  }
    56  
    57  // CreateStageDispatcherPartition creates a dispatcher that sends
    58  // events according to partitions. Number of partitions 'n' must be > 0.
    59  // 'hash' should return number within range [0,n). Value outside of this range
    60  // is discarding event.
    61  // If 'hash' is nil the random partition will be used on every event.
    62  func CreateStageDispatcherPartition(n uint, hash func(etf.Term) int) StageDispatcherBehavior {
    63  	if hash == nil {
    64  		hash = func(event etf.Term) int {
    65  			p := rand.Intn(int(n) - 1)
    66  			return p
    67  		}
    68  	}
    69  	return &dispatcherPartition{
    70  		n:    n,
    71  		hash: hash,
    72  	}
    73  }
    74  
    75  type StageDispatchItem struct {
    76  	subscription StageSubscription
    77  	events       etf.List
    78  }
    79  
    80  //
    81  // Dispatcher Demand implementation
    82  //
    83  
    84  type demand struct {
    85  	subscription StageSubscription
    86  	minDemand    uint
    87  	maxDemand    uint
    88  	count        uint
    89  	partition    uint
    90  }
    91  
    92  type demandState struct {
    93  	demands map[etf.Pid]*demand
    94  	order   []etf.Pid
    95  	i       int
    96  	// buffer of events
    97  	events         chan etf.Term
    98  	bufferSize     uint
    99  	bufferKeepLast bool
   100  }
   101  
   102  type partitionState struct {
   103  	demands map[etf.Pid]*demand
   104  	// partitioned
   105  	order  [][]etf.Pid
   106  	i      []int
   107  	events []chan etf.Term
   108  
   109  	bufferSize     uint
   110  	bufferKeepLast bool
   111  }
   112  
   113  type broadcastState struct {
   114  	demands map[etf.Pid]*demand
   115  	// maxDemand should be a min value of all MaxDemand
   116  	maxDemand uint
   117  
   118  	// minDemand should be a max value of all MinDemand
   119  	minDemand uint
   120  
   121  	// Number of broadcast iteration could be done.
   122  	// Computes on every Ask/Cancel call as a minimum value
   123  	// among the all demands.
   124  	broadcasts uint
   125  
   126  	events         chan etf.Term
   127  	bufferSize     uint
   128  	bufferKeepLast bool
   129  }
   130  
   131  // Init
   132  func (dd *dispatcherDemand) Init(opts StageOptions) interface{} {
   133  	state := &demandState{
   134  		demands:        make(map[etf.Pid]*demand),
   135  		i:              0,
   136  		events:         make(chan etf.Term, opts.BufferSize),
   137  		bufferSize:     opts.BufferSize,
   138  		bufferKeepLast: opts.BufferKeepLast,
   139  	}
   140  	return state
   141  }
   142  
   143  // Ask
   144  func (dd *dispatcherDemand) Ask(state interface{}, subscription StageSubscription, count uint) {
   145  	st := state.(*demandState)
   146  	demand, ok := st.demands[subscription.Pid]
   147  	if !ok {
   148  		return
   149  	}
   150  	demand.count += count
   151  	return
   152  }
   153  
   154  // Cancel
   155  func (dd *dispatcherDemand) Cancel(state interface{}, subscription StageSubscription) {
   156  	st := state.(*demandState)
   157  	delete(st.demands, subscription.Pid)
   158  	for i := range st.order {
   159  		if st.order[i] != subscription.Pid {
   160  			continue
   161  		}
   162  		st.order[i] = st.order[0]
   163  		st.order = st.order[1:]
   164  		break
   165  	}
   166  	return
   167  }
   168  
   169  // Dispatch
   170  func (dd *dispatcherDemand) Dispatch(state interface{}, events etf.List) []StageDispatchItem {
   171  	st := state.(*demandState)
   172  	// put events into the buffer before we start dispatching
   173  	for e := range events {
   174  		select {
   175  		case st.events <- events[e]:
   176  			continue
   177  		default:
   178  			// buffer is full
   179  			if st.bufferKeepLast {
   180  				<-st.events
   181  				st.events <- events[e]
   182  				continue
   183  			}
   184  		}
   185  		// dont have enough space to keep these events.
   186  		lib.Warning("DispatcherDemand. Event buffer is full. Discarding event: ", events[e])
   187  		break
   188  	}
   189  
   190  	// check out whether we have subscribers
   191  	if len(st.order) == 0 {
   192  		return nil
   193  	}
   194  
   195  	dispatchItems := []StageDispatchItem{}
   196  	nextDemand := st.i
   197  	for {
   198  		left := uint(0)
   199  		for range st.order {
   200  			if st.i > len(st.order)-1 {
   201  				st.i = 0
   202  			}
   203  
   204  			if len(st.events) == 0 {
   205  				// have nothing to dispatch
   206  				break
   207  			}
   208  
   209  			pid := st.order[st.i]
   210  			demand := st.demands[pid]
   211  			st.i++
   212  
   213  			if demand.count < demand.minDemand {
   214  				break
   215  			}
   216  
   217  			if demand.count == 0 || len(st.events) < int(demand.minDemand) {
   218  				continue
   219  			}
   220  
   221  			nextDemand = st.i
   222  			item := makeDispatchItem(st.events, demand)
   223  			dispatchItems = append(dispatchItems, item)
   224  
   225  			demand.count -= uint(len(item.events))
   226  			left += demand.count
   227  
   228  			if len(st.events) < int(demand.minDemand) {
   229  				continue
   230  			}
   231  		}
   232  		if left > 0 && len(st.events) > 0 {
   233  			continue
   234  		}
   235  		break
   236  	}
   237  
   238  	st.i = nextDemand
   239  
   240  	return dispatchItems
   241  }
   242  
   243  // Subscribe
   244  func (dd *dispatcherDemand) Subscribe(state interface{}, subscription StageSubscription, opts StageSubscribeOptions) error {
   245  	st := state.(*demandState)
   246  	newDemand := &demand{
   247  		subscription: subscription,
   248  		minDemand:    opts.MinDemand,
   249  		maxDemand:    opts.MaxDemand,
   250  	}
   251  	st.demands[subscription.Pid] = newDemand
   252  	st.order = append(st.order, subscription.Pid)
   253  	return nil
   254  }
   255  
   256  //
   257  // Dispatcher Broadcast implementation
   258  //
   259  
   260  // Init
   261  func (db *dispatcherBroadcast) Init(opts StageOptions) interface{} {
   262  	state := &broadcastState{
   263  		demands:        make(map[etf.Pid]*demand),
   264  		events:         make(chan etf.Term, opts.BufferSize),
   265  		bufferSize:     opts.BufferSize,
   266  		bufferKeepLast: opts.BufferKeepLast,
   267  	}
   268  	return state
   269  }
   270  
   271  // Ask
   272  func (db *dispatcherBroadcast) Ask(state interface{}, subscription StageSubscription, count uint) {
   273  	st := state.(*broadcastState)
   274  	demand, ok := st.demands[subscription.Pid]
   275  	if !ok {
   276  		return
   277  	}
   278  	demand.count += count
   279  	st.broadcasts = minCountDemand(st.demands)
   280  	return
   281  }
   282  
   283  // Cancel
   284  func (db *dispatcherBroadcast) Cancel(state interface{}, subscription StageSubscription) {
   285  	st := state.(*broadcastState)
   286  	delete(st.demands, subscription.Pid)
   287  	st.broadcasts = minCountDemand(st.demands)
   288  	return
   289  }
   290  
   291  // Dispatch
   292  func (db *dispatcherBroadcast) Dispatch(state interface{}, events etf.List) []StageDispatchItem {
   293  	st := state.(*broadcastState)
   294  	// put events into the buffer before we start dispatching
   295  	for e := range events {
   296  		select {
   297  		case st.events <- events[e]:
   298  			continue
   299  		default:
   300  			// buffer is full
   301  			if st.bufferKeepLast {
   302  				<-st.events
   303  				st.events <- events[e]
   304  				continue
   305  			}
   306  		}
   307  		// dont have enough space to keep these events.
   308  		lib.Warning("DispatcherBroadcast. Event buffer is full. Discarding event: ", events[e])
   309  		break
   310  	}
   311  
   312  	demand := &demand{
   313  		minDemand: st.minDemand,
   314  		maxDemand: st.maxDemand,
   315  		count:     st.broadcasts,
   316  	}
   317  
   318  	items := []StageDispatchItem{}
   319  	for {
   320  		if st.broadcasts == 0 {
   321  			break
   322  		}
   323  		if len(st.events) < int(st.minDemand) {
   324  			break
   325  		}
   326  
   327  		broadcast_item := makeDispatchItem(st.events, demand)
   328  		for _, d := range st.demands {
   329  			item := StageDispatchItem{
   330  				subscription: d.subscription,
   331  				events:       broadcast_item.events,
   332  			}
   333  			items = append(items, item)
   334  			d.count -= uint(len(item.events))
   335  		}
   336  		st.broadcasts--
   337  	}
   338  	return items
   339  }
   340  
   341  // Subscribe
   342  func (db *dispatcherBroadcast) Subscribe(state interface{}, subscription StageSubscription, opts StageSubscribeOptions) error {
   343  	st := state.(*broadcastState)
   344  	newDemand := &demand{
   345  		subscription: subscription,
   346  		minDemand:    opts.MinDemand,
   347  		maxDemand:    opts.MaxDemand,
   348  	}
   349  	if len(st.demands) == 0 {
   350  		st.minDemand = opts.MinDemand
   351  		st.maxDemand = opts.MaxDemand
   352  		st.demands[subscription.Pid] = newDemand
   353  		return nil
   354  	}
   355  
   356  	// check if min and max outside of the having range
   357  	// defined by the previous subscriptions
   358  	if opts.MaxDemand < st.minDemand {
   359  		return fmt.Errorf("broadcast dispatcher: MaxDemand (%d) outside of the accepted range (%d..%d)", opts.MaxDemand, st.minDemand, st.maxDemand)
   360  	}
   361  	if opts.MinDemand > st.maxDemand {
   362  		return fmt.Errorf("broadcast dispatcher: MinDemand (%d) outside of the accepted range (%d..%d)", opts.MinDemand, st.minDemand, st.maxDemand)
   363  	}
   364  
   365  	// adjust the range
   366  	if opts.MaxDemand < st.maxDemand {
   367  		st.maxDemand = opts.MaxDemand
   368  	}
   369  	if opts.MinDemand > st.minDemand {
   370  		st.minDemand = opts.MinDemand
   371  	}
   372  	st.demands[subscription.Pid] = newDemand
   373  
   374  	// we should stop broadcast events until this subscription make a new demand
   375  	st.broadcasts = 0
   376  	return nil
   377  }
   378  
   379  //
   380  // Dispatcher Partition implementation
   381  //
   382  
   383  // Init
   384  func (dp *dispatcherPartition) Init(opts StageOptions) interface{} {
   385  	state := &partitionState{
   386  		demands:        make(map[etf.Pid]*demand),
   387  		order:          make([][]etf.Pid, dp.n),
   388  		i:              make([]int, dp.n),
   389  		events:         make([]chan etf.Term, dp.n),
   390  		bufferSize:     opts.BufferSize,
   391  		bufferKeepLast: opts.BufferKeepLast,
   392  	}
   393  	for i := range state.events {
   394  		state.events[i] = make(chan etf.Term, state.bufferSize)
   395  	}
   396  	return state
   397  }
   398  
   399  // Ask
   400  func (dp *dispatcherPartition) Ask(state interface{}, subscription StageSubscription, count uint) {
   401  	st := state.(*partitionState)
   402  	demand, ok := st.demands[subscription.Pid]
   403  	if !ok {
   404  		return
   405  	}
   406  	demand.count += count
   407  	return
   408  }
   409  
   410  // Cancel
   411  func (dp *dispatcherPartition) Cancel(state interface{}, subscription StageSubscription) {
   412  	st := state.(*partitionState)
   413  	demand, ok := st.demands[subscription.Pid]
   414  	if !ok {
   415  		return
   416  	}
   417  	delete(st.demands, subscription.Pid)
   418  	for i := range st.order[demand.partition] {
   419  		if st.order[demand.partition][i] != subscription.Pid {
   420  			continue
   421  		}
   422  		st.order[demand.partition][i] = st.order[demand.partition][0]
   423  		st.order[demand.partition] = st.order[demand.partition][1:]
   424  		break
   425  	}
   426  	return
   427  }
   428  
   429  // Dispatch
   430  func (dp *dispatcherPartition) Dispatch(state interface{}, events etf.List) []StageDispatchItem {
   431  	st := state.(*partitionState)
   432  	// put events into the buffer before we start dispatching
   433  	for e := range events {
   434  		partition := dp.hash(events[e])
   435  		if partition < 0 || partition > int(dp.n-1) {
   436  			// discard this event. partition is out of range
   437  			continue
   438  		}
   439  		select {
   440  		case st.events[partition] <- events[e]:
   441  			continue
   442  		default:
   443  			// buffer is full
   444  			if st.bufferKeepLast {
   445  				<-st.events[partition]
   446  				st.events[partition] <- events[e]
   447  				continue
   448  			}
   449  		}
   450  		// dont have enough space to keep these events. discard the rest of them.
   451  		lib.Warning("DispatcherPartition. Event buffer is full. Discarding event: ", events[e])
   452  		break
   453  	}
   454  
   455  	dispatchItems := []StageDispatchItem{}
   456  	for partition := range st.events {
   457  		// do we have anything to dispatch?
   458  		if len(st.events[partition]) == 0 {
   459  			continue
   460  		}
   461  
   462  		nextDemand := st.i[partition]
   463  		for {
   464  			countLeft := uint(0)
   465  			for range st.order[partition] {
   466  				order_index := st.i[partition]
   467  				if order_index > len(st.order[partition])-1 {
   468  					order_index = 0
   469  				}
   470  				if len(st.events[partition]) == 0 {
   471  					// have nothing to dispatch
   472  					break
   473  				}
   474  
   475  				pid := st.order[partition][order_index]
   476  				demand := st.demands[pid]
   477  				st.i[partition] = order_index + 1
   478  
   479  				if demand.count == 0 || len(st.events[partition]) < int(demand.minDemand) {
   480  					continue
   481  				}
   482  
   483  				nextDemand = st.i[partition]
   484  				item := makeDispatchItem(st.events[partition], demand)
   485  				demand.count -= uint(len(st.events[partition]))
   486  				dispatchItems = append(dispatchItems, item)
   487  				if len(st.events[partition]) < int(demand.minDemand) {
   488  					continue
   489  				}
   490  				countLeft += demand.count
   491  			}
   492  			if countLeft > 0 && len(st.events[partition]) > 0 {
   493  				continue
   494  			}
   495  			break
   496  		}
   497  
   498  		st.i[partition] = nextDemand
   499  	}
   500  	return dispatchItems
   501  }
   502  
   503  // Subscribe
   504  func (dp *dispatcherPartition) Subscribe(state interface{}, subscription StageSubscription, opts StageSubscribeOptions) error {
   505  	st := state.(*partitionState)
   506  	if opts.Partition > dp.n-1 {
   507  		return fmt.Errorf("unknown partition")
   508  	}
   509  	newDemand := &demand{
   510  		subscription: subscription,
   511  		minDemand:    opts.MinDemand,
   512  		maxDemand:    opts.MaxDemand,
   513  		partition:    opts.Partition,
   514  	}
   515  	st.demands[subscription.Pid] = newDemand
   516  	st.order[opts.Partition] = append(st.order[opts.Partition], subscription.Pid)
   517  	return nil
   518  }
   519  
   520  // helpers
   521  
   522  func makeDispatchItem(events chan etf.Term, d *demand) StageDispatchItem {
   523  	item := StageDispatchItem{
   524  		subscription: d.subscription,
   525  	}
   526  
   527  	for i := uint(0); i < d.count; i++ {
   528  		if i == d.maxDemand {
   529  			break
   530  		}
   531  		select {
   532  		case e := <-events:
   533  			item.events = append(item.events, e)
   534  			continue
   535  		default:
   536  			// we dont have events in the buffer
   537  		}
   538  		break
   539  	}
   540  	return item
   541  }
   542  
   543  func minCountDemand(demands map[etf.Pid]*demand) uint {
   544  	if len(demands) == 0 {
   545  		return uint(0)
   546  	}
   547  
   548  	minCount := uint(100)
   549  
   550  	for _, d := range demands {
   551  		if d.count < minCount {
   552  			minCount = d.count
   553  		}
   554  	}
   555  	return minCount
   556  }