Files
Dan DavisonandGitHub c1355e9165 Update APIs: server-imposed deadline expiry and non-blocking Update Poll (#5114)
## What changed?

Makes changes summarized in
https://github.com/temporalio/temporal/assets/52205/0660a021-588a-4573-94d3-301cae0c0040

### Empty responses on server-timeout
The behavior of `PollWorkflowExecutionUpdate` and
`UpdateWorkflowExecution` was changed as follows:
- They introduce a "server-imposed deadline" equal to current time plus
`LongPollExpirationInterval`
- If a timeout occurs due to the server-imposed deadline, then they do
not return an error, but return a response with an empty outcome.
- Otherwise, their behavior is unchanged (in particular, if a timeout
occurs due context deadline expiry, they return the error as before)

### Non-blocking poll
- Change the Update Poll endpoint so that omitting the `WaitPolicy` now
means "do not wait; respond with current Update status without
blocking". Previously omitting the `WaitPolicy` was interpreted to mean
"attempt to wait until Completed"
- Also return the `UpdateRef`

## Why?

### Empty responses on server-timeout
- This makes the behavior of the endoint consistent with
`GetWorkflowExecutionHistory`.
- This makes sense, because that endpoint is used in an analogous way
(e.g. `GetWorkflowExecutionHistory` is used for `wfHandle.result()` and
`PollWorkflowExecutionUpdateRequest` is used for
`updateHandle.result()`, and both involve polling for the result).
- In particular, a polling client interprets
timeout-on-server-imposed-deadline to mean that it should continue
polling, and we would prefer not to use an error to communicate this
non-exceptional situation.

### Non-blocking poll
SDK clients may use non-blocking poll to, for example
- Obtain current status of an Update (perhaps `updateHandle.describe()`
- Determine whether an Update with a given ID already exists / is
running

### How did you test it?
- Unit tests
- Modified Typescript SDK to not send `WaitPolicy`, ran a feature test
against the modified server and modified SDK, and confirmed API
communication as expected (this revealed that I needed to stop Frontend
mutating the request to set `WaitPolicy` to `Completed`).
- I changed the poll loop in sdk-typescript to no longer catch
`DeadlineExceeded`, ran the `update/basic_async` feature test against
the local SDK and against server binaries built prior to and with this
commit, and confirmed that the change in this PR fixed the poll loop.

### Potential risks
- Could break Update operations in SDKs

### Is hotfix candidate?
- No
2023-11-16 17:48:49 -05:00

96 lines
3.2 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package update
import (
"fmt"
enumspb "go.temporal.io/api/enums/v1"
)
type (
state uint32
stateSet uint32
)
const (
stateAdmitted state = 1 << iota
stateProvisionallyRequested
stateRequested
stateProvisionallyAccepted
stateAccepted
stateProvisionallyCompleted
stateCompleted
)
func (s state) String() string {
switch s {
case stateAdmitted:
return "Admitted"
case stateProvisionallyRequested:
return "ProvisionallyRequested"
case stateRequested:
return "Requested"
case stateProvisionallyAccepted:
return "ProvisionallyAccepted"
case stateAccepted:
return "Accepted"
case stateProvisionallyCompleted:
return "ProvisionallyCompleted"
case stateCompleted:
return "Completed"
}
return "unrecognized state"
}
// LifecycleStage maps the states of the Update state machine to the values of
// enumspb.UpdateWorkflowExecutionLifecycleStage.
func (s state) LifecycleStage() (enumspb.UpdateWorkflowExecutionLifecycleStage, error) {
switch s {
case stateAdmitted:
return enumspb.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ADMITTED, nil
case stateProvisionallyRequested:
return enumspb.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ADMITTED, nil
case stateRequested:
return enumspb.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ADMITTED, nil
case stateProvisionallyAccepted:
return enumspb.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ADMITTED, nil
case stateAccepted:
return enumspb.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED, nil
case stateProvisionallyCompleted:
// Transition could be due to either validation rejection or completion
// of Update, so Admitted is the most advanced stage known to have been
// reached.
return enumspb.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ADMITTED, nil
case stateCompleted:
return enumspb.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_COMPLETED, nil
}
return enumspb.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_UNSPECIFIED, fmt.Errorf("unrecognized state: %v", s) // nolint:goerr113
}
func (s state) Matches(mask stateSet) bool {
return uint32(s)&uint32(mask) == uint32(s)
}