From 096be54d324e931f71b7463a7a1dfcb00b6cccde Mon Sep 17 00:00:00 2001 From: Manan Gupta Date: Fri, 16 Aug 2024 19:52:08 +0530 Subject: [PATCH 1/7] feat: add code to prevent people from running atomic transactions without semi-sync Signed-off-by: Manan Gupta --- go/vt/vterrors/code.go | 1 + .../vttablet/tabletmanager/rpc_replication.go | 29 +++++++++++++------ go/vt/vttablet/tabletserver/controller.go | 3 ++ go/vt/vttablet/tabletserver/tabletserver.go | 5 ++++ go/vt/vttablet/tabletservermock/controller.go | 5 ++++ 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/go/vt/vterrors/code.go b/go/vt/vterrors/code.go index 83a87503265..9b7f7bb06fa 100644 --- a/go/vt/vterrors/code.go +++ b/go/vt/vterrors/code.go @@ -97,6 +97,7 @@ var ( VT09023 = errorWithoutState("VT09023", vtrpcpb.Code_FAILED_PRECONDITION, "could not map %v to a keyspace id", "Unable to determine the shard for the given row.") VT09024 = errorWithoutState("VT09024", vtrpcpb.Code_FAILED_PRECONDITION, "could not map %v to a unique keyspace id: %v", "Unable to determine the shard for the given row.") VT09025 = errorWithoutState("VT09025", vtrpcpb.Code_FAILED_PRECONDITION, "atomic transaction error: %v", "Error in atomic transactions") + VT09026 = errorWithoutState("VT09026", vtrpcpb.Code_FAILED_PRECONDITION, "two-pc is enabled, but semi-sync is not", "Two-PC requires semi-sync but it is not enabled") VT10001 = errorWithoutState("VT10001", vtrpcpb.Code_ABORTED, "foreign key constraints are not allowed", "Foreign key constraints are not allowed, see https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/.") VT10002 = errorWithoutState("VT10002", vtrpcpb.Code_ABORTED, "atomic distributed transaction not allowed: %s", "The distributed transaction cannot be committed. A rollback decision is taken.") diff --git a/go/vt/vttablet/tabletmanager/rpc_replication.go b/go/vt/vttablet/tabletmanager/rpc_replication.go index 3e745222092..02ed4d2dd33 100644 --- a/go/vt/vttablet/tabletmanager/rpc_replication.go +++ b/go/vt/vttablet/tabletmanager/rpc_replication.go @@ -22,11 +22,10 @@ import ( "strings" "time" + "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/mysql/replication" "vitess.io/vitess/go/mysql/sqlerror" "vitess.io/vitess/go/protoutil" - - "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/vt/mysqlctl" "vitess.io/vitess/go/vt/proto/vtrpc" @@ -348,6 +347,17 @@ func (tm *TabletManager) InitPrimary(ctx context.Context, semiSync bool) (string } defer tm.unlock() + semiSyncAction, err := tm.convertBoolToSemiSyncAction(ctx, semiSync) + if err != nil { + return "", err + } + + // Check if two-pc is enabled but semi-sync is not. + // If so, we return an error, because atomic transactions require semi-sync for correctness. + if tm.QueryServiceControl.TwoPCEnabled() && semiSyncAction != SemiSyncActionSet { + return "", vterrors.VT09026() + } + // Setting super_read_only `OFF` so that we can run the DDL commands if _, err := tm.MysqlDaemon.SetSuperReadOnly(ctx, false); err != nil { if sqlErr, ok := err.(*sqlerror.SQLError); ok && sqlErr.Number() == sqlerror.ERUnknownSystemVariable { @@ -369,11 +379,6 @@ func (tm *TabletManager) InitPrimary(ctx context.Context, semiSync bool) (string return "", err } - semiSyncAction, err := tm.convertBoolToSemiSyncAction(ctx, semiSync) - if err != nil { - return "", err - } - // Set the server read-write, from now on we can accept real // client writes. Note that if semi-sync replication is enabled, // we'll still need some replicas to be able to commit transactions. @@ -910,12 +915,18 @@ func (tm *TabletManager) PromoteReplica(ctx context.Context, semiSync bool) (str } defer tm.unlock() - pos, err := tm.MysqlDaemon.Promote(ctx, tm.hookExtraEnv()) + semiSyncAction, err := tm.convertBoolToSemiSyncAction(ctx, semiSync) if err != nil { return "", err } - semiSyncAction, err := tm.convertBoolToSemiSyncAction(ctx, semiSync) + // Check if two-pc is enabled but semi-sync is not. + // If so, we return an error, because atomic transactions require semi-sync for correctness. + if tm.QueryServiceControl.TwoPCEnabled() && semiSyncAction != SemiSyncActionSet { + return "", vterrors.VT09026() + } + + pos, err := tm.MysqlDaemon.Promote(ctx, tm.hookExtraEnv()) if err != nil { return "", err } diff --git a/go/vt/vttablet/tabletserver/controller.go b/go/vt/vttablet/tabletserver/controller.go index 0336d9a73cc..ec1ed0da672 100644 --- a/go/vt/vttablet/tabletserver/controller.go +++ b/go/vt/vttablet/tabletserver/controller.go @@ -93,6 +93,9 @@ type Controller interface { // CheckThrottler CheckThrottler(ctx context.Context, appName string, flags *throttle.CheckFlags) *throttle.CheckResult GetThrottlerStatus(ctx context.Context) *throttle.ThrottlerStatus + + // TwoPCEnabled returns if two pc is enabled or not. + TwoPCEnabled() bool } // Ensure TabletServer satisfies Controller interface. diff --git a/go/vt/vttablet/tabletserver/tabletserver.go b/go/vt/vttablet/tabletserver/tabletserver.go index e3e951892b7..bc6c475f41d 100644 --- a/go/vt/vttablet/tabletserver/tabletserver.go +++ b/go/vt/vttablet/tabletserver/tabletserver.go @@ -1692,6 +1692,11 @@ func (tsv *TabletServer) GetThrottlerStatus(ctx context.Context) *throttle.Throt return r } +// TwoPCEnabled returns whether TwoPC is enabled or not. +func (tsv *TabletServer) TwoPCEnabled() bool { + return tsv.config.TwoPCEnable +} + // HandlePanic is part of the queryservice.QueryService interface func (tsv *TabletServer) HandlePanic(err *error) { if x := recover(); x != nil { diff --git a/go/vt/vttablet/tabletservermock/controller.go b/go/vt/vttablet/tabletservermock/controller.go index 7c7055b3e15..a8a16ed06a3 100644 --- a/go/vt/vttablet/tabletservermock/controller.go +++ b/go/vt/vttablet/tabletservermock/controller.go @@ -226,6 +226,11 @@ func (tqsc *Controller) GetThrottlerStatus(ctx context.Context) *throttle.Thrott return nil } +// TwoPCEnabled returns whether TwoPC is enabled or not. +func (tqsc *Controller) TwoPCEnabled() bool { + return false +} + // EnterLameduck implements tabletserver.Controller. func (tqsc *Controller) EnterLameduck() { tqsc.mu.Lock() From 69a0e2b932ff7d1c0e2303c42bca7081dd996ea9 Mon Sep 17 00:00:00 2001 From: Manan Gupta Date: Fri, 16 Aug 2024 20:03:48 +0530 Subject: [PATCH 2/7] test: add test for verifying the changes Signed-off-by: Manan Gupta --- .../endtoend/transaction/twopc/twopc_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/go/test/endtoend/transaction/twopc/twopc_test.go b/go/test/endtoend/transaction/twopc/twopc_test.go index 5aab1f5a2e2..56fbaf99208 100644 --- a/go/test/endtoend/transaction/twopc/twopc_test.go +++ b/go/test/endtoend/transaction/twopc/twopc_test.go @@ -1030,6 +1030,24 @@ func TestReadingUnresolvedTransactions(t *testing.T) { } } +// TestSemiSyncRequiredWithTwoPC tests that semi-sync is required when using two-phase commit. +func TestSemiSyncRequiredWithTwoPC(t *testing.T) { + out, err := clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput("SetKeyspaceDurabilityPolicy", keyspaceName, "--durability-policy=none") + require.NoError(t, err, out) + defer clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput("SetKeyspaceDurabilityPolicy", keyspaceName, "--durability-policy=semi_sync") + + // After changing the durability policy for the given keyspace to none, we try to PRS. + // This call should fail. + shard := clusterInstance.Keyspaces[0].Shards[2] + newPrimary := shard.Vttablets[1] + output, err := clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput( + "PlannedReparentShard", + fmt.Sprintf("%s/%s", keyspaceName, shard.Name), + "--new-primary", newPrimary.Alias) + require.Error(t, err) + require.Contains(t, output, "two-pc is enabled, but semi-sync is not") +} + // TestDisruptions tests that atomic transactions persevere through various disruptions. func TestDisruptions(t *testing.T) { testcases := []struct { From 705a1cd76fe1c31a19dbed8bb14d98f8f5bfeb63 Mon Sep 17 00:00:00 2001 From: Manan Gupta Date: Wed, 21 Aug 2024 18:34:44 +0530 Subject: [PATCH 3/7] feat: instead of failing primary promotion, we disallow prepare calls instead Signed-off-by: Manan Gupta --- go/vt/vttablet/tabletmanager/rpc_replication.go | 16 ++++++---------- go/vt/vttablet/tabletserver/controller.go | 4 ++-- go/vt/vttablet/tabletserver/dt_executor.go | 3 +++ go/vt/vttablet/tabletserver/tabletserver.go | 6 +++--- go/vt/vttablet/tabletserver/tx_engine.go | 6 +++++- go/vt/vttablet/tabletservermock/controller.go | 5 ++--- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/go/vt/vttablet/tabletmanager/rpc_replication.go b/go/vt/vttablet/tabletmanager/rpc_replication.go index e3ce7fa9e70..1b4fdcca108 100644 --- a/go/vt/vttablet/tabletmanager/rpc_replication.go +++ b/go/vt/vttablet/tabletmanager/rpc_replication.go @@ -352,11 +352,9 @@ func (tm *TabletManager) InitPrimary(ctx context.Context, semiSync bool) (string return "", err } - // Check if two-pc is enabled but semi-sync is not. - // If so, we return an error, because atomic transactions require semi-sync for correctness. - if tm.QueryServiceControl.TwoPCEnabled() && semiSyncAction != SemiSyncActionSet { - return "", vterrors.VT09031() - } + // If semi-sync is enabled, we need to set two pc to be allowed. + // Otherwise, we block all Prepared calls because atomic transactions require semi-sync for correctness.. + tm.QueryServiceControl.SetTwoPCAllowed(semiSyncAction == SemiSyncActionSet) // Setting super_read_only `OFF` so that we can run the DDL commands if _, err := tm.MysqlDaemon.SetSuperReadOnly(ctx, false); err != nil { @@ -920,11 +918,9 @@ func (tm *TabletManager) PromoteReplica(ctx context.Context, semiSync bool) (str return "", err } - // Check if two-pc is enabled but semi-sync is not. - // If so, we return an error, because atomic transactions require semi-sync for correctness. - if tm.QueryServiceControl.TwoPCEnabled() && semiSyncAction != SemiSyncActionSet { - return "", vterrors.VT09031() - } + // If semi-sync is enabled, we need to set two pc to be allowed. + // Otherwise, we block all Prepared calls because atomic transactions require semi-sync for correctness.. + tm.QueryServiceControl.SetTwoPCAllowed(semiSyncAction == SemiSyncActionSet) pos, err := tm.MysqlDaemon.Promote(ctx, tm.hookExtraEnv()) if err != nil { diff --git a/go/vt/vttablet/tabletserver/controller.go b/go/vt/vttablet/tabletserver/controller.go index ec1ed0da672..4fe8f30153f 100644 --- a/go/vt/vttablet/tabletserver/controller.go +++ b/go/vt/vttablet/tabletserver/controller.go @@ -94,8 +94,8 @@ type Controller interface { CheckThrottler(ctx context.Context, appName string, flags *throttle.CheckFlags) *throttle.CheckResult GetThrottlerStatus(ctx context.Context) *throttle.ThrottlerStatus - // TwoPCEnabled returns if two pc is enabled or not. - TwoPCEnabled() bool + // SetTwoPCAllowed sets whether TwoPC is allowed or not. + SetTwoPCAllowed(bool) } // Ensure TabletServer satisfies Controller interface. diff --git a/go/vt/vttablet/tabletserver/dt_executor.go b/go/vt/vttablet/tabletserver/dt_executor.go index 1fd1df12d56..13764c6afff 100644 --- a/go/vt/vttablet/tabletserver/dt_executor.go +++ b/go/vt/vttablet/tabletserver/dt_executor.go @@ -53,6 +53,9 @@ func (dte *DTExecutor) Prepare(transactionID int64, dtid string) error { if !dte.te.twopcEnabled { return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") } + if !dte.te.twopcAllowed { + return vterrors.VT09031() + } defer dte.te.env.Stats().QueryTimings.Record("PREPARE", time.Now()) dte.logStats.TransactionID = transactionID diff --git a/go/vt/vttablet/tabletserver/tabletserver.go b/go/vt/vttablet/tabletserver/tabletserver.go index bc6c475f41d..74200e762b9 100644 --- a/go/vt/vttablet/tabletserver/tabletserver.go +++ b/go/vt/vttablet/tabletserver/tabletserver.go @@ -1692,9 +1692,9 @@ func (tsv *TabletServer) GetThrottlerStatus(ctx context.Context) *throttle.Throt return r } -// TwoPCEnabled returns whether TwoPC is enabled or not. -func (tsv *TabletServer) TwoPCEnabled() bool { - return tsv.config.TwoPCEnable +// SetTwoPCAllowed sets whether TwoPC is allowed or not. +func (tsv *TabletServer) SetTwoPCAllowed(allowed bool) { + tsv.te.twopcAllowed = allowed } // HandlePanic is part of the queryservice.QueryService interface diff --git a/go/vt/vttablet/tabletserver/tx_engine.go b/go/vt/vttablet/tabletserver/tx_engine.go index 33e22e321bc..a3ff844e0e1 100644 --- a/go/vt/vttablet/tabletserver/tx_engine.go +++ b/go/vt/vttablet/tabletserver/tx_engine.go @@ -75,7 +75,11 @@ type TxEngine struct { // transition while creating new transactions beginRequests sync.WaitGroup - twopcEnabled bool + // twopcEnabled is the flag value of whether the user has enabled twopc or not. + twopcEnabled bool + // twopcAllowed is wether it is safe to allow two pc transactions or not. + // If the primary tablet doesn't run with semi-sync we set this to false, and disallow any prepared calls. + twopcAllowed bool shutdownGracePeriod time.Duration coordinatorAddress string abandonAge time.Duration diff --git a/go/vt/vttablet/tabletservermock/controller.go b/go/vt/vttablet/tabletservermock/controller.go index a8a16ed06a3..a3a471a6c3f 100644 --- a/go/vt/vttablet/tabletservermock/controller.go +++ b/go/vt/vttablet/tabletservermock/controller.go @@ -226,9 +226,8 @@ func (tqsc *Controller) GetThrottlerStatus(ctx context.Context) *throttle.Thrott return nil } -// TwoPCEnabled returns whether TwoPC is enabled or not. -func (tqsc *Controller) TwoPCEnabled() bool { - return false +// SetTwoPCAllowed sets whether TwoPC is allowed or not. +func (tqsc *Controller) SetTwoPCAllowed(bool) { } // EnterLameduck implements tabletserver.Controller. From 4b0d5eec390f44e1f53b2bfc894ed06daf239cb7 Mon Sep 17 00:00:00 2001 From: Manan Gupta Date: Wed, 21 Aug 2024 19:02:52 +0530 Subject: [PATCH 4/7] test: fix test to reflect latest changes Signed-off-by: Manan Gupta --- .../endtoend/transaction/twopc/twopc_test.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/go/test/endtoend/transaction/twopc/twopc_test.go b/go/test/endtoend/transaction/twopc/twopc_test.go index 56fbaf99208..77f3ca2f354 100644 --- a/go/test/endtoend/transaction/twopc/twopc_test.go +++ b/go/test/endtoend/transaction/twopc/twopc_test.go @@ -1032,20 +1032,31 @@ func TestReadingUnresolvedTransactions(t *testing.T) { // TestSemiSyncRequiredWithTwoPC tests that semi-sync is required when using two-phase commit. func TestSemiSyncRequiredWithTwoPC(t *testing.T) { + // cleanup all the old data. + conn, closer := start(t) + defer closer() + out, err := clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput("SetKeyspaceDurabilityPolicy", keyspaceName, "--durability-policy=none") require.NoError(t, err, out) defer clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput("SetKeyspaceDurabilityPolicy", keyspaceName, "--durability-policy=semi_sync") - // After changing the durability policy for the given keyspace to none, we try to PRS. - // This call should fail. + // After changing the durability policy for the given keyspace to none, we run PRS. shard := clusterInstance.Keyspaces[0].Shards[2] newPrimary := shard.Vttablets[1] - output, err := clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput( + _, err = clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput( "PlannedReparentShard", fmt.Sprintf("%s/%s", keyspaceName, shard.Name), "--new-primary", newPrimary.Alias) + require.NoError(t, err) + + // A new distributed transaction should fail. + utils.Exec(t, conn, "begin") + utils.Exec(t, conn, "insert into twopc_t1(id, col) values(4, 4)") + utils.Exec(t, conn, "insert into twopc_t1(id, col) values(6, 4)") + utils.Exec(t, conn, "insert into twopc_t1(id, col) values(9, 4)") + _, err = utils.ExecAllowError(t, conn, "commit") require.Error(t, err) - require.Contains(t, output, "two-pc is enabled, but semi-sync is not") + require.ErrorContains(t, err, "two-pc is enabled, but semi-sync is not") } // TestDisruptions tests that atomic transactions persevere through various disruptions. From 270d2a12e0e517028a67edbf193af53d4dcd937f Mon Sep 17 00:00:00 2001 From: Manan Gupta Date: Thu, 22 Aug 2024 10:39:07 +0530 Subject: [PATCH 5/7] feat: enable twopc by default and also set it during DemotePrimary Signed-off-by: Manan Gupta --- go/vt/vttablet/tabletmanager/rpc_replication.go | 4 ++++ go/vt/vttablet/tabletserver/tx_engine.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/go/vt/vttablet/tabletmanager/rpc_replication.go b/go/vt/vttablet/tabletmanager/rpc_replication.go index 351807cd091..1bd05493a59 100644 --- a/go/vt/vttablet/tabletmanager/rpc_replication.go +++ b/go/vt/vttablet/tabletmanager/rpc_replication.go @@ -598,6 +598,10 @@ func (tm *TabletManager) UndoDemotePrimary(ctx context.Context, semiSync bool) e return err } + // If semi-sync is enabled, we need to set two pc to be allowed. + // Otherwise, we block all Prepared calls because atomic transactions require semi-sync for correctness.. + tm.QueryServiceControl.SetTwoPCAllowed(semiSyncAction == SemiSyncActionSet) + // If using semi-sync, we need to enable source-side. if err := tm.fixSemiSync(ctx, topodatapb.TabletType_PRIMARY, semiSyncAction); err != nil { return err diff --git a/go/vt/vttablet/tabletserver/tx_engine.go b/go/vt/vttablet/tabletserver/tx_engine.go index 5858f0034de..c465dc00578 100644 --- a/go/vt/vttablet/tabletserver/tx_engine.go +++ b/go/vt/vttablet/tabletserver/tx_engine.go @@ -104,6 +104,9 @@ func NewTxEngine(env tabletenv.Env, dxNotifier func()) *TxEngine { } limiter := txlimiter.New(env) te.txPool = NewTxPool(env, limiter) + // We initially allow twoPC (handles vttablet restarts). + // We will disallow them, when a new tablet is promoted if semi-sync is turned off. + te.twopcAllowed = true te.twopcEnabled = config.TwoPCEnable if te.twopcEnabled { if config.TwoPCAbandonAge <= 0 { From 683aa751e74be7b259acabf9198a1e92207e37e1 Mon Sep 17 00:00:00 2001 From: Manan Gupta Date: Thu, 22 Aug 2024 11:06:16 +0530 Subject: [PATCH 6/7] test: make the test using twopc transactions run semi-sync Signed-off-by: Manan Gupta --- go/test/endtoend/transaction/tx_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/go/test/endtoend/transaction/tx_test.go b/go/test/endtoend/transaction/tx_test.go index 40621a1d84b..475b17cfa2c 100644 --- a/go/test/endtoend/transaction/tx_test.go +++ b/go/test/endtoend/transaction/tx_test.go @@ -69,9 +69,10 @@ func TestMain(m *testing.M) { // Start keyspace keyspace := &cluster.Keyspace{ - Name: keyspaceName, - SchemaSQL: SchemaSQL, - VSchema: VSchema, + Name: keyspaceName, + SchemaSQL: SchemaSQL, + VSchema: VSchema, + DurabilityPolicy: "semi_sync", } if err := clusterInstance.StartKeyspace(*keyspace, []string{"-80", "80-"}, 1, false); err != nil { return 1, err From b6208bdec2014a0b1f2475990b40f6d9df195442 Mon Sep 17 00:00:00 2001 From: Manan Gupta Date: Fri, 23 Aug 2024 15:37:55 +0530 Subject: [PATCH 7/7] feat: reuse the existing error code Signed-off-by: Manan Gupta --- go/vt/vterrors/code.go | 1 - go/vt/vttablet/tabletserver/dt_executor.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/go/vt/vterrors/code.go b/go/vt/vterrors/code.go index 5a1d7dab34a..31c98cef280 100644 --- a/go/vt/vterrors/code.go +++ b/go/vt/vterrors/code.go @@ -102,7 +102,6 @@ var ( VT09028 = errorWithState("VT09028", vtrpcpb.Code_FAILED_PRECONDITION, CTERecursiveForbiddenJoinOrder, "In recursive query block of Recursive Common Table Expression '%s', the recursive table must neither be in the right argument of a LEFT JOIN, nor be forced to be non-first with join order hints", "") VT09029 = errorWithState("VT09029", vtrpcpb.Code_FAILED_PRECONDITION, CTERecursiveRequiresSingleReference, "In recursive query block of Recursive Common Table Expression %s, the recursive table must be referenced only once, and not in any subquery", "") VT09030 = errorWithState("VT09030", vtrpcpb.Code_FAILED_PRECONDITION, CTEMaxRecursionDepth, "Recursive query aborted after 1000 iterations.", "") - VT09031 = errorWithoutState("VT09031", vtrpcpb.Code_FAILED_PRECONDITION, "two-pc is enabled, but semi-sync is not", "Two-PC requires semi-sync but it is not enabled") VT10001 = errorWithoutState("VT10001", vtrpcpb.Code_ABORTED, "foreign key constraints are not allowed", "Foreign key constraints are not allowed, see https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/.") VT10002 = errorWithoutState("VT10002", vtrpcpb.Code_ABORTED, "atomic distributed transaction not allowed: %s", "The distributed transaction cannot be committed. A rollback decision is taken.") diff --git a/go/vt/vttablet/tabletserver/dt_executor.go b/go/vt/vttablet/tabletserver/dt_executor.go index da002859020..94e540c9a28 100644 --- a/go/vt/vttablet/tabletserver/dt_executor.go +++ b/go/vt/vttablet/tabletserver/dt_executor.go @@ -54,7 +54,7 @@ func (dte *DTExecutor) Prepare(transactionID int64, dtid string) error { return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") } if !dte.te.twopcAllowed { - return vterrors.VT09031() + return vterrors.VT10002("two-pc is enabled, but semi-sync is not") } defer dte.te.env.Stats().QueryTimings.Record("PREPARE", time.Now()) dte.logStats.TransactionID = transactionID