go.uber.org/cadence@v1.2.9/internal/workflow_replayer_utils.go (about) 1 // Copyright (c) 2017-2021 Uber Technologies Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package internal 22 23 import ( 24 "bytes" 25 "reflect" 26 "strings" 27 28 s "go.uber.org/cadence/.gen/go/shared" 29 ) 30 31 func matchReplayWithHistory(info *WorkflowInfo, replayDecisions []*s.Decision, historyEvents []*s.HistoryEvent) error { 32 di := 0 33 hi := 0 34 hSize := len(historyEvents) 35 dSize := len(replayDecisions) 36 matchLoop: 37 for hi < hSize || di < dSize { 38 var e *s.HistoryEvent 39 if hi < hSize { 40 e = historyEvents[hi] 41 if skipDeterministicCheckForUpsertChangeVersion(historyEvents, hi) { 42 hi += 2 43 continue matchLoop 44 } 45 if skipDeterministicCheckForEvent(e) { 46 hi++ 47 continue matchLoop 48 } 49 } 50 51 var d *s.Decision 52 if di < dSize { 53 d = replayDecisions[di] 54 if skipDeterministicCheckForDecision(d) { 55 di++ 56 continue matchLoop 57 } 58 } 59 60 if d == nil { 61 return NewNonDeterminsticError("missing replay decision", info, e, nil) 62 } 63 64 if e == nil { 65 return NewNonDeterminsticError("extra replay decision", info, nil, d) 66 } 67 68 if !isDecisionMatchEvent(d, e, false) { 69 return NewNonDeterminsticError("mismatch", info, e, d) 70 } 71 72 di++ 73 hi++ 74 } 75 return nil 76 } 77 78 func lastPartOfName(name string) string { 79 name = strings.TrimSuffix(name, "-fm") 80 lastDotIdx := strings.LastIndex(name, ".") 81 if lastDotIdx < 0 || lastDotIdx == len(name)-1 { 82 return name 83 } 84 return name[lastDotIdx+1:] 85 } 86 87 func skipDeterministicCheckForEvent(e *s.HistoryEvent) bool { 88 if e.GetEventType() == s.EventTypeMarkerRecorded { 89 markerName := e.MarkerRecordedEventAttributes.GetMarkerName() 90 if markerName == versionMarkerName || markerName == mutableSideEffectMarkerName { 91 return true 92 } 93 } 94 return false 95 } 96 97 // special check for upsert change version event 98 func skipDeterministicCheckForUpsertChangeVersion(events []*s.HistoryEvent, idx int) bool { 99 e := events[idx] 100 if e.GetEventType() == s.EventTypeMarkerRecorded && 101 e.MarkerRecordedEventAttributes.GetMarkerName() == versionMarkerName && 102 idx < len(events)-1 && 103 events[idx+1].GetEventType() == s.EventTypeUpsertWorkflowSearchAttributes { 104 if _, ok := events[idx+1].UpsertWorkflowSearchAttributesEventAttributes.SearchAttributes.IndexedFields[CadenceChangeVersion]; ok { 105 return true 106 } 107 } 108 return false 109 } 110 111 func skipDeterministicCheckForDecision(d *s.Decision) bool { 112 if d.GetDecisionType() == s.DecisionTypeRecordMarker { 113 markerName := d.RecordMarkerDecisionAttributes.GetMarkerName() 114 if markerName == versionMarkerName || markerName == mutableSideEffectMarkerName { 115 return true 116 } 117 } 118 return false 119 } 120 121 func isDecisionMatchEvent(d *s.Decision, e *s.HistoryEvent, strictMode bool) bool { 122 switch d.GetDecisionType() { 123 case s.DecisionTypeScheduleActivityTask: 124 if e.GetEventType() != s.EventTypeActivityTaskScheduled { 125 return false 126 } 127 eventAttributes := e.ActivityTaskScheduledEventAttributes 128 decisionAttributes := d.ScheduleActivityTaskDecisionAttributes 129 130 if eventAttributes.GetActivityId() != decisionAttributes.GetActivityId() || 131 lastPartOfName(eventAttributes.ActivityType.GetName()) != lastPartOfName(decisionAttributes.ActivityType.GetName()) || 132 (strictMode && eventAttributes.TaskList.GetName() != decisionAttributes.TaskList.GetName()) || 133 (strictMode && bytes.Compare(eventAttributes.Input, decisionAttributes.Input) != 0) { 134 return false 135 } 136 137 return true 138 139 case s.DecisionTypeRequestCancelActivityTask: 140 if e.GetEventType() != s.EventTypeActivityTaskCancelRequested { 141 return false 142 } 143 decisionAttributes := d.RequestCancelActivityTaskDecisionAttributes 144 eventAttributes := e.ActivityTaskCancelRequestedEventAttributes 145 if eventAttributes.GetActivityId() != decisionAttributes.GetActivityId() { 146 return false 147 } 148 149 return true 150 151 case s.DecisionTypeStartTimer: 152 if e.GetEventType() != s.EventTypeTimerStarted { 153 return false 154 } 155 eventAttributes := e.TimerStartedEventAttributes 156 decisionAttributes := d.StartTimerDecisionAttributes 157 158 if eventAttributes.GetTimerId() != decisionAttributes.GetTimerId() || 159 (strictMode && eventAttributes.GetStartToFireTimeoutSeconds() != decisionAttributes.GetStartToFireTimeoutSeconds()) { 160 return false 161 } 162 163 return true 164 165 case s.DecisionTypeCancelTimer: 166 if e.GetEventType() != s.EventTypeTimerCanceled && e.GetEventType() != s.EventTypeCancelTimerFailed { 167 return false 168 } 169 decisionAttributes := d.CancelTimerDecisionAttributes 170 if e.GetEventType() == s.EventTypeTimerCanceled { 171 eventAttributes := e.TimerCanceledEventAttributes 172 if eventAttributes.GetTimerId() != decisionAttributes.GetTimerId() { 173 return false 174 } 175 } else if e.GetEventType() == s.EventTypeCancelTimerFailed { 176 eventAttributes := e.CancelTimerFailedEventAttributes 177 if eventAttributes.GetTimerId() != decisionAttributes.GetTimerId() { 178 return false 179 } 180 } 181 182 return true 183 184 case s.DecisionTypeCompleteWorkflowExecution: 185 if e.GetEventType() != s.EventTypeWorkflowExecutionCompleted { 186 return false 187 } 188 if strictMode { 189 eventAttributes := e.WorkflowExecutionCompletedEventAttributes 190 decisionAttributes := d.CompleteWorkflowExecutionDecisionAttributes 191 192 if bytes.Compare(eventAttributes.Result, decisionAttributes.Result) != 0 { 193 return false 194 } 195 } 196 197 return true 198 199 case s.DecisionTypeFailWorkflowExecution: 200 if e.GetEventType() != s.EventTypeWorkflowExecutionFailed { 201 return false 202 } 203 if strictMode { 204 eventAttributes := e.WorkflowExecutionFailedEventAttributes 205 decisionAttributes := d.FailWorkflowExecutionDecisionAttributes 206 207 if eventAttributes.GetReason() != decisionAttributes.GetReason() || 208 bytes.Compare(eventAttributes.Details, decisionAttributes.Details) != 0 { 209 return false 210 } 211 } 212 213 return true 214 215 case s.DecisionTypeRecordMarker: 216 if e.GetEventType() != s.EventTypeMarkerRecorded { 217 return false 218 } 219 eventAttributes := e.MarkerRecordedEventAttributes 220 decisionAttributes := d.RecordMarkerDecisionAttributes 221 if eventAttributes.GetMarkerName() != decisionAttributes.GetMarkerName() { 222 return false 223 } 224 225 return true 226 227 case s.DecisionTypeRequestCancelExternalWorkflowExecution: 228 if e.GetEventType() != s.EventTypeRequestCancelExternalWorkflowExecutionInitiated { 229 return false 230 } 231 eventAttributes := e.RequestCancelExternalWorkflowExecutionInitiatedEventAttributes 232 decisionAttributes := d.RequestCancelExternalWorkflowExecutionDecisionAttributes 233 if checkDomainsInDecisionAndEvent(eventAttributes.GetDomain(), decisionAttributes.GetDomain()) || 234 eventAttributes.WorkflowExecution.GetWorkflowId() != decisionAttributes.GetWorkflowId() { 235 return false 236 } 237 238 return true 239 240 case s.DecisionTypeSignalExternalWorkflowExecution: 241 if e.GetEventType() != s.EventTypeSignalExternalWorkflowExecutionInitiated { 242 return false 243 } 244 eventAttributes := e.SignalExternalWorkflowExecutionInitiatedEventAttributes 245 decisionAttributes := d.SignalExternalWorkflowExecutionDecisionAttributes 246 if checkDomainsInDecisionAndEvent(eventAttributes.GetDomain(), decisionAttributes.GetDomain()) || 247 eventAttributes.GetSignalName() != decisionAttributes.GetSignalName() || 248 eventAttributes.WorkflowExecution.GetWorkflowId() != decisionAttributes.Execution.GetWorkflowId() { 249 return false 250 } 251 252 return true 253 254 case s.DecisionTypeCancelWorkflowExecution: 255 if e.GetEventType() != s.EventTypeWorkflowExecutionCanceled { 256 return false 257 } 258 if strictMode { 259 eventAttributes := e.WorkflowExecutionCanceledEventAttributes 260 decisionAttributes := d.CancelWorkflowExecutionDecisionAttributes 261 if bytes.Compare(eventAttributes.Details, decisionAttributes.Details) != 0 { 262 return false 263 } 264 } 265 return true 266 267 case s.DecisionTypeContinueAsNewWorkflowExecution: 268 if e.GetEventType() != s.EventTypeWorkflowExecutionContinuedAsNew { 269 return false 270 } 271 272 return true 273 274 case s.DecisionTypeStartChildWorkflowExecution: 275 if e.GetEventType() != s.EventTypeStartChildWorkflowExecutionInitiated { 276 return false 277 } 278 eventAttributes := e.StartChildWorkflowExecutionInitiatedEventAttributes 279 decisionAttributes := d.StartChildWorkflowExecutionDecisionAttributes 280 if lastPartOfName(eventAttributes.WorkflowType.GetName()) != lastPartOfName(decisionAttributes.WorkflowType.GetName()) || 281 (strictMode && checkDomainsInDecisionAndEvent(eventAttributes.GetDomain(), decisionAttributes.GetDomain())) || 282 (strictMode && eventAttributes.TaskList.GetName() != decisionAttributes.TaskList.GetName()) { 283 return false 284 } 285 286 return true 287 288 case s.DecisionTypeUpsertWorkflowSearchAttributes: 289 if e.GetEventType() != s.EventTypeUpsertWorkflowSearchAttributes { 290 return false 291 } 292 eventAttributes := e.UpsertWorkflowSearchAttributesEventAttributes 293 decisionAttributes := d.UpsertWorkflowSearchAttributesDecisionAttributes 294 if strictMode && !isSearchAttributesMatched(eventAttributes.SearchAttributes, decisionAttributes.SearchAttributes) { 295 return false 296 } 297 return true 298 } 299 300 return false 301 } 302 303 func isSearchAttributesMatched(attrFromEvent, attrFromDecision *s.SearchAttributes) bool { 304 if attrFromEvent != nil && attrFromDecision != nil { 305 return reflect.DeepEqual(attrFromEvent.IndexedFields, attrFromDecision.IndexedFields) 306 } 307 return attrFromEvent == nil && attrFromDecision == nil 308 } 309 310 // return true if the check fails: 311 // 312 // domain is not empty in decision 313 // and domain is not replayDomain 314 // and domains unmatch in decision and events 315 func checkDomainsInDecisionAndEvent(eventDomainName, decisionDomainName string) bool { 316 if decisionDomainName == "" || IsReplayDomain(decisionDomainName) { 317 return false 318 } 319 return eventDomainName != decisionDomainName 320 }