@@ -48,11 +48,19 @@ const (
4848 Sticky
4949)
5050
51+ // pollHint carries a pre-decided sticky/normal decision from runPoller to PollTask.
52+ // When non-nil, the workflow task poller uses the hint instead of re-deciding.
53+ type pollHint struct {
54+ isSticky bool
55+ }
56+
5157type (
5258 // taskPoller interface to poll for tasks
5359 taskPoller interface {
54- // PollTask polls for one new task
55- PollTask () (taskForWorker , error )
60+ // PollTask polls for one new task. The hint, when non-nil, carries a
61+ // pre-decided sticky/normal choice for workflow task pollers. Non-workflow
62+ // pollers should ignore it.
63+ PollTask (hint * pollHint ) (taskForWorker , error )
5664 // Called when the poller will no longer be polled. Presently only useful for
5765 // workflow workers.
5866 Cleanup () error
@@ -373,10 +381,13 @@ func (wtp *workflowTaskPoller) Cleanup() error {
373381 return err
374382}
375383
376- // PollTask polls a new task
377- func (wtp * workflowTaskPoller ) PollTask () (taskForWorker , error ) {
384+ // PollTask polls a new task. If hint is non-nil, the pre-decided sticky/normal
385+ // choice is used instead of re-deciding in getNextPollRequest.
386+ func (wtp * workflowTaskPoller ) PollTask (hint * pollHint ) (taskForWorker , error ) {
378387 // Get the task.
379- workflowTask , err := wtp .doPoll (wtp .poll )
388+ workflowTask , err := wtp .doPoll (func (ctx context.Context ) (taskForWorker , error ) {
389+ return wtp .poll (ctx , hint )
390+ })
380391 if err != nil {
381392 return nil , err
382393 }
@@ -727,7 +738,7 @@ func (latp *localActivityTaskPoller) Cleanup() error {
727738 return nil
728739}
729740
730- func (latp * localActivityTaskPoller ) PollTask () (taskForWorker , error ) {
741+ func (latp * localActivityTaskPoller ) PollTask (_ * pollHint ) (taskForWorker , error ) {
731742 return latp .laTunnel .getTask (), nil
732743}
733744
@@ -901,16 +912,50 @@ func (wtp *workflowTaskPoller) updateBacklog(taskQueueKind enumspb.TaskQueueKind
901912 wtp .requestLock .Unlock ()
902913}
903914
915+ // decideNextPollKind commits to a sticky/normal decision for the next poll.
916+ // It increments the appropriate pending counter and returns whether the poll
917+ // should target the sticky queue. Only meaningful in Mixed mode; for Sticky and
918+ // NonSticky modes the answer is deterministic and no counter is changed.
919+ func (wtp * workflowTaskPoller ) decideNextPollKind () (isSticky bool ) {
920+ if wtp .mode != Mixed || wtp .stickyCacheSize <= 0 {
921+ return wtp .mode == Sticky
922+ }
923+ wtp .requestLock .Lock ()
924+ defer wtp .requestLock .Unlock ()
925+ if wtp .stickyBacklog > 0 || wtp .pendingStickyPollCount <= wtp .pendingRegularPollCount {
926+ wtp .pendingStickyPollCount ++
927+ return true
928+ }
929+ wtp .pendingRegularPollCount ++
930+ return false
931+ }
932+
933+ // undoPollDecision reverses the counter increment made by decideNextPollKind.
934+ // Call this when a pre-decided poll will not happen (e.g. slot reservation
935+ // was cancelled or failed).
936+ func (wtp * workflowTaskPoller ) undoPollDecision (isSticky bool ) {
937+ if wtp .mode != Mixed || wtp .stickyCacheSize <= 0 {
938+ return
939+ }
940+ if isSticky {
941+ wtp .release (enumspb .TASK_QUEUE_KIND_STICKY )
942+ } else {
943+ wtp .release (enumspb .TASK_QUEUE_KIND_NORMAL )
944+ }
945+ }
946+
904947// getNextPollRequest returns appropriate next poll request based on poller configuration and mode.
905948// Simple rules:
906949// 1. if mode is NonSticky, always poll from regular task queue
907950// 2. if mode is Sticky, always poll from sticky task queue
908951// 3. if mode is Mixed
909952// 3.1. if sticky execution is disabled, always poll for regular task queue
910- // 3.2. otherwise:
911- // 3.2.1) if sticky task queue has backlog, always prefer to process sticky task first
912- // 3.2.2) poll from the task queue that has less pending requests (prefer sticky when they are the same).
913- func (wtp * workflowTaskPoller ) getNextPollRequest () (request * workflowservice.PollWorkflowTaskQueueRequest ) {
953+ // 3.2. otherwise, if a hint is provided, use the pre-decided kind (counter
954+ // was already incremented by decideNextPollKind)
955+ // 3.3. otherwise (no hint / nil hint):
956+ // 3.3.1) if sticky task queue has backlog, always prefer to process sticky task first
957+ // 3.3.2) poll from the task queue that has less pending requests (prefer sticky when they are the same).
958+ func (wtp * workflowTaskPoller ) getNextPollRequest (hint * pollHint ) (request * workflowservice.PollWorkflowTaskQueueRequest ) {
914959 taskQueue := & taskqueuepb.TaskQueue {
915960 Name : wtp .taskQueueName ,
916961 Kind : enumspb .TASK_QUEUE_KIND_NORMAL ,
@@ -923,16 +968,27 @@ func (wtp *workflowTaskPoller) getNextPollRequest() (request *workflowservice.Po
923968 taskQueue .Kind = enumspb .TASK_QUEUE_KIND_STICKY
924969 taskQueue .NormalName = wtp .taskQueueName
925970 } else if wtp .mode == Mixed {
926- wtp .requestLock .Lock ()
927- if wtp .stickyBacklog > 0 || wtp .pendingStickyPollCount <= wtp .pendingRegularPollCount {
928- wtp .pendingStickyPollCount ++
929- taskQueue .Name = getWorkerTaskQueue (wtp .stickyUUID )
930- taskQueue .Kind = enumspb .TASK_QUEUE_KIND_STICKY
931- taskQueue .NormalName = wtp .taskQueueName
971+ if hint != nil {
972+ // Use the pre-decided kind; counter was already incremented
973+ // by decideNextPollKind.
974+ if hint .isSticky {
975+ taskQueue .Name = getWorkerTaskQueue (wtp .stickyUUID )
976+ taskQueue .Kind = enumspb .TASK_QUEUE_KIND_STICKY
977+ taskQueue .NormalName = wtp .taskQueueName
978+ }
932979 } else {
933- wtp .pendingRegularPollCount ++
980+ // Fallback: decide inline (original behavior).
981+ wtp .requestLock .Lock ()
982+ if wtp .stickyBacklog > 0 || wtp .pendingStickyPollCount <= wtp .pendingRegularPollCount {
983+ wtp .pendingStickyPollCount ++
984+ taskQueue .Name = getWorkerTaskQueue (wtp .stickyUUID )
985+ taskQueue .Kind = enumspb .TASK_QUEUE_KIND_STICKY
986+ taskQueue .NormalName = wtp .taskQueueName
987+ } else {
988+ wtp .pendingRegularPollCount ++
989+ }
990+ wtp .requestLock .Unlock ()
934991 }
935- wtp .requestLock .Unlock ()
936992 } else {
937993 panic ("unknown workflow task poller mode" )
938994 }
@@ -973,12 +1029,12 @@ func (wtp *workflowTaskPoller) pollWorkflowTaskQueue(ctx context.Context, reques
9731029}
9741030
9751031// Poll for a single workflow task from the service
976- func (wtp * workflowTaskPoller ) poll (ctx context.Context ) (taskForWorker , error ) {
1032+ func (wtp * workflowTaskPoller ) poll (ctx context.Context , hint * pollHint ) (taskForWorker , error ) {
9771033 traceLog (func () {
9781034 wtp .logger .Debug ("workflowTaskPoller::Poll" )
9791035 })
9801036
981- request := wtp .getNextPollRequest ()
1037+ request := wtp .getNextPollRequest (hint )
9821038 defer wtp .release (request .TaskQueue .GetKind ())
9831039
9841040 response , err := wtp .pollWorkflowTaskQueue (ctx , request )
@@ -1207,7 +1263,7 @@ func (atp *activityTaskPoller) Cleanup() error {
12071263}
12081264
12091265// PollTask polls a new task
1210- func (atp * activityTaskPoller ) PollTask () (taskForWorker , error ) {
1266+ func (atp * activityTaskPoller ) PollTask (_ * pollHint ) (taskForWorker , error ) {
12111267 // Get the task.
12121268 activityTask , err := atp .doPoll (atp .poll )
12131269 if err != nil {
0 commit comments