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  })