@@ -2,11 +2,14 @@ package nexusoperation
22
33import (
44 "context"
5+ "errors"
56
67 enumspb "go.temporal.io/api/enums/v1"
8+ "go.temporal.io/api/serviceerror"
79 "go.temporal.io/api/workflowservice/v1"
810 "go.temporal.io/server/chasm"
911 nexusoperationpb "go.temporal.io/server/chasm/lib/nexusoperation/gen/nexusoperationpb/v1"
12+ "go.temporal.io/server/common/contextutil"
1013 "go.temporal.io/server/common/log"
1114)
1215
@@ -59,7 +62,14 @@ func (h *handler) StartNexusOperation(
5962 }, nil
6063}
6164
62- // TODO: Add long-poll support.
65+ // DescribeNexusOperation queries current operation state, optionally as a long-poll that waits
66+ // for any state change.
67+ //
68+ // When used to long-poll, it returns an empty non-error response on context
69+ // deadline expiry, to indicate that the state being waited for was not reached. Callers should
70+ // interpret this as an invitation to resubmit their long-poll request. This response is sent before
71+ // the caller's deadline (see nexusoperation.longPollBuffer) so that it is likely that the caller
72+ // does indeed receive the non-error response.
6373func (h * handler ) DescribeNexusOperation (
6474 ctx context.Context ,
6575 req * nexusoperationpb.DescribeNexusOperationRequest ,
@@ -72,7 +82,102 @@ func (h *handler) DescribeNexusOperation(
7282 RunID : req .GetFrontendRequest ().GetRunId (),
7383 })
7484
75- return chasm .ReadComponent (ctx , ref , (* Operation ).buildDescribeResponse , req , nil )
85+ token := req .GetFrontendRequest ().GetLongPollToken ()
86+ if len (token ) == 0 {
87+ // No long poll.
88+ return chasm .ReadComponent (ctx , ref , (* Operation ).buildDescribeResponse , req )
89+ }
90+
91+ // Determine the long poll timeout and buffer.
92+ ns := req .GetFrontendRequest ().GetNamespace ()
93+ ctx , cancel := contextutil .WithDeadlineBuffer (
94+ ctx ,
95+ h .config .LongPollTimeout (ns ),
96+ h .config .LongPollBuffer (ns ),
97+ )
98+ defer cancel ()
99+
100+ // Poll for the operation state to change.
101+ response , _ , err = chasm .PollComponent (ctx , ref , func (
102+ o * Operation ,
103+ ctx chasm.Context ,
104+ req * nexusoperationpb.DescribeNexusOperationRequest ,
105+ ) (* nexusoperationpb.DescribeNexusOperationResponse , bool , error ) {
106+ changed , err := chasm .ExecutionStateChanged (o , ctx , token )
107+ if err != nil {
108+ if errors .Is (err , chasm .ErrMalformedComponentRef ) {
109+ return nil , false , serviceerror .NewInvalidArgument ("invalid long poll token" )
110+ }
111+ if errors .Is (err , chasm .ErrInvalidComponentRef ) {
112+ return nil , false , serviceerror .NewInvalidArgument ("long poll token does not match execution" )
113+ }
114+ return nil , false , err
115+ }
116+ if changed {
117+ response , err := o .buildDescribeResponse (ctx , req )
118+ return response , true , err
119+ }
120+ return nil , false , nil
121+ }, req )
122+
123+ if err != nil && ctx .Err () != nil {
124+ // Send empty non-error response on deadline expiry: caller should continue long-polling.
125+ return & nexusoperationpb.DescribeNexusOperationResponse {
126+ FrontendResponse : & workflowservice.DescribeNexusOperationExecutionResponse {},
127+ }, nil
128+ }
129+ return response , err
130+ }
131+
132+ // PollNexusOperation long-polls for a Nexus operation to reach a specific stage.
133+ //
134+ // It returns an empty non-error response on context deadline expiry, to indicate that the state
135+ // being waited for was not reached. Callers should interpret this as an invitation to resubmit
136+ // their long-poll request. This response is sent before the caller's
137+ // deadline (see nexusoperation.longPollBuffer) so that it is likely that the caller
138+ // does indeed receive the non-error response.
139+ func (h * handler ) PollNexusOperation (
140+ ctx context.Context ,
141+ req * nexusoperationpb.PollNexusOperationRequest ,
142+ ) (response * nexusoperationpb.PollNexusOperationResponse , err error ) {
143+ defer log .CapturePanic (h .logger , & err )
144+
145+ ref := chasm.NewComponentRef [* Operation ](chasm.ExecutionKey {
146+ NamespaceID : req .GetNamespaceId (),
147+ BusinessID : req .GetFrontendRequest ().GetOperationId (),
148+ RunID : req .GetFrontendRequest ().GetRunId (),
149+ })
150+
151+ // Determine the long poll timeout and buffer.
152+ ns := req .GetFrontendRequest ().GetNamespace ()
153+ ctx , cancel := contextutil .WithDeadlineBuffer (
154+ ctx ,
155+ h .config .LongPollTimeout (ns ),
156+ h .config .LongPollBuffer (ns ),
157+ )
158+ defer cancel ()
159+
160+ // Poll for the wait stage to be reached.
161+ waitStage := req .GetFrontendRequest ().GetWaitStage ()
162+ response , _ , err = chasm .PollComponent (ctx , ref , func (
163+ o * Operation ,
164+ ctx chasm.Context ,
165+ req * nexusoperationpb.PollNexusOperationRequest ,
166+ ) (* nexusoperationpb.PollNexusOperationResponse , bool , error ) {
167+ if o .isWaitStageReached (ctx , waitStage ) {
168+ response := o .buildPollResponse (ctx )
169+ return response , true , nil
170+ }
171+ return nil , false , nil
172+ }, req )
173+
174+ if err != nil && ctx .Err () != nil {
175+ // Send an empty non-error response as an invitation to resubmit the long-poll.
176+ return & nexusoperationpb.PollNexusOperationResponse {
177+ FrontendResponse : & workflowservice.PollNexusOperationExecutionResponse {},
178+ }, nil
179+ }
180+ return response , err
76181}
77182
78183// RequestCancelNexusOperation requests cancellation of a standalone Nexus operation via CHASM.
0 commit comments