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