Skip to content

Commit f0e724a

Browse files
committed
fix time skipping bugs in updateWfOptions
1 parent 697eb2a commit f0e724a

4 files changed

Lines changed: 214 additions & 78 deletions

File tree

service/history/api/updateworkflowoptions/api.go

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ func Invoke(
123123
return ret, nil
124124
}
125125

126+
// UpdateSideEffectOptions is used to track which WorkflowOptions have side effects,
127+
// and cannot be detected simply by proto.Equal.
128+
type UpdateSideEffectOptions struct {
129+
timeSkippingConfig bool
130+
}
131+
132+
func (u UpdateSideEffectOptions) hasChanges() bool {
133+
return u.timeSkippingConfig
134+
}
135+
126136
// MergeAndApply merges the requested options mentioned in the field mask with the current options in the mutable state
127137
// and applies the changes to the mutable state. Returns the merged options and a boolean indicating if there were any changes.
128138
func MergeAndApply(
@@ -132,7 +142,7 @@ func MergeAndApply(
132142
identity string,
133143
) (*workflowpb.WorkflowExecutionOptions, bool, error) {
134144
// Merge the requested options mentioned in the field mask with the current options in the mutable state
135-
mergedOpts, err := mergeWorkflowExecutionOptions(
145+
mergedOpts, sideEffects, err := mergeWorkflowExecutionOptions(
136146
getOptionsFromMutableState(ms),
137147
opts,
138148
updateMask,
@@ -141,17 +151,15 @@ func MergeAndApply(
141151
return nil, false, serviceerror.NewInvalidArgumentf("error applying update_options: %v", err)
142152
}
143153

144-
// If there is no mutable state change at all, return with no new history event and Noop=true
145-
hasChanges := !proto.Equal(mergedOpts, getOptionsFromMutableState(ms))
154+
// sideEffects.hasChanges() forces hasChanges=true for bound renewals where proto.Equal sees no difference.
155+
hasChanges := !proto.Equal(mergedOpts, getOptionsFromMutableState(ms)) || sideEffects.hasChanges()
146156
if !hasChanges {
147157
return mergedOpts, false, nil
148158
}
149159

150-
unsetOverride := false
151-
if mergedOpts.GetVersioningOverride() == nil {
152-
unsetOverride = true
153-
}
154-
_, err = ms.AddWorkflowExecutionOptionsUpdatedEvent(mergedOpts.GetVersioningOverride(), unsetOverride, "", nil, nil, identity, mergedOpts.GetPriority(), mergedOpts.GetTimeSkippingConfig(), nil)
160+
unsetOverride := mergedOpts.GetVersioningOverride() == nil
161+
_, err = ms.AddWorkflowExecutionOptionsUpdatedEvent(
162+
mergedOpts.GetVersioningOverride(), unsetOverride, "", nil, nil, identity, mergedOpts.GetPriority(), mergedOpts.GetTimeSkippingConfig(), nil)
155163
if err != nil {
156164
return nil, hasChanges, err
157165
}
@@ -180,30 +188,32 @@ func getOptionsFromMutableState(ms historyi.MutableState) *workflowpb.WorkflowEx
180188
return opts
181189
}
182190

183-
// mergeWorkflowExecutionOptions copies the given paths in `src` struct to `dst` struct
191+
// mergeWorkflowExecutionOptions copies the given paths in `src` struct to `dst` struct and returns
192+
// the side-effect options for fields that produce side effects when applied (see UpdateSideEffectOptions).
184193
func mergeWorkflowExecutionOptions(
185194
mergeInto, mergeFrom *workflowpb.WorkflowExecutionOptions,
186195
updateMask *fieldmaskpb.FieldMask,
187-
) (*workflowpb.WorkflowExecutionOptions, error) {
196+
) (*workflowpb.WorkflowExecutionOptions, UpdateSideEffectOptions, error) {
188197
_, err := fieldmaskpb.New(mergeInto, updateMask.GetPaths()...)
189198
if err != nil { // errors if any paths are not valid for the struct we are merging into
190-
return nil, err
199+
return nil, UpdateSideEffectOptions{}, err
191200
}
192201
updateFields := util.ParseFieldMask(updateMask)
202+
193203
if _, ok := updateFields["versioningOverride"]; ok {
194204
mergeInto.VersioningOverride = mergeFrom.GetVersioningOverride()
195205
}
196206

197207
if _, ok := updateFields["versioningOverride.deployment"]; ok {
198208
if _, ok := updateFields["versioningOverride.behavior"]; !ok {
199-
return nil, serviceerror.NewInvalidArgument("versioning_override fields must be updated together")
209+
return nil, UpdateSideEffectOptions{}, serviceerror.NewInvalidArgument("versioning_override fields must be updated together")
200210
}
201211
mergeInto.VersioningOverride = mergeFrom.GetVersioningOverride()
202212
}
203213

204214
if _, ok := updateFields["versioningOverride.behavior"]; ok {
205215
if _, ok := updateFields["versioningOverride.deployment"]; !ok {
206-
return nil, serviceerror.NewInvalidArgument("versioning_override fields must be updated together")
216+
return nil, UpdateSideEffectOptions{}, serviceerror.NewInvalidArgument("versioning_override fields must be updated together")
207217
}
208218
mergeInto.VersioningOverride = mergeFrom.GetVersioningOverride()
209219
}
@@ -235,12 +245,23 @@ func mergeWorkflowExecutionOptions(
235245
mergeInto.Priority.FairnessWeight = mergeFrom.Priority.GetFairnessWeight()
236246
}
237247

238-
// ==== Time Skipping Config
239-
// nil means "no change" — only update if the caller provided an explicit value.
248+
// ==== Time Skipping Config (has side effects when applied)
249+
var originalTsc *workflowpb.TimeSkippingConfig
250+
if tsc := mergeInto.GetTimeSkippingConfig(); tsc != nil {
251+
if cloned, ok := proto.Clone(tsc).(*workflowpb.TimeSkippingConfig); ok {
252+
originalTsc = cloned
253+
}
254+
}
255+
256+
// getting the new TSC config
257+
var boundInMask bool
240258
if _, ok := updateFields["timeSkippingConfig"]; ok {
241259
if mergeFrom.GetTimeSkippingConfig() != nil {
242260
mergeInto.TimeSkippingConfig = mergeFrom.GetTimeSkippingConfig()
243261
}
262+
if mergeFrom.GetTimeSkippingConfig().GetBound() != nil {
263+
boundInMask = true
264+
}
244265
}
245266

246267
if _, ok := updateFields["timeSkippingConfig.enabled"]; ok {
@@ -257,7 +278,16 @@ func mergeWorkflowExecutionOptions(
257278
mergeInto.TimeSkippingConfig.Bound = &workflowpb.TimeSkippingConfig_MaxElapsedDuration{
258279
MaxElapsedDuration: mergeFrom.GetTimeSkippingConfig().GetMaxElapsedDuration(),
259280
}
281+
boundInMask = true
260282
}
261283

262-
return mergeInto, nil
284+
var sideEffects UpdateSideEffectOptions
285+
// either bound is used or the config contents are changed
286+
if boundInMask || (!proto.Equal(mergeInto.GetTimeSkippingConfig(), originalTsc)) {
287+
sideEffects.timeSkippingConfig = true
288+
} else {
289+
sideEffects.timeSkippingConfig = false
290+
mergeInto.TimeSkippingConfig = nil
291+
}
292+
return mergeInto, sideEffects, nil
263293
}

0 commit comments

Comments
 (0)