github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/conn_fsm.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 // 11 // 12 // 13 // This file contains the definition of a connection's state machine, expressed 14 // through the util/fsm library. Also see txn_state.go, which contains the 15 // txnState structure used as the ExtendedState mutated by all the Actions. 16 17 package sql 18 19 import ( 20 "time" 21 22 "github.com/cockroachdb/cockroach/pkg/roachpb" 23 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 24 "github.com/cockroachdb/cockroach/pkg/util/fsm" 25 "github.com/cockroachdb/cockroach/pkg/util/hlc" 26 ) 27 28 // Constants for the String() representation of the session states. Shared with 29 // the CLI code which needs to recognize them. 30 const ( 31 NoTxnStateStr = "NoTxn" 32 OpenStateStr = "Open" 33 AbortedStateStr = "Aborted" 34 CommitWaitStateStr = "CommitWait" 35 InternalErrorStateStr = "InternalError" 36 ) 37 38 /// States. 39 40 type stateNoTxn struct{} 41 42 var _ fsm.State = &stateNoTxn{} 43 44 func (stateNoTxn) String() string { 45 return NoTxnStateStr 46 } 47 48 type stateOpen struct { 49 ImplicitTxn fsm.Bool 50 } 51 52 var _ fsm.State = &stateOpen{} 53 54 func (stateOpen) String() string { 55 return OpenStateStr 56 } 57 58 // stateAborted is entered on errors (retriable and non-retriable). A ROLLBACK 59 // TO SAVEPOINT can move the transaction back to stateOpen. 60 type stateAborted struct{} 61 62 var _ fsm.State = &stateAborted{} 63 64 func (stateAborted) String() string { 65 return AbortedStateStr 66 } 67 68 type stateCommitWait struct{} 69 70 var _ fsm.State = &stateCommitWait{} 71 72 func (stateCommitWait) String() string { 73 return CommitWaitStateStr 74 } 75 76 // stateInternalError is used by the InternalExecutor when running statements in 77 // a higher-level transaction. The fsm is in this state after encountering an 78 // execution error when running in an external transaction: the "SQL 79 // transaction" is finished, however the higher-level transaction is not rolled 80 // back. 81 type stateInternalError struct{} 82 83 var _ fsm.State = &stateInternalError{} 84 85 func (stateInternalError) String() string { 86 return InternalErrorStateStr 87 } 88 89 func (stateNoTxn) State() {} 90 func (stateOpen) State() {} 91 func (stateAborted) State() {} 92 func (stateCommitWait) State() {} 93 func (stateInternalError) State() {} 94 95 /// Events. 96 97 type eventTxnStart struct { 98 ImplicitTxn fsm.Bool 99 } 100 type eventTxnStartPayload struct { 101 tranCtx transitionCtx 102 103 pri roachpb.UserPriority 104 // txnSQLTimestamp is the timestamp that statements executed in the 105 // transaction that is started by this event will report for now(), 106 // current_timestamp(), transaction_timestamp(). 107 txnSQLTimestamp time.Time 108 readOnly tree.ReadWriteMode 109 historicalTimestamp *hlc.Timestamp 110 } 111 112 // makeEventTxnStartPayload creates an eventTxnStartPayload. 113 func makeEventTxnStartPayload( 114 pri roachpb.UserPriority, 115 readOnly tree.ReadWriteMode, 116 txnSQLTimestamp time.Time, 117 historicalTimestamp *hlc.Timestamp, 118 tranCtx transitionCtx, 119 ) eventTxnStartPayload { 120 return eventTxnStartPayload{ 121 pri: pri, 122 readOnly: readOnly, 123 txnSQLTimestamp: txnSQLTimestamp, 124 historicalTimestamp: historicalTimestamp, 125 tranCtx: tranCtx, 126 } 127 } 128 129 type eventTxnFinish struct{} 130 131 // eventTxnFinishPayload represents the payload for eventTxnFinish. 132 type eventTxnFinishPayload struct { 133 // commit is set if the transaction committed, false if it was aborted. 134 commit bool 135 } 136 137 // eventSavepointRollback is generated when we want to move from Aborted to Open 138 // through a ROLLBACK TO SAVEPOINT <not cockroach_restart>. Note that it is not 139 // generated when such a savepoint is rolled back to from the Open state. In 140 // that case no event is necessary. 141 type eventSavepointRollback struct{} 142 143 type eventNonRetriableErr struct { 144 IsCommit fsm.Bool 145 } 146 147 // eventNonRetriableErrPayload represents the payload for eventNonRetriableErr. 148 type eventNonRetriableErrPayload struct { 149 // err is the error that caused the event. 150 err error 151 } 152 153 // errorCause implements the payloadWithError interface. 154 func (p eventNonRetriableErrPayload) errorCause() error { 155 return p.err 156 } 157 158 // eventNonRetriableErrorPayload implements payloadWithError. 159 var _ payloadWithError = eventNonRetriableErrPayload{} 160 161 type eventRetriableErr struct { 162 CanAutoRetry fsm.Bool 163 IsCommit fsm.Bool 164 } 165 166 // eventRetriableErrPayload represents the payload for eventRetriableErr. 167 type eventRetriableErrPayload struct { 168 // err is the error that caused the event 169 err error 170 // rewCap must be set if CanAutoRetry is set on the event. It will be passed 171 // back to the connExecutor to perform the rewind. 172 rewCap rewindCapability 173 } 174 175 // errorCause implements the payloadWithError interface. 176 func (p eventRetriableErrPayload) errorCause() error { 177 return p.err 178 } 179 180 // eventRetriableErrPayload implements payloadWithError. 181 var _ payloadWithError = eventRetriableErrPayload{} 182 183 // eventTxnRestart is generated by a rollback to a savepoint placed at the 184 // beginning of the transaction (commonly SAVEPOINT cockroach_restart). 185 type eventTxnRestart struct{} 186 187 // eventTxnReleased is generated after a successful RELEASE SAVEPOINT 188 // cockroach_restart. It moves the state to CommitWait. The event is not 189 // generated by releasing regular savepoints. 190 type eventTxnReleased struct{} 191 192 // payloadWithError is a common interface for the payloads that wrap an error. 193 type payloadWithError interface { 194 errorCause() error 195 } 196 197 func (eventTxnStart) Event() {} 198 func (eventTxnFinish) Event() {} 199 func (eventSavepointRollback) Event() {} 200 func (eventNonRetriableErr) Event() {} 201 func (eventRetriableErr) Event() {} 202 func (eventTxnRestart) Event() {} 203 func (eventTxnReleased) Event() {} 204 205 // TxnStateTransitions describe the transitions used by a connExecutor's 206 // fsm.Machine. Args.Extended is a txnState, which is muted by the Actions. 207 // 208 // This state machine accepts the eventNonRetriableErr{IsCommit: fsm.True} in all 209 // states. This contract is in place to support the cleanup of connExecutor -> 210 // this event can always be sent when the connExecutor is tearing down. 211 // 212 // NOTE: The Args.Ctx passed to the actions is the connExecutor's context. While 213 // we are inside a SQL txn, the txn's ctx should be used for operations (i.e 214 // txnState.Ctx, which is a child ctx). This is so because transitions that move 215 // in and out of transactions need to have access to both contexts. 216 // 217 //go:generate ../util/fsm/gen/reports.sh TxnStateTransitions stateNoTxn 218 var TxnStateTransitions = fsm.Compile(fsm.Pattern{ 219 // NoTxn 220 // 221 // Note that we don't handle any errors in this state. The connExecutor is 222 // supposed to send an eventTxnStart before any other statement that may 223 // generate an error. 224 stateNoTxn{}: { 225 eventTxnStart{fsm.Var("implicitTxn")}: { 226 Description: "BEGIN, or before a statement running as an implicit txn", 227 Next: stateOpen{ImplicitTxn: fsm.Var("implicitTxn")}, 228 Action: noTxnToOpen, 229 }, 230 eventNonRetriableErr{IsCommit: fsm.Any}: { 231 // This event doesn't change state, but it produces a skipBatch advance 232 // code. 233 Description: "anything but BEGIN or extended protocol command error", 234 Next: stateNoTxn{}, 235 Action: func(args fsm.Args) error { 236 ts := args.Extended.(*txnState) 237 ts.setAdvanceInfo(skipBatch, noRewind, noEvent) 238 return nil 239 }, 240 }, 241 }, 242 243 /// Open 244 stateOpen{ImplicitTxn: fsm.Any}: { 245 eventTxnFinish{}: { 246 Description: "COMMIT/ROLLBACK, or after a statement running as an implicit txn", 247 Next: stateNoTxn{}, 248 Action: func(args fsm.Args) error { 249 // Note that the KV txn has been committed or rolled back by the 250 // statement execution by this point. 251 return args.Extended.(*txnState).finishTxn( 252 args.Payload.(eventTxnFinishPayload), 253 ) 254 }, 255 }, 256 // Handle the error on COMMIT cases: we move to NoTxn as per Postgres error 257 // semantics. 258 eventRetriableErr{CanAutoRetry: fsm.False, IsCommit: fsm.True}: { 259 Description: "Retriable err on COMMIT", 260 Next: stateNoTxn{}, 261 Action: cleanupAndFinishOnError, 262 }, 263 eventNonRetriableErr{IsCommit: fsm.True}: { 264 Next: stateNoTxn{}, 265 Action: cleanupAndFinishOnError, 266 }, 267 }, 268 stateOpen{ImplicitTxn: fsm.Var("implicitTxn")}: { 269 // This is the case where we auto-retry. 270 eventRetriableErr{CanAutoRetry: fsm.True, IsCommit: fsm.Any}: { 271 // We leave the transaction in Open. In particular, we don't move to 272 // RestartWait, as there'd be nothing to move us back from RestartWait to 273 // Open. 274 // Note: Preparing the KV txn for restart has already happened by this 275 // point. 276 Description: "Retriable err; will auto-retry", 277 Next: stateOpen{ImplicitTxn: fsm.Var("implicitTxn")}, 278 Action: func(args fsm.Args) error { 279 // The caller will call rewCap.rewindAndUnlock(). 280 args.Extended.(*txnState).setAdvanceInfo( 281 rewind, 282 args.Payload.(eventRetriableErrPayload).rewCap, 283 txnRestart) 284 return nil 285 }, 286 }, 287 }, 288 // Handle the errors in implicit txns. They move us to NoTxn. 289 stateOpen{ImplicitTxn: fsm.True}: { 290 eventRetriableErr{CanAutoRetry: fsm.False, IsCommit: fsm.False}: { 291 Next: stateNoTxn{}, 292 Action: cleanupAndFinishOnError, 293 }, 294 eventNonRetriableErr{IsCommit: fsm.False}: { 295 Next: stateNoTxn{}, 296 Action: cleanupAndFinishOnError, 297 }, 298 }, 299 // Handle the errors in explicit txns. They move us to Aborted. 300 stateOpen{ImplicitTxn: fsm.False}: { 301 eventNonRetriableErr{IsCommit: fsm.False}: { 302 Next: stateAborted{}, 303 Action: func(args fsm.Args) error { 304 ts := args.Extended.(*txnState) 305 ts.setAdvanceInfo(skipBatch, noRewind, noEvent) 306 return nil 307 }, 308 }, 309 // ROLLBACK TO SAVEPOINT cockroach. There's not much to do other than generating a 310 // txnRestart output event. 311 eventTxnRestart{}: { 312 Description: "ROLLBACK TO SAVEPOINT cockroach_restart", 313 Next: stateOpen{ImplicitTxn: fsm.False}, 314 Action: func(args fsm.Args) error { 315 args.Extended.(*txnState).setAdvanceInfo(advanceOne, noRewind, txnRestart) 316 return nil 317 }, 318 }, 319 eventRetriableErr{CanAutoRetry: fsm.False, IsCommit: fsm.False}: { 320 Next: stateAborted{}, 321 Action: func(args fsm.Args) error { 322 // Note: Preparing the KV txn for restart has already happened by this 323 // point. 324 args.Extended.(*txnState).setAdvanceInfo(skipBatch, noRewind, noEvent) 325 return nil 326 }, 327 }, 328 eventTxnReleased{}: { 329 Description: "RELEASE SAVEPOINT cockroach_restart", 330 Next: stateCommitWait{}, 331 Action: func(args fsm.Args) error { 332 args.Extended.(*txnState).setAdvanceInfo(advanceOne, noRewind, txnCommit) 333 return nil 334 }, 335 }, 336 }, 337 338 /// Aborted 339 // 340 // Note that we don't handle any error events here. Any statement but a 341 // ROLLBACK (TO SAVEPOINT) is expected to not be passed to the state machine. 342 stateAborted{}: { 343 eventTxnFinish{}: { 344 Description: "ROLLBACK", 345 Next: stateNoTxn{}, 346 Action: func(args fsm.Args) error { 347 ts := args.Extended.(*txnState) 348 ts.txnAbortCount.Inc(1) 349 // Note that the KV txn has been rolled back by now by statement 350 // execution. 351 return ts.finishTxn(args.Payload.(eventTxnFinishPayload)) 352 }, 353 }, 354 // Any statement. 355 eventNonRetriableErr{IsCommit: fsm.False}: { 356 // This event doesn't change state, but it returns a skipBatch code. 357 Description: "any other statement", 358 Next: stateAborted{}, 359 Action: func(args fsm.Args) error { 360 args.Extended.(*txnState).setAdvanceInfo(skipBatch, noRewind, noEvent) 361 return nil 362 }, 363 }, 364 // ConnExecutor closing. 365 eventNonRetriableErr{IsCommit: fsm.True}: { 366 // This event doesn't change state, but it returns a skipBatch code. 367 Description: "ConnExecutor closing", 368 Next: stateAborted{}, 369 Action: cleanupAndFinishOnError, 370 }, 371 // ROLLBACK TO SAVEPOINT <not cockroach_restart> success. 372 eventSavepointRollback{}: { 373 Description: "ROLLBACK TO SAVEPOINT (not cockroach_restart) success", 374 Next: stateOpen{ImplicitTxn: fsm.False}, 375 Action: func(args fsm.Args) error { 376 args.Extended.(*txnState).setAdvanceInfo(advanceOne, noRewind, noEvent) 377 return nil 378 }, 379 }, 380 // ROLLBACK TO SAVEPOINT <not cockroach_restart> failed because the txn needs to restart. 381 eventRetriableErr{CanAutoRetry: fsm.Any, IsCommit: fsm.Any}: { 382 // This event doesn't change state, but it returns a skipBatch code. 383 Description: "ROLLBACK TO SAVEPOINT (not cockroach_restart) failed because txn needs restart", 384 Next: stateAborted{}, 385 Action: func(args fsm.Args) error { 386 args.Extended.(*txnState).setAdvanceInfo(skipBatch, noRewind, noEvent) 387 return nil 388 }, 389 }, 390 // ROLLBACK TO SAVEPOINT cockroach_restart. 391 eventTxnRestart{}: { 392 Description: "ROLLBACK TO SAVEPOINT cockroach_restart", 393 Next: stateOpen{ImplicitTxn: fsm.False}, 394 Action: func(args fsm.Args) error { 395 args.Extended.(*txnState).setAdvanceInfo(advanceOne, noRewind, txnRestart) 396 return nil 397 }, 398 }, 399 }, 400 401 stateCommitWait{}: { 402 eventTxnFinish{}: { 403 Description: "COMMIT", 404 Next: stateNoTxn{}, 405 Action: func(args fsm.Args) error { 406 return args.Extended.(*txnState).finishTxn( 407 args.Payload.(eventTxnFinishPayload), 408 ) 409 }, 410 }, 411 eventNonRetriableErr{IsCommit: fsm.Any}: { 412 // This event doesn't change state, but it returns a skipBatch code. 413 // 414 // Note that we don't expect any errors from error on COMMIT in this 415 // state. 416 Description: "any other statement", 417 Next: stateCommitWait{}, 418 Action: func(args fsm.Args) error { 419 args.Extended.(*txnState).setAdvanceInfo(skipBatch, noRewind, noEvent) 420 return nil 421 }, 422 }, 423 }, 424 }) 425 426 // noTxnToOpen implements the side effects of starting a txn. It also calls 427 // setAdvanceInfo(). 428 func noTxnToOpen(args fsm.Args) error { 429 connCtx := args.Ctx 430 ev := args.Event.(eventTxnStart) 431 payload := args.Payload.(eventTxnStartPayload) 432 ts := args.Extended.(*txnState) 433 434 txnTyp := explicitTxn 435 advCode := advanceOne 436 if ev.ImplicitTxn.Get() { 437 txnTyp = implicitTxn 438 // For an implicit txn, we want the statement that produced the event to be 439 // executed again (this time in state Open). 440 advCode = stayInPlace 441 } 442 443 ts.resetForNewSQLTxn( 444 connCtx, 445 txnTyp, 446 payload.txnSQLTimestamp, 447 payload.historicalTimestamp, 448 payload.pri, 449 payload.readOnly, 450 nil, /* txn */ 451 payload.tranCtx, 452 ) 453 ts.setAdvanceInfo(advCode, noRewind, txnStart) 454 return nil 455 } 456 457 // finishTxn finishes the transaction. It also calls setAdvanceInfo(). 458 func (ts *txnState) finishTxn(payload eventTxnFinishPayload) error { 459 ts.finishSQLTxn() 460 461 var ev txnEvent 462 if payload.commit { 463 ev = txnCommit 464 } else { 465 ev = txnRollback 466 } 467 ts.setAdvanceInfo(advanceOne, noRewind, ev) 468 return nil 469 } 470 471 // cleanupAndFinishOnError rolls back the KV txn and finishes the SQL txn. 472 func cleanupAndFinishOnError(args fsm.Args) error { 473 ts := args.Extended.(*txnState) 474 ts.mu.txn.CleanupOnError(ts.Ctx, args.Payload.(payloadWithError).errorCause()) 475 ts.finishSQLTxn() 476 ts.setAdvanceInfo(skipBatch, noRewind, txnRollback) 477 return nil 478 } 479 480 // BoundTxnStateTransitions is the state machine used by the InternalExecutor 481 // when running SQL inside a higher-level txn. It's a very limited state 482 // machine: it doesn't allow starting or finishing txns, auto-retries, etc. 483 var BoundTxnStateTransitions = fsm.Compile(fsm.Pattern{ 484 stateOpen{ImplicitTxn: fsm.False}: { 485 // We accept eventNonRetriableErr with both IsCommit={True, fsm.False}, even 486 // though this state machine does not support COMMIT statements because 487 // connExecutor.close() sends an eventNonRetriableErr{IsCommit: fsm.True} event. 488 eventNonRetriableErr{IsCommit: fsm.Any}: { 489 Next: stateInternalError{}, 490 Action: func(args fsm.Args) error { 491 ts := args.Extended.(*txnState) 492 ts.finishSQLTxn() 493 ts.setAdvanceInfo(skipBatch, noRewind, txnRollback) 494 return nil 495 }, 496 }, 497 eventRetriableErr{CanAutoRetry: fsm.Any, IsCommit: fsm.False}: { 498 Next: stateInternalError{}, 499 Action: func(args fsm.Args) error { 500 ts := args.Extended.(*txnState) 501 ts.finishSQLTxn() 502 ts.setAdvanceInfo(skipBatch, noRewind, txnRollback) 503 return nil 504 }, 505 }, 506 }, 507 })