github.com/ergo-services/ergo@v1.999.224/gen/stage.go (about) 1 package gen 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/ergo-services/ergo/etf" 8 "github.com/ergo-services/ergo/lib" 9 //"github.com/ergo-services/ergo/lib" 10 ) 11 12 type StageCancelMode uint 13 14 // StageOptions defines the producer configuration using Init callback. It will be ignored 15 // if it acts as a consumer only. 16 type StageOptions struct { 17 18 // DisableDemandHandle. the demand is always handling using the HandleDemand callback. 19 // When this options is set to 'true', demands are accumulated until mode is 20 // set back to 'false' using SetDemandHandle(true) method 21 DisableDemandHandle bool 22 23 // BufferSize the size of the buffer to store events without demand. 24 // default value = defaultDispatcherBufferSize 25 BufferSize uint 26 27 // BufferKeepLast defines whether the first or last entries should be 28 // kept on the buffer in case the buffer size is exceeded. 29 BufferKeepLast bool 30 31 Dispatcher StageDispatcherBehavior 32 } 33 34 type StageStatus error 35 36 const ( 37 StageCancelPermanent StageCancelMode = 0 38 StageCancelTransient StageCancelMode = 1 39 StageCancelTemporary StageCancelMode = 2 40 41 defaultDispatcherBufferSize = 10000 42 ) 43 44 var ( 45 StageStatusOK StageStatus = nil 46 StageStatusStop StageStatus = fmt.Errorf("stop") 47 StageStatusUnsupported StageStatus = fmt.Errorf("unsupported") 48 StageStatusNotAProducer StageStatus = fmt.Errorf("not a producer") 49 ) 50 51 // StageBehavior interface for the Stage inmplementation 52 type StageBehavior interface { 53 ServerBehavior 54 55 // InitStage 56 InitStage(process *StageProcess, args ...etf.Term) (StageOptions, error) 57 58 // HandleDemand this callback is invoked on a producer stage 59 // The producer that implements this callback must either store the demand, or return the amount of requested events. 60 HandleDemand(process *StageProcess, subscription StageSubscription, count uint) (etf.List, StageStatus) 61 62 // HandleEvents this callback is invoked on a consumer stage. 63 HandleEvents(process *StageProcess, subscription StageSubscription, events etf.List) StageStatus 64 65 // HandleSubscribe This callback is invoked on a producer stage. 66 HandleSubscribe(process *StageProcess, subscription StageSubscription, options StageSubscribeOptions) StageStatus 67 68 // HandleSubscribed this callback is invoked as a confirmation for the subscription request 69 // Returning false means that demand must be sent to producers explicitly using Ask method. 70 // Returning true means the stage implementation will take care of automatically sending. 71 HandleSubscribed(process *StageProcess, subscription StageSubscription, opts StageSubscribeOptions) (bool, StageStatus) 72 73 // HandleCancel 74 // Invoked when a consumer is no longer subscribed to a producer (invoked on a producer stage) 75 // The cancelReason will be a {Cancel: "cancel", Reason: _} if the reason for cancellation 76 // was a Stage.Cancel call. Any other value means the cancellation reason was 77 // due to an EXIT. 78 HandleCancel(process *StageProcess, subscription StageSubscription, reason string) StageStatus 79 80 // HandleCanceled 81 // Invoked when a consumer is no longer subscribed to a producer (invoked on a consumer stage) 82 // Termination this stage depends on a cancel mode for the given subscription. For the cancel mode 83 // StageCancelPermanent - this stage will be terminated right after this callback invoking. 84 // For the cancel mode StageCancelTransient - it depends on a reason of subscription canceling. 85 // Cancel mode StageCancelTemporary keeps this stage alive whether the reason could be. 86 HandleCanceled(process *StageProcess, subscription StageSubscription, reason string) StageStatus 87 88 // HandleStageCall this callback is invoked on ServerProcess.Call. This method is optional 89 // for the implementation 90 HandleStageCall(process *StageProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) 91 // HandleStageDirect this callback is invoked on Process.Direct. This method is optional 92 // for the implementation 93 HandleStageDirect(process *StageProcess, ref etf.Ref, message interface{}) (interface{}, DirectStatus) 94 // HandleStageCast this callback is invoked on ServerProcess.Cast. This method is optional 95 // for the implementation 96 HandleStageCast(process *StageProcess, message etf.Term) ServerStatus 97 // HandleStageInfo this callback is invoked on Process.Send. This method is optional 98 // for the implementation 99 HandleStageInfo(process *StageProcess, message etf.Term) ServerStatus 100 // HandleStageTerminate this callback is invoked on a termination process 101 HandleStageTerminate(process *StageProcess, reason string) 102 } 103 104 type StageSubscription struct { 105 Pid etf.Pid 106 ID etf.Ref 107 } 108 109 type subscriptionInternal struct { 110 Producer etf.Term 111 Subscription StageSubscription 112 options StageSubscribeOptions 113 Monitor etf.Ref 114 // number of event requests (demands) made as a consumer. 115 count uint 116 } 117 118 type StageSubscribeOptions struct { 119 MinDemand uint `etf:"min_demand"` 120 MaxDemand uint `etf:"max_demand"` 121 // The stage implementation will take care of automatically sending 122 // demand to producer (as a default behavior). You can disable it 123 // setting ManualDemand to true 124 ManualDemand bool `etf:"manual"` 125 // What should happened with consumer if producer has terminated 126 // StageCancelPermanent the consumer exits when the producer cancels or exits. 127 // StageCancelTransient the consumer exits only if reason is not "normal", 128 // "shutdown", or {"shutdown", _} 129 // StageCancelTemporary the consumer never exits 130 Cancel StageCancelMode `etf:"cancel"` 131 132 // Partition is defined the number of partition this subscription should belongs to. 133 // This option uses in the DispatcherPartition 134 Partition uint `etf:"partition"` 135 136 // Extra is intended to be a custom set of options for the custom implementation 137 // of StageDispatcherBehavior 138 Extra etf.Term `etf:"extra"` 139 } 140 141 type StageCancelReason struct { 142 Cancel string 143 Reason string 144 } 145 146 type Stage struct { 147 Server 148 } 149 150 type StageProcess struct { 151 ServerProcess 152 153 options StageOptions 154 demandBuffer []demandRequest 155 dispatcherState interface{} 156 // keep our subscriptions 157 producers map[etf.Ref]*subscriptionInternal 158 // keep our subscribers 159 consumers map[etf.Pid]*subscriptionInternal 160 // 161 behavior StageBehavior 162 } 163 164 type stageRequestCommand struct { 165 Cmd etf.Atom 166 Opt1 interface{} 167 Opt2 interface{} 168 } 169 170 type stageMessage struct { 171 Request etf.Atom 172 Subscription StageSubscription 173 Command interface{} 174 } 175 176 type setManualDemand struct { 177 subscription StageSubscription 178 enable bool 179 } 180 181 type setCancelMode struct { 182 subscription StageSubscription 183 cancel StageCancelMode 184 } 185 186 type setForwardDemand struct { 187 forward bool 188 } 189 190 type demandRequest struct { 191 subscription StageSubscription 192 count uint 193 } 194 195 // SetCancelMode defines how consumer will handle termination of the producer. There are 3 modes: 196 // StageCancelPermanent (default) - consumer exits when the producer cancels or exits 197 // StageCancelTransient - consumer exits only if reason is not normal, shutdown, or {shutdown, reason} 198 // StageCancelTemporary - never exits 199 func (s *Stage) SetCancelMode(p Process, subscription StageSubscription, cancel StageCancelMode) error { 200 message := setCancelMode{ 201 subscription: subscription, 202 cancel: cancel, 203 } 204 205 _, err := p.Direct(message) 206 return err 207 } 208 209 // 210 // StageProcess methods 211 // 212 213 // SetAutoDemand setting this option to false means that demand must be sent to producers 214 // explicitly using Ask method. This mode can be used when a special behavior is desired. 215 // Setting this options to true enables auto demand mode (this is default mode for the consumer) 216 func (p *StageProcess) SetAutoDemand(subscription StageSubscription, autodemand bool) error { 217 subInternal, ok := p.producers[subscription.ID] 218 if !ok { 219 return fmt.Errorf("unknown subscription") 220 } 221 subInternal.options.ManualDemand = autodemand == false 222 if subInternal.count < subInternal.options.MinDemand && subInternal.options.ManualDemand == false { 223 cnt := subInternal.options.MaxDemand - subInternal.count 224 p.sendDemand(subInternal.Producer, subscription, cnt) 225 subInternal.count += cnt 226 } 227 return nil 228 } 229 230 // AutoDemand returns value of the auto demand option 231 func (p *StageProcess) AutoDemand(subscription StageSubscription) (bool, error) { 232 subInternal, ok := p.producers[subscription.ID] 233 if !ok { 234 return false, fmt.Errorf("unknown subscription") 235 } 236 return subInternal.options.ManualDemand == false, nil 237 } 238 239 // SetDemandHandle setting this option to false disables handling demand requests on a producer stage. 240 // This is useful as a synchronization mechanism, where the demand is accumulated until 241 // all consumers are subscribed. By default this option is true. 242 func (p *StageProcess) SetDemandHandle(enable bool) { 243 p.options.DisableDemandHandle = enable == false 244 if enable == true { 245 // create demand with count = 0, which will be ignored but start 246 // the processing of the buffered demands 247 msg := etf.Tuple{ 248 etf.Atom("$gen_producer"), 249 etf.Tuple{etf.Pid{}, etf.Ref{}}, 250 etf.Tuple{etf.Atom("ask"), 0}, 251 } 252 p.Send(p.Self(), msg) 253 } 254 } 255 256 // DemandHandle returns whether enabled handling demand requests. 257 func (p *StageProcess) DemandHandle() bool { 258 return p.options.DisableDemandHandle == false 259 } 260 261 // SetCancelMode defines how consumer will handle termination of the producer. There are 3 modes: 262 // StageCancelPermanent (default) - consumer exits when the producer cancels or exits 263 // StageCancelTransient - consumer exits only if reason is not normal, shutdown, or {shutdown, reason} 264 // StageCancelTemporary - never exits 265 func (p *StageProcess) SetCancelMode(subscription StageSubscription, mode StageCancelMode) error { 266 subInternal, ok := p.producers[subscription.ID] 267 if !ok { 268 return fmt.Errorf("unknown subscription") 269 } 270 271 subInternal.options.Cancel = mode 272 return nil 273 } 274 275 // CancelMode returns current cancel mode for the consumer 276 func (p *StageProcess) CancelMode(subscription StageSubscription) (StageCancelMode, error) { 277 subInternal, ok := p.producers[subscription.ID] 278 if !ok { 279 return 0, fmt.Errorf("unknown subscription") 280 } 281 282 return subInternal.options.Cancel, nil 283 } 284 285 // Subscribe subscribes to the given producer. HandleSubscribed callback will be invoked 286 // on a consumer stage once a request for the subscription is sent. If something went wrong 287 // on a producer side the callback HandleCancel will be invoked with a reason of cancelation. 288 func (p *StageProcess) Subscribe(producer etf.Term, opts StageSubscribeOptions) (StageSubscription, error) { 289 var subscription StageSubscription 290 switch producer.(type) { 291 case string: 292 case etf.Pid: 293 case ProcessID: 294 default: 295 return subscription, fmt.Errorf("allowed type for producer: etf.Pid, string, gen.ProcessID") 296 } 297 298 subscription_id := p.MonitorProcess(producer) 299 subscription.Pid = p.Self() 300 subscription.ID = subscription_id 301 302 subscribe_opts := etf.List{ 303 etf.Tuple{ 304 etf.Atom("min_demand"), 305 opts.MinDemand, 306 }, 307 etf.Tuple{ 308 etf.Atom("max_demand"), 309 opts.MaxDemand, 310 }, 311 etf.Tuple{ 312 etf.Atom("cancel"), 313 int(opts.Cancel), // custom types couldn't be handled by etf.Encode 314 }, 315 etf.Tuple{ 316 etf.Atom("manual"), 317 opts.ManualDemand, 318 }, 319 etf.Tuple{ 320 etf.Atom("partition"), 321 opts.Partition, 322 }, 323 } 324 325 // In order to get rid of race condition we should send this message 326 // before we send 'subscribe' to the producer process. Just 327 // to make sure if we registered this subscription before the MessageDown 328 // or MessageExit message arrived in case of something went wrong. 329 msg := etf.Tuple{ 330 etf.Atom("$gen_consumer"), 331 etf.Tuple{p.Self(), subscription_id}, 332 etf.Tuple{etf.Atom("subscribed"), producer, subscribe_opts}, 333 } 334 p.Send(p.Self(), msg) 335 336 msg = etf.Tuple{ 337 etf.Atom("$gen_producer"), 338 etf.Tuple{p.Self(), subscription_id}, 339 etf.Tuple{etf.Atom("subscribe"), etf.Atom("nil"), subscribe_opts}, 340 } 341 p.Send(producer, msg) 342 343 return subscription, nil 344 } 345 346 // SendEvents sends events to the subscribers 347 func (p *StageProcess) SendEvents(events etf.List) error { 348 var deliver []StageDispatchItem 349 // dispatch to the subscribers 350 if len(p.consumers) == 0 { 351 return fmt.Errorf("no subscribers") 352 } 353 deliver = p.options.Dispatcher.Dispatch(p.dispatcherState, events) 354 if len(deliver) == 0 { 355 return fmt.Errorf("no demand") 356 } 357 for d := range deliver { 358 msg := etf.Tuple{ 359 etf.Atom("$gen_consumer"), 360 etf.Tuple{deliver[d].subscription.Pid, deliver[d].subscription.ID}, 361 deliver[d].events, 362 } 363 p.Send(deliver[d].subscription.Pid, msg) 364 } 365 return nil 366 } 367 368 // Ask makes a demand request for the given subscription. This function must only be 369 // used in the cases when a consumer sets a subscription to manual mode using DisableAutoDemand 370 func (p *StageProcess) Ask(subscription StageSubscription, count uint) error { 371 subInternal, ok := p.producers[subscription.ID] 372 if ok == false { 373 return fmt.Errorf("unknown subscription") 374 } 375 if subInternal.options.ManualDemand == false { 376 return fmt.Errorf("auto demand") 377 } 378 379 p.sendDemand(subInternal.Producer, subInternal.Subscription, count) 380 subInternal.count += count 381 return nil 382 } 383 384 // Cancel 385 func (p *StageProcess) Cancel(subscription StageSubscription, reason string) error { 386 // if we act as a consumer with this subscription 387 if subInternal, ok := p.producers[subscription.ID]; ok { 388 msg := etf.Tuple{ 389 etf.Atom("$gen_producer"), 390 etf.Tuple{subscription.Pid, subscription.ID}, 391 etf.Tuple{etf.Atom("cancel"), reason}, 392 } 393 p.Send(subInternal.Producer, msg) 394 cmd := stageRequestCommand{ 395 Cmd: etf.Atom("cancel"), 396 Opt1: "normal", 397 } 398 if _, err := p.handleConsumer(subInternal.Subscription, cmd); err != nil { 399 return err 400 } 401 return nil 402 } 403 // if we act as a producer within this subscription 404 if subInternal, ok := p.consumers[subscription.Pid]; ok { 405 msg := etf.Tuple{ 406 etf.Atom("$gen_consumer"), 407 etf.Tuple{subscription.Pid, subscription.ID}, 408 etf.Tuple{etf.Atom("cancel"), reason}, 409 } 410 p.Send(subscription.Pid, msg) 411 p.DemonitorProcess(subInternal.Monitor) 412 cmd := stageRequestCommand{ 413 Cmd: etf.Atom("cancel"), 414 Opt1: "normal", 415 } 416 if _, err := p.handleProducer(subInternal.Subscription, cmd); err != nil { 417 return err 418 } 419 return nil 420 } 421 return fmt.Errorf("unknown subscription") 422 423 } 424 425 // gen.Server callbacks 426 func (gst *Stage) Init(process *ServerProcess, args ...etf.Term) error { 427 stageProcess := &StageProcess{ 428 ServerProcess: *process, 429 producers: make(map[etf.Ref]*subscriptionInternal), 430 consumers: make(map[etf.Pid]*subscriptionInternal), 431 } 432 // do not inherit parent State 433 stageProcess.State = nil 434 435 behavior := process.Behavior().(StageBehavior) 436 behavior, ok := process.Behavior().(StageBehavior) 437 if !ok { 438 return fmt.Errorf("Stage: not a StageBehavior") 439 } 440 stageProcess.behavior = behavior 441 442 stageOpts, err := behavior.InitStage(stageProcess, args...) 443 if err != nil { 444 return err 445 } 446 447 if stageOpts.BufferSize == 0 { 448 stageOpts.BufferSize = defaultDispatcherBufferSize 449 } 450 451 // if dispatcher wasn't specified create a default one StageDispatcherDemand 452 if stageOpts.Dispatcher == nil { 453 stageOpts.Dispatcher = CreateStageDispatcherDemand() 454 } 455 456 stageProcess.dispatcherState = stageOpts.Dispatcher.Init(stageOpts) 457 stageProcess.options = stageOpts 458 459 process.State = stageProcess 460 return nil 461 } 462 463 func (gst *Stage) HandleCall(process *ServerProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) { 464 stageProcess := process.State.(*StageProcess) 465 return stageProcess.behavior.HandleStageCall(stageProcess, from, message) 466 } 467 468 func (gst *Stage) HandleDirect(process *ServerProcess, ref etf.Ref, message interface{}) (interface{}, DirectStatus) { 469 stageProcess := process.State.(*StageProcess) 470 return stageProcess.behavior.HandleStageDirect(stageProcess, ref, message) 471 } 472 473 func (gst *Stage) HandleCast(process *ServerProcess, message etf.Term) ServerStatus { 474 stageProcess := process.State.(*StageProcess) 475 return stageProcess.behavior.HandleStageCast(stageProcess, message) 476 } 477 478 func (gst *Stage) HandleInfo(process *ServerProcess, message etf.Term) ServerStatus { 479 var r stageMessage 480 481 stageProcess := process.State.(*StageProcess) 482 483 // check if we got a MessageDown 484 if d, isDown := IsMessageDown(message); isDown { 485 if err := stageProcess.handleStageDown(d); err != nil { 486 return err 487 } 488 return ServerStatusOK 489 } 490 491 if err := etf.TermIntoStruct(message, &r); err != nil { 492 reply := stageProcess.behavior.HandleStageInfo(stageProcess, message) 493 return reply 494 } 495 496 _, err := stageProcess.handleStageRequest(r) 497 498 switch err { 499 case nil: 500 return ServerStatusOK 501 case StageStatusStop: 502 return ServerStatusStop 503 case StageStatusUnsupported: 504 status := stageProcess.behavior.HandleStageInfo(stageProcess, message) 505 return status 506 default: 507 return err 508 } 509 } 510 511 func (gst *Stage) Terminate(process *ServerProcess, reason string) { 512 stageProcess := process.State.(*StageProcess) 513 stageProcess.behavior.HandleStageTerminate(stageProcess, reason) 514 } 515 516 // default callbacks 517 518 // InitStage 519 func (gst *Stage) InitStage(process *StageProcess, args ...etf.Term) error { 520 return nil 521 } 522 523 // HandleSagaCall 524 func (gst *Stage) HandleStageCall(process *StageProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) { 525 // default callback if it wasn't implemented 526 lib.Warning("HandleStageCall: unhandled message (from %#v) %#v", from, message) 527 return etf.Atom("ok"), ServerStatusOK 528 } 529 530 // HandleStageDirect 531 func (gst *Stage) HandleStageDirect(process *StageProcess, ref etf.Ref, message interface{}) (interface{}, DirectStatus) { 532 // default callback if it wasn't implemented 533 return nil, lib.ErrUnsupportedRequest 534 } 535 536 // HandleStageCast 537 func (gst *Stage) HandleStageCast(process *StageProcess, message etf.Term) ServerStatus { 538 // default callback if it wasn't implemented 539 lib.Warning("HandleStageCast: unhandled message %#v", message) 540 return ServerStatusOK 541 } 542 543 // HandleStageInfo 544 func (gst *Stage) HandleStageInfo(process *StageProcess, message etf.Term) ServerStatus { 545 // default callback if it wasn't implemnted 546 lib.Warning("HandleStageInfo: unhandled message %#v", message) 547 return ServerStatusOK 548 } 549 550 func (gst *Stage) HandleStageTerminate(process *StageProcess, reason string) { 551 return 552 } 553 554 // HandleSubscribe 555 func (gst *Stage) HandleSubscribe(process *StageProcess, subscription StageSubscription, options StageSubscribeOptions) StageStatus { 556 return StageStatusNotAProducer 557 } 558 559 // HandleSubscribed 560 func (gst *Stage) HandleSubscribed(process *StageProcess, subscription StageSubscription, opts StageSubscribeOptions) (bool, StageStatus) { 561 return opts.ManualDemand, StageStatusOK 562 } 563 564 // HandleCancel 565 func (gst *Stage) HandleCancel(process *StageProcess, subscription StageSubscription, reason string) StageStatus { 566 // default callback if it wasn't implemented 567 return StageStatusOK 568 } 569 570 // HandleCanceled 571 func (gst *Stage) HandleCanceled(process *StageProcess, subscription StageSubscription, reason string) StageStatus { 572 // default callback if it wasn't implemented 573 return StageStatusOK 574 } 575 576 // HanndleEvents 577 func (gst *Stage) HandleEvents(process *StageProcess, subscription StageSubscription, events etf.List) StageStatus { 578 lib.Warning("Stage HandleEvents: unhandled subscription (%#v) events %#v", subscription, events) 579 return StageStatusOK 580 } 581 582 // HandleDemand 583 func (gst *Stage) HandleDemand(process *StageProcess, subscription StageSubscription, count uint) (etf.List, StageStatus) { 584 lib.Warning("Stage HandleDemand: unhandled subscription (%#v) demand %#v", subscription, count) 585 return nil, StageStatusOK 586 } 587 588 // private functions 589 590 func (p *StageProcess) handleStageRequest(m stageMessage) (etf.Term, StageStatus) { 591 var command stageRequestCommand 592 switch m.Request { 593 case "$gen_consumer": 594 // I wish i had {events, [...]} for the events message (in 595 // fashion of the other messages), but the original autors 596 // made this way, so i have to use this little hack in order 597 // to use the same handler 598 if cmd, ok := m.Command.(etf.List); ok { 599 command.Cmd = etf.Atom("events") 600 command.Opt1 = cmd 601 return p.handleConsumer(m.Subscription, command) 602 } 603 if err := etf.TermIntoStruct(m.Command, &command); err != nil { 604 return nil, StageStatusUnsupported 605 } 606 return p.handleConsumer(m.Subscription, command) 607 case "$gen_producer": 608 if err := etf.TermIntoStruct(m.Command, &command); err != nil { 609 return nil, StageStatusUnsupported 610 } 611 return p.handleProducer(m.Subscription, command) 612 } 613 return nil, StageStatusUnsupported 614 } 615 616 func (p *StageProcess) handleConsumer(subscription StageSubscription, cmd stageRequestCommand) (etf.Term, error) { 617 var subscriptionOpts StageSubscribeOptions 618 var err error 619 620 switch cmd.Cmd { 621 case etf.Atom("events"): 622 events := cmd.Opt1.(etf.List) 623 numEvents := len(events) 624 625 subInternal, ok := p.producers[subscription.ID] 626 if !ok { 627 lib.Warning("consumer got %d events for unknown subscription %#v", numEvents, subscription) 628 return etf.Atom("ok"), nil 629 } 630 subInternal.count -= uint(numEvents) 631 if subInternal.count < 0 { 632 return nil, fmt.Errorf("got %d events which haven't bin requested", numEvents) 633 } 634 if numEvents < int(subInternal.options.MinDemand) { 635 return nil, fmt.Errorf("got %d events which is less than min %d", numEvents, subInternal.options.MinDemand) 636 } 637 if numEvents > int(subInternal.options.MaxDemand) { 638 return nil, fmt.Errorf("got %d events which is more than max %d", numEvents, subInternal.options.MaxDemand) 639 } 640 641 err = p.behavior.HandleEvents(p, subscription, events) 642 if err != nil { 643 return nil, err 644 } 645 646 // if subscription has auto demand we should request yet another 647 // bunch of events 648 if subInternal.count < subInternal.options.MinDemand && subInternal.options.ManualDemand == false { 649 650 cnt := subInternal.options.MaxDemand - subInternal.count 651 p.sendDemand(subInternal.Producer, subscription, cnt) 652 subInternal.count += cnt 653 } 654 return etf.Atom("ok"), nil 655 656 case etf.Atom("subscribed"): 657 if err := etf.TermProplistIntoStruct(cmd.Opt2, &subscriptionOpts); err != nil { 658 return nil, err 659 } 660 661 manualDemand, status := p.behavior.HandleSubscribed(p, subscription, subscriptionOpts) 662 663 if status != StageStatusOK { 664 return nil, status 665 } 666 subscriptionOpts.ManualDemand = manualDemand 667 668 producer := cmd.Opt1 669 subInternal := &subscriptionInternal{ 670 Subscription: subscription, 671 Producer: producer, 672 options: subscriptionOpts, 673 } 674 p.producers[subscription.ID] = subInternal 675 676 if manualDemand == false { 677 p.sendDemand(producer, subscription, subInternal.options.MaxDemand) 678 subInternal.count = subInternal.options.MaxDemand 679 } 680 681 return etf.Atom("ok"), nil 682 683 case etf.Atom("retry-cancel"): 684 // if "subscribed" message hasn't still arrived then just ignore it 685 if _, ok := p.producers[subscription.ID]; !ok { 686 return etf.Atom("ok"), nil 687 } 688 fallthrough 689 case etf.Atom("cancel"): 690 // the subscription was canceled 691 reason, ok := cmd.Opt1.(string) 692 if !ok { 693 return nil, fmt.Errorf("Cancel reason is not a string") 694 } 695 696 subInternal, ok := p.producers[subscription.ID] 697 if !ok { 698 // There might be a case when "cancel" message arrives before 699 // the "subscribed" message due to async nature of messaging, 700 // so we should wait a bit and try to handle it one more time 701 // using "retry-cancel" message. 702 // I got this problem with GOMAXPROCS=1 703 msg := etf.Tuple{ 704 etf.Atom("$gen_consumer"), 705 etf.Tuple{subscription.Pid, subscription.ID}, 706 etf.Tuple{etf.Atom("retry-cancel"), reason}, 707 } 708 // handle it in a second 709 p.SendAfter(p.Self(), msg, 200*time.Millisecond) 710 return etf.Atom("ok"), nil 711 } 712 713 // if we already handle MessageDown skip it 714 if reason != "noconnection" { 715 p.DemonitorProcess(subscription.ID) 716 } 717 delete(p.producers, subscription.ID) 718 719 err = p.behavior.HandleCanceled(p, subscription, reason) 720 if err != nil { 721 return nil, err 722 } 723 724 switch subInternal.options.Cancel { 725 case StageCancelTemporary: 726 return etf.Atom("ok"), nil 727 case StageCancelTransient: 728 if reason == "normal" || reason == "shutdown" { 729 return etf.Atom("ok"), nil 730 } 731 return nil, fmt.Errorf(reason) 732 default: 733 // StageCancelPermanent 734 return nil, fmt.Errorf(reason) 735 } 736 } 737 738 return nil, fmt.Errorf("unknown Stage command (HandleCast)") 739 } 740 741 func (p *StageProcess) handleProducer(subscription StageSubscription, cmd stageRequestCommand) (etf.Term, error) { 742 var subscriptionOpts StageSubscribeOptions 743 var err error 744 745 switch cmd.Cmd { 746 case etf.Atom("subscribe"): 747 // {subscribe, Cancel, Opts} 748 if err = etf.TermProplistIntoStruct(cmd.Opt2, &subscriptionOpts); err != nil { 749 return nil, err 750 } 751 752 // TODO handle cmd.Opts1 - could be etf.Atom("nil") or list of subscriptions 753 // for the cancelation 754 755 if subscriptionOpts.MinDemand > subscriptionOpts.MaxDemand { 756 msg := etf.Tuple{ 757 etf.Atom("$gen_consumer"), 758 etf.Tuple{subscription.Pid, subscription.ID}, 759 etf.Tuple{etf.Atom("cancel"), fmt.Errorf("MinDemand greater MaxDemand")}, 760 } 761 p.Send(subscription.Pid, msg) 762 return etf.Atom("ok"), nil 763 } 764 765 err = p.behavior.HandleSubscribe(p, subscription, subscriptionOpts) 766 767 switch err { 768 case nil: 769 // cancel current subscription if this consumer has been already subscribed 770 if s, ok := p.consumers[subscription.Pid]; ok { 771 msg := etf.Tuple{ 772 etf.Atom("$gen_consumer"), 773 etf.Tuple{subscription.Pid, s.Subscription.ID}, 774 etf.Tuple{etf.Atom("cancel"), "resubscribed"}, 775 } 776 p.Send(subscription.Pid, msg) 777 // notify dispatcher about cancelation the previous subscription 778 canceledSubscription := StageSubscription{ 779 Pid: subscription.Pid, 780 ID: s.Subscription.ID, 781 } 782 // cancel current demands 783 p.options.Dispatcher.Cancel(p.dispatcherState, canceledSubscription) 784 // notify dispatcher about the new subscription 785 if err := p.options.Dispatcher.Subscribe(p.dispatcherState, subscription, subscriptionOpts); err != nil { 786 // dispatcher can't handle this subscription 787 msg := etf.Tuple{ 788 etf.Atom("$gen_consumer"), 789 etf.Tuple{subscription.Pid, s.Subscription.ID}, 790 etf.Tuple{etf.Atom("cancel"), err.Error()}, 791 } 792 p.Send(subscription.Pid, msg) 793 return etf.Atom("ok"), nil 794 } 795 796 s.Subscription = subscription 797 return etf.Atom("ok"), nil 798 } 799 800 if err := p.options.Dispatcher.Subscribe(p.dispatcherState, subscription, subscriptionOpts); err != nil { 801 // dispatcher can't handle this subscription 802 msg := etf.Tuple{ 803 etf.Atom("$gen_consumer"), 804 etf.Tuple{subscription.Pid, subscription.ID}, 805 etf.Tuple{etf.Atom("cancel"), err.Error()}, 806 } 807 p.Send(subscription.Pid, msg) 808 return etf.Atom("ok"), nil 809 } 810 811 // monitor subscriber in order to remove this subscription 812 // if it terminated unexpectedly 813 m := p.MonitorProcess(subscription.Pid) 814 s := &subscriptionInternal{ 815 Subscription: subscription, 816 Monitor: m, 817 options: subscriptionOpts, 818 } 819 p.consumers[subscription.Pid] = s 820 return etf.Atom("ok"), nil 821 822 case StageStatusNotAProducer: 823 // if it wasnt overloaded - send 'cancel' to the consumer 824 msg := etf.Tuple{ 825 etf.Atom("$gen_consumer"), 826 etf.Tuple{subscription.Pid, subscription.ID}, 827 etf.Tuple{etf.Atom("cancel"), err.Error()}, 828 } 829 p.Send(subscription.Pid, msg) 830 return etf.Atom("ok"), nil 831 832 default: 833 // any other error should terminate this stage 834 return nil, err 835 } 836 case etf.Atom("retry-ask"): 837 // if the "subscribe" message hasn't still arrived, send a cancelation message 838 // to the consumer 839 if _, ok := p.consumers[subscription.Pid]; !ok { 840 msg := etf.Tuple{ 841 etf.Atom("$gen_consumer"), 842 etf.Tuple{subscription.Pid, subscription.ID}, 843 etf.Tuple{etf.Atom("cancel"), "not subscribed"}, 844 } 845 p.Send(subscription.Pid, msg) 846 return etf.Atom("ok"), nil 847 } 848 fallthrough 849 850 case etf.Atom("ask"): 851 var events etf.List 852 var deliver []StageDispatchItem 853 var count uint 854 switch c := cmd.Opt1.(type) { 855 case int: 856 count = uint(c) 857 case uint: 858 count = c 859 default: 860 return nil, fmt.Errorf("Demand has wrong value %#v. Expected positive integer", cmd.Opt1) 861 } 862 863 // handle buffered demand on exit this function 864 defer func() { 865 if p.options.DisableDemandHandle { 866 return 867 } 868 if len(p.demandBuffer) == 0 { 869 return 870 } 871 d := p.demandBuffer[0] 872 msg := etf.Tuple{ 873 etf.Atom("$gen_producer"), 874 etf.Tuple{d.subscription.Pid, d.subscription.ID}, 875 etf.Tuple{etf.Atom("ask"), d.count}, 876 } 877 p.Send(p.Self(), msg) 878 p.demandBuffer = p.demandBuffer[1:] 879 }() 880 881 if count == 0 { 882 // just ignore it 883 return etf.Atom("ok"), nil 884 } 885 886 if _, ok := p.consumers[subscription.Pid]; !ok { 887 // there might be a case when "ask" message arrives before 888 // the "subscribe" message due to async nature of messaging, 889 // so we should wait a bit and try to handle it one more time 890 // using "retry-ask" message 891 msg := etf.Tuple{ 892 etf.Atom("$gen_producer"), 893 etf.Tuple{subscription.Pid, subscription.ID}, 894 etf.Tuple{etf.Atom("retry-ask"), count}, 895 } 896 // handle it in a second 897 p.SendAfter(p.Self(), msg, 1*time.Second) 898 return etf.Atom("ok"), nil 899 } 900 901 if p.options.DisableDemandHandle { 902 d := demandRequest{ 903 subscription: subscription, 904 count: count, 905 } 906 // FIXME it would be more effective to use sync.Pool with 907 // preallocated array behind the slice. 908 // see how it was made in lib.TakeBuffer 909 p.demandBuffer = append(p.demandBuffer, d) 910 return etf.Atom("ok"), nil 911 } 912 913 events, _ = p.behavior.HandleDemand(p, subscription, count) 914 915 // register this demand and trying to dispatch having events 916 dispatcher := p.options.Dispatcher 917 dispatcher.Ask(p.dispatcherState, subscription, count) 918 deliver = dispatcher.Dispatch(p.dispatcherState, events) 919 if len(deliver) == 0 { 920 return etf.Atom("ok"), nil 921 } 922 923 for d := range deliver { 924 msg := etf.Tuple{ 925 etf.Atom("$gen_consumer"), 926 etf.Tuple{deliver[d].subscription.Pid, deliver[d].subscription.ID}, 927 deliver[d].events, 928 } 929 p.Send(deliver[d].subscription.Pid, msg) 930 } 931 932 return etf.Atom("ok"), nil 933 934 case etf.Atom("cancel"): 935 var e error 936 // handle this cancelation in the dispatcher 937 dispatcher := p.options.Dispatcher 938 dispatcher.Cancel(p.dispatcherState, subscription) 939 reason := cmd.Opt1.(string) 940 // handle it in a Stage callback 941 e = p.behavior.HandleCancel(p, subscription, reason) 942 delete(p.consumers, subscription.Pid) 943 return etf.Atom("ok"), e 944 } 945 946 return nil, fmt.Errorf("unknown Stage command (HandleCall)") 947 } 948 949 func (p *StageProcess) handleStageDown(down MessageDown) error { 950 // remove subscription for producer and consumer. corner case - two 951 // processes have subscribed to each other. 952 953 // checking for subscribers (if we act as a producer). 954 // we monitor them by Pid only 955 if subInternal, ok := p.consumers[down.Pid]; ok { 956 // producer monitors consumer by the Pid and stores monitor reference 957 // in the subInternal struct 958 p.DemonitorProcess(subInternal.Monitor) 959 cmd := stageRequestCommand{ 960 Cmd: etf.Atom("cancel"), 961 Opt1: down.Reason, 962 } 963 if _, err := p.handleProducer(subInternal.Subscription, cmd); err != nil { 964 return err 965 } 966 } 967 968 // checking for producers (if we act as a consumer) 969 if subInternal, ok := p.producers[down.Ref]; ok { 970 971 cmd := stageRequestCommand{ 972 Cmd: etf.Atom("cancel"), 973 Opt1: down.Reason, 974 } 975 976 if _, err := p.handleConsumer(subInternal.Subscription, cmd); err != nil { 977 return err 978 } 979 } 980 981 return nil 982 } 983 984 // for the consumer side only 985 func (p *StageProcess) sendDemand(producer etf.Term, subscription StageSubscription, count uint) { 986 msg := etf.Tuple{ 987 etf.Atom("$gen_producer"), 988 etf.Tuple{p.Self(), subscription.ID}, 989 etf.Tuple{etf.Atom("ask"), count}, 990 } 991 p.Send(producer, msg) 992 }