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 }