@@ -11,7 +11,8 @@ import (
1111
1212// DispatchOutcome names the arm of matching's DispatchNexusTaskResponse that came back from a
1313// DispatchNexusTask call. The nested oneofs and the deprecated variants collapse into one flat set of
14- // cases.
14+ // cases. (Including those from a worker sending the deprecated failure responses, those will get
15+ // silently converted into the newer forms.)
1516//
1617// The zero value is the empty string and is not a valid outcome, so a switch over a DispatchOutcome
1718// needs a default clause. For metric tags use DispatchResult.OutcomeTag, not the string value.
@@ -32,26 +33,30 @@ const (
3233 // DispatchOutcomeCancelAccepted means the worker accepted the cancellation request.
3334 DispatchOutcomeCancelAccepted DispatchOutcome = "cancel-accepted"
3435
35- // DispatchOutcomeOperationFailure means the worker ran the operation and it failed or was
36- // canceled. The task was handled; this is the handler's answer, not a delivery problem.
36+ // DispatchOutcomeOperationFailure means the worker successfully ran the operation to completion. But
37+ // operation resulted in a failure or was canceled. This is not a delivery problem.
38+ //
39+ // e.g. the Nexus handler fails with:
40+ // nexus.NewOperationFailedError("insufficient funds")
41+ // nexus.NewOperationCanceledError("already canceled upstream")
3742 DispatchOutcomeOperationFailure DispatchOutcome = "operation-failure"
3843
39- // DispatchOutcomeOperationFailureDeprecated is DispatchOutcomeOperationFailure as reported by a
40- // worker predating Temporal failure responses.
41- DispatchOutcomeOperationFailureDeprecated DispatchOutcome = "operation-failure-deprecated"
42-
4344 // DispatchOutcomeHandlerFailure means the worker refused the task with a Nexus handler error,
4445 // whose retry behavior says whether another attempt is worthwhile.
46+ //
47+ // e.g. the Temporal worker fails before the handler executes, with:
48+ // nexus.NewHandlerErrorf(nexus.HandlerErrorTypeBadRequest, "cannot deserialize input")
49+ // nexus.NewHandlerErrorf(nexus.HandlerErrorTypeBadRequest, "callback URL required for async UpdateWorkflow operation invocations")
4550 DispatchOutcomeHandlerFailure DispatchOutcome = "nexus-handler-failure"
4651
4752 // DispatchOutcomeWorkerFailure means the worker failed the task with a failure that is not a
4853 // Nexus handler error, e.g. an application error sent via RespondNexusTaskFailed.
54+ //
55+ // e.g. the Temporal worker fails before the handler executes, but with a non-HandlerError like:
56+ // TimeoutFailureInfo
57+ // CanceledFailureInfo
4958 DispatchOutcomeWorkerFailure DispatchOutcome = "worker-failure"
5059
51- // DispatchOutcomeHandlerFailureDeprecated is DispatchOutcomeHandlerFailure as reported by a worker
52- // predating Temporal failure responses.
53- DispatchOutcomeHandlerFailureDeprecated DispatchOutcome = "nexus-handler-failure-deprecated"
54-
5560 // DispatchOutcomeRequestTimeout means matching gave up before the task was answered: no worker
5661 // was polling the task queue, or a worker took the task and never responded.
5762 DispatchOutcomeRequestTimeout DispatchOutcome = "request-timeout"
@@ -94,44 +99,17 @@ type DispatchResult struct {
9499 // in place mutate the response too.
95100 Failure * failurepb.Failure
96101
97- // HandlerError is set only for DispatchOutcomeHandlerFailureDeprecated.
98- HandlerError * nexuspb.HandlerError
99-
100- // OperationError is set only for DispatchOutcomeOperationFailureDeprecated.
101- OperationError * nexuspb.UnsuccessfulOperationError
102- }
103-
104- // handlerErrorType returns the Nexus handler error type the worker reported, or "" when the outcome is
105- // not a handler error.
106- func (r DispatchResult ) handlerErrorType () string {
107- switch r .Outcome {
108- case DispatchOutcomeHandlerFailure :
109- return r .Failure .GetNexusHandlerFailureInfo ().GetType ()
110- case DispatchOutcomeHandlerFailureDeprecated :
111- //nolint:staticcheck // Deprecated field on a deprecated variant.
112- return r .HandlerError .GetErrorType ()
113- default :
114- return ""
115- }
102+ // usedDeprecatedFormat records that the worker answered in a format predating Temporal failure
103+ // responses. Outcome and Failure are normalized to failurepb.Failure. This is only needed
104+ // for reporting metrics. (Which are different based on the kind of error format received.)
105+ usedDeprecatedFormat bool
116106}
117107
118- // ClassifyStartOperationDispatch classifies matching's response to a dispatched StartOperation task.
119- func ClassifyStartOperationDispatch (resp * matchingservice.DispatchNexusTaskResponse ) DispatchResult {
120- return classifyDispatchNexusTaskResponse (resp , classifyStartOperationResponse )
121- }
122-
123- // ClassifyCancelOperationDispatch classifies matching's response to a dispatched CancelOperation task.
124- func ClassifyCancelOperationDispatch (resp * matchingservice.DispatchNexusTaskResponse ) DispatchResult {
125- return classifyDispatchNexusTaskResponse (
126- resp ,
127- func (* nexuspb.StartOperationResponse ) DispatchResult {
128- // A cancel response carries no fields, so any response means the worker accepted.
129- return DispatchResult {Outcome : DispatchOutcomeCancelAccepted }
130- })
131- }
132-
133- // classifyDispatchNexusTaskResponse converts a DispatchNexusTaskResponse into a DispatchResult object.
134- func classifyDispatchNexusTaskResponse (
108+ // baseClassifyDispatchNexusTaskResponse converts a DispatchNexusTaskResponse into a DispatchResult object.
109+ //
110+ // The supplied lambda is invoked to classify a successful Response outcome. (Since the DispatchNexusTaskResponse
111+ // proto is used for different kinds of request types, only the caller will know what success means.)
112+ func baseClassifyDispatchNexusTaskResponse (
135113 resp * matchingservice.DispatchNexusTaskResponse ,
136114 onResponseFn func (* nexuspb.StartOperationResponse ) DispatchResult ,
137115) DispatchResult {
@@ -147,9 +125,10 @@ func classifyDispatchNexusTaskResponse(
147125
148126 case * matchingservice.DispatchNexusTaskResponse_HandlerError : //nolint:staticcheck // Deprecated, still sent by older workers.
149127 return DispatchResult {
150- Outcome : DispatchOutcomeHandlerFailureDeprecated ,
128+ Outcome : DispatchOutcomeHandlerFailure ,
151129 //nolint:staticcheck // Deprecated field on a deprecated variant.
152- HandlerError : t .HandlerError ,
130+ Failure : deprecatedHandlerErrorToFailure (t .HandlerError ),
131+ usedDeprecatedFormat : true ,
153132 }
154133
155134 case * matchingservice.DispatchNexusTaskResponse_RequestTimeout :
@@ -165,7 +144,7 @@ func classifyDispatchNexusTaskResponse(
165144 }
166145}
167146
168- // classifyStartOperationResponse classifies the answer a worker gave to a StartOperation request.
147+ // classifyStartOperationResponse classifies the response a worker gave to a StartOperation request.
169148func classifyStartOperationResponse (resp * nexuspb.StartOperationResponse ) DispatchResult {
170149 switch t := resp .GetVariant ().(type ) {
171150 case * nexuspb.StartOperationResponse_SyncSuccess :
@@ -196,16 +175,91 @@ func classifyStartOperationResponse(resp *nexuspb.StartOperationResponse) Dispat
196175
197176 case * nexuspb.StartOperationResponse_OperationError : //nolint:staticcheck // Deprecated, still sent by older workers.
198177 return DispatchResult {
199- Outcome : DispatchOutcomeOperationFailureDeprecated ,
178+ Outcome : DispatchOutcomeOperationFailure ,
200179 //nolint:staticcheck // Deprecated field on a deprecated variant.
201- OperationError : t .OperationError ,
180+ Failure : deprecatedOperationErrorToFailure (t .OperationError ),
181+ usedDeprecatedFormat : true ,
202182 }
203183
204184 default :
205185 return DispatchResult {Outcome : DispatchOutcomeUnrecognized }
206186 }
207187}
208188
189+ // ClassifyStartOperationDispatch classifies matching's response to a dispatched StartOperation task.
190+ func ClassifyStartOperationDispatch (resp * matchingservice.DispatchNexusTaskResponse ) DispatchResult {
191+ return baseClassifyDispatchNexusTaskResponse (resp , classifyStartOperationResponse )
192+ }
193+
194+ // ClassifyCancelOperationDispatch classifies matching's response to a dispatched CancelOperation task.
195+ func ClassifyCancelOperationDispatch (resp * matchingservice.DispatchNexusTaskResponse ) DispatchResult {
196+ return baseClassifyDispatchNexusTaskResponse (
197+ resp ,
198+ func (* nexuspb.StartOperationResponse ) DispatchResult {
199+ // A cancel response carries no fields, so any response means the worker accepted.
200+ return DispatchResult {Outcome : DispatchOutcomeCancelAccepted }
201+ })
202+ }
203+
204+ // deprecatedHandlerErrorToFailure converts the deprecated nexuspb.HandlerError into the new format,
205+ // a Temporal failurepb.Failure.
206+ func deprecatedHandlerErrorToFailure (handlerErr * nexuspb.HandlerError ) * failurepb.Failure {
207+ failure := & failurepb.Failure {
208+ FailureInfo : & failurepb.Failure_NexusHandlerFailureInfo {
209+ NexusHandlerFailureInfo : & failurepb.NexusHandlerFailureInfo {
210+ Type : handlerErr .GetErrorType (),
211+ RetryBehavior : handlerErr .GetRetryBehavior (),
212+ },
213+ },
214+ }
215+ if handlerErr .GetFailure () != nil {
216+ failure .Cause = convertNexusFailureToTemporalFailure (handlerErr .GetFailure ())
217+ }
218+ return failure
219+ }
220+
221+ // deprecatedOperationErrorToFailure converts the deprecated nexuspb.UnsccessfulOperationError into the
222+ // new format, a Temporal failurepb.Failure proto.
223+ func deprecatedOperationErrorToFailure (opErr * nexuspb.UnsuccessfulOperationError ) * failurepb.Failure {
224+ failure := convertNexusFailureToTemporalFailure (opErr .GetFailure ())
225+
226+ // If the OperationError was because it was cancelled, encode that into the Failure proto.
227+ opState := nexus .OperationState (opErr .GetOperationState ())
228+ isCanceledErr := opState == nexus .OperationStateCanceled
229+ if ! isCanceledErr && failure .GetCanceledFailureInfo () == nil {
230+ return failure
231+ }
232+
233+ return & failurepb.Failure {
234+ Message : failure .GetMessage (),
235+ Cause : failure ,
236+ FailureInfo : & failurepb.Failure_CanceledFailureInfo {
237+ CanceledFailureInfo : & failurepb.CanceledFailureInfo {},
238+ },
239+ }
240+ }
241+
242+ // convertNexusFailureToTemporalFailure converts the deprecated nexuspb.Failure into the new format,
243+ // a Temporal failurepb.Failure.
244+ func convertNexusFailureToTemporalFailure (nexusFailure * nexuspb.Failure ) * failurepb.Failure {
245+ //nolint:staticcheck // Deprecated function still in use for backward compatibility.
246+ converted , err := NexusFailureToTemporalFailure (ProtoFailureToNexusFailure (nexusFailure ))
247+ // A failure that cannot be re-encoded falls back to its message. We know the operation failed,
248+ // we just don't recognize the format of the data in its cause/details.
249+ if err != nil {
250+ return & failurepb.Failure {
251+ Message : nexusFailure .GetMessage (),
252+ FailureInfo : & failurepb.Failure_ApplicationFailureInfo {
253+ ApplicationFailureInfo : & failurepb.ApplicationFailureInfo {
254+ Type : "NexusFailure" ,
255+ NonRetryable : false ,
256+ },
257+ },
258+ }
259+ }
260+ return converted
261+ }
262+
209263// OutcomeTag returns the metrics outcome tag for a dispatch.
210264//
211265// The handler-error suffix is bounded by boundHandlerErrorType(). A worker failure that is not a handler
@@ -225,14 +279,15 @@ func (r DispatchResult) metricOutcome() string {
225279 case DispatchOutcomeCancelAccepted :
226280 return "success"
227281 case DispatchOutcomeOperationFailure :
282+ if r .usedDeprecatedFormat {
283+ return "operation_error"
284+ }
228285 return "failure"
229- case DispatchOutcomeOperationFailureDeprecated :
230- return "operation_error"
231286 case DispatchOutcomeHandlerFailure ,
232- DispatchOutcomeWorkerFailure ,
233- DispatchOutcomeHandlerFailureDeprecated :
287+ DispatchOutcomeWorkerFailure :
234288 // A worker failure has no handler error type to report and will map to UNKNOWN.
235- return "handler_error:" + boundHandlerErrorType (r .handlerErrorType ())
289+ hErrType := r .Failure .GetNexusHandlerFailureInfo ().GetType ()
290+ return "handler_error:" + boundHandlerErrorType (hErrType )
236291 case DispatchOutcomeRequestTimeout :
237292 return "handler_timeout"
238293 default :
0 commit comments