vitess.io/vitess@v0.16.2/go/vt/vttablet/queryservice/wrapped.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package queryservice
    18  
    19  import (
    20  	"context"
    21  
    22  	"vitess.io/vitess/go/sqltypes"
    23  	"vitess.io/vitess/go/vt/vterrors"
    24  
    25  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    26  	querypb "vitess.io/vitess/go/vt/proto/query"
    27  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    28  )
    29  
    30  var _ QueryService = &wrappedService{}
    31  
    32  // WrapperFunc defines the signature for the wrapper function used by Wrap.
    33  // Parameter ordering is as follows: original parameters, connection, method name, additional parameters and inner func.
    34  // The inner function returns err and canRetry.
    35  // If canRetry is true, the error is specific to the current vttablet and can be retried elsewhere.
    36  // The flag will be false if there was no error.
    37  type WrapperFunc func(ctx context.Context, target *querypb.Target, conn QueryService, name string, inTransaction bool, inner func(context.Context, *querypb.Target, QueryService) (canRetry bool, err error)) error
    38  
    39  // Wrap returns a wrapped version of the original QueryService implementation.
    40  // This lets you avoid repeating boiler-plate code by consolidating it in the
    41  // wrapper function.
    42  // A good example of this is go/vt/vtgate/gateway/discoverygateway.go.
    43  // For every method invocation, the wrapper function is called, which can
    44  // in turn call the provided inner function that will use the input parameters
    45  // to call the implementation. In order to load balance across multiple
    46  // implementations, you can set impl to be nil and provide the connection
    47  // as input to the action function. In the case of StreamHealth or Close,
    48  // there is no target and it will be nil. If necessary, the wrapper
    49  // can validate the nil against the method name. The wrapper is also
    50  // responsible for calling HandlePanic where necessary.
    51  func Wrap(impl QueryService, wrapper WrapperFunc) QueryService {
    52  	return &wrappedService{
    53  		impl:    impl,
    54  		wrapper: wrapper,
    55  	}
    56  }
    57  
    58  // canRetry returns true if the error is retryable on a different vttablet.
    59  // Nil error or a canceled context make it return
    60  // false. Otherwise, the error code determines the outcome.
    61  func canRetry(ctx context.Context, err error) bool {
    62  	if err == nil {
    63  		return false
    64  	}
    65  
    66  	select {
    67  	case <-ctx.Done():
    68  		return false
    69  	default:
    70  	}
    71  
    72  	switch vterrors.Code(err) {
    73  	case vtrpcpb.Code_UNAVAILABLE, vtrpcpb.Code_FAILED_PRECONDITION, vtrpcpb.Code_CLUSTER_EVENT:
    74  		return true
    75  	}
    76  	return false
    77  }
    78  
    79  // wrappedService wraps an existing QueryService with
    80  // a decorator function.
    81  type wrappedService struct {
    82  	impl    QueryService
    83  	wrapper WrapperFunc
    84  }
    85  
    86  func (ws *wrappedService) Begin(ctx context.Context, target *querypb.Target, options *querypb.ExecuteOptions) (state TransactionState, err error) {
    87  	err = ws.wrapper(ctx, target, ws.impl, "Begin", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
    88  		var innerErr error
    89  		state, innerErr = conn.Begin(ctx, target, options)
    90  		return canRetry(ctx, innerErr), innerErr
    91  	})
    92  	return state, err
    93  }
    94  
    95  func (ws *wrappedService) Commit(ctx context.Context, target *querypb.Target, transactionID int64) (int64, error) {
    96  	var rID int64
    97  	err := ws.wrapper(ctx, target, ws.impl, "Commit", true, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
    98  		var innerErr error
    99  		rID, innerErr = conn.Commit(ctx, target, transactionID)
   100  		return canRetry(ctx, innerErr), innerErr
   101  	})
   102  	if err != nil {
   103  		return 0, err
   104  	}
   105  	return rID, nil
   106  }
   107  
   108  func (ws *wrappedService) Rollback(ctx context.Context, target *querypb.Target, transactionID int64) (int64, error) {
   109  	var rID int64
   110  	err := ws.wrapper(ctx, target, ws.impl, "Rollback", true, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   111  		var innerErr error
   112  		rID, innerErr = conn.Rollback(ctx, target, transactionID)
   113  		return canRetry(ctx, innerErr), innerErr
   114  	})
   115  	if err != nil {
   116  		return 0, err
   117  	}
   118  	return rID, nil
   119  }
   120  
   121  func (ws *wrappedService) Prepare(ctx context.Context, target *querypb.Target, transactionID int64, dtid string) error {
   122  	return ws.wrapper(ctx, target, ws.impl, "Prepare", true, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   123  		innerErr := conn.Prepare(ctx, target, transactionID, dtid)
   124  		return canRetry(ctx, innerErr), innerErr
   125  	})
   126  }
   127  
   128  func (ws *wrappedService) CommitPrepared(ctx context.Context, target *querypb.Target, dtid string) (err error) {
   129  	return ws.wrapper(ctx, target, ws.impl, "CommitPrepared", true, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   130  		innerErr := conn.CommitPrepared(ctx, target, dtid)
   131  		return canRetry(ctx, innerErr), innerErr
   132  	})
   133  }
   134  
   135  func (ws *wrappedService) RollbackPrepared(ctx context.Context, target *querypb.Target, dtid string, originalID int64) (err error) {
   136  	return ws.wrapper(ctx, target, ws.impl, "RollbackPrepared", true, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   137  		innerErr := conn.RollbackPrepared(ctx, target, dtid, originalID)
   138  		return canRetry(ctx, innerErr), innerErr
   139  	})
   140  }
   141  
   142  func (ws *wrappedService) CreateTransaction(ctx context.Context, target *querypb.Target, dtid string, participants []*querypb.Target) (err error) {
   143  	return ws.wrapper(ctx, target, ws.impl, "CreateTransaction", true, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   144  		innerErr := conn.CreateTransaction(ctx, target, dtid, participants)
   145  		return canRetry(ctx, innerErr), innerErr
   146  	})
   147  }
   148  
   149  func (ws *wrappedService) StartCommit(ctx context.Context, target *querypb.Target, transactionID int64, dtid string) (err error) {
   150  	return ws.wrapper(ctx, target, ws.impl, "StartCommit", true, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   151  		innerErr := conn.StartCommit(ctx, target, transactionID, dtid)
   152  		return canRetry(ctx, innerErr), innerErr
   153  	})
   154  }
   155  
   156  func (ws *wrappedService) SetRollback(ctx context.Context, target *querypb.Target, dtid string, transactionID int64) (err error) {
   157  	return ws.wrapper(ctx, target, ws.impl, "SetRollback", true, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   158  		innerErr := conn.SetRollback(ctx, target, dtid, transactionID)
   159  		return canRetry(ctx, innerErr), innerErr
   160  	})
   161  }
   162  
   163  func (ws *wrappedService) ConcludeTransaction(ctx context.Context, target *querypb.Target, dtid string) (err error) {
   164  	return ws.wrapper(ctx, target, ws.impl, "ConcludeTransaction", true, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   165  		innerErr := conn.ConcludeTransaction(ctx, target, dtid)
   166  		return canRetry(ctx, innerErr), innerErr
   167  	})
   168  }
   169  
   170  func (ws *wrappedService) ReadTransaction(ctx context.Context, target *querypb.Target, dtid string) (metadata *querypb.TransactionMetadata, err error) {
   171  	err = ws.wrapper(ctx, target, ws.impl, "ReadTransaction", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   172  		var innerErr error
   173  		metadata, innerErr = conn.ReadTransaction(ctx, target, dtid)
   174  		return canRetry(ctx, innerErr), innerErr
   175  	})
   176  	return metadata, err
   177  }
   178  
   179  func (ws *wrappedService) Execute(ctx context.Context, target *querypb.Target, query string, bindVars map[string]*querypb.BindVariable, transactionID, reservedID int64, options *querypb.ExecuteOptions) (qr *sqltypes.Result, err error) {
   180  	inDedicatedConn := transactionID != 0 || reservedID != 0
   181  	err = ws.wrapper(ctx, target, ws.impl, "Execute", inDedicatedConn, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   182  		var innerErr error
   183  		qr, innerErr = conn.Execute(ctx, target, query, bindVars, transactionID, reservedID, options)
   184  		// You cannot retry if you're in a transaction.
   185  		retryable := canRetry(ctx, innerErr) && (!inDedicatedConn)
   186  		return retryable, innerErr
   187  	})
   188  	return qr, err
   189  }
   190  
   191  // StreamExecute implements the QueryService interface
   192  func (ws *wrappedService) StreamExecute(ctx context.Context, target *querypb.Target, query string, bindVars map[string]*querypb.BindVariable, transactionID int64, reservedID int64, options *querypb.ExecuteOptions, callback func(*sqltypes.Result) error) error {
   193  	inDedicatedConn := transactionID != 0 || reservedID != 0
   194  	return ws.wrapper(ctx, target, ws.impl, "StreamExecute", inDedicatedConn, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   195  		streamingStarted := false
   196  		innerErr := conn.StreamExecute(ctx, target, query, bindVars, transactionID, reservedID, options, func(qr *sqltypes.Result) error {
   197  			streamingStarted = true
   198  			return callback(qr)
   199  		})
   200  		// You cannot restart a stream once it's sent results.
   201  		retryable := canRetry(ctx, innerErr) && (!streamingStarted)
   202  		return retryable, innerErr
   203  	})
   204  }
   205  
   206  func (ws *wrappedService) BeginExecute(ctx context.Context, target *querypb.Target, preQueries []string, query string, bindVars map[string]*querypb.BindVariable, reservedID int64, options *querypb.ExecuteOptions) (state TransactionState, qr *sqltypes.Result, err error) {
   207  	inDedicatedConn := reservedID != 0
   208  	err = ws.wrapper(ctx, target, ws.impl, "BeginExecute", inDedicatedConn, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   209  		var innerErr error
   210  		state, qr, innerErr = conn.BeginExecute(ctx, target, preQueries, query, bindVars, reservedID, options)
   211  		return canRetry(ctx, innerErr) && !inDedicatedConn, innerErr
   212  	})
   213  	return state, qr, err
   214  }
   215  
   216  // BeginStreamExecute implements the QueryService interface
   217  func (ws *wrappedService) BeginStreamExecute(ctx context.Context, target *querypb.Target, preQueries []string, query string, bindVars map[string]*querypb.BindVariable, reservedID int64, options *querypb.ExecuteOptions, callback func(*sqltypes.Result) error) (state TransactionState, err error) {
   218  	inDedicatedConn := reservedID != 0
   219  	err = ws.wrapper(ctx, target, ws.impl, "BeginStreamExecute", inDedicatedConn, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   220  		var innerErr error
   221  		state, innerErr = conn.BeginStreamExecute(ctx, target, preQueries, query, bindVars, reservedID, options, callback)
   222  		return canRetry(ctx, innerErr) && !inDedicatedConn, innerErr
   223  	})
   224  	return state, err
   225  }
   226  
   227  func (ws *wrappedService) MessageStream(ctx context.Context, target *querypb.Target, name string, callback func(*sqltypes.Result) error) error {
   228  	return ws.wrapper(ctx, target, ws.impl, "MessageStream", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   229  		innerErr := conn.MessageStream(ctx, target, name, callback)
   230  		return canRetry(ctx, innerErr), innerErr
   231  	})
   232  }
   233  
   234  func (ws *wrappedService) MessageAck(ctx context.Context, target *querypb.Target, name string, ids []*querypb.Value) (count int64, err error) {
   235  	err = ws.wrapper(ctx, target, ws.impl, "MessageAck", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   236  		var innerErr error
   237  		count, innerErr = conn.MessageAck(ctx, target, name, ids)
   238  		return canRetry(ctx, innerErr), innerErr
   239  	})
   240  	return count, err
   241  }
   242  
   243  func (ws *wrappedService) VStream(ctx context.Context, request *binlogdatapb.VStreamRequest, send func([]*binlogdatapb.VEvent) error) error {
   244  	return ws.wrapper(ctx, request.Target, ws.impl, "VStream", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   245  		innerErr := conn.VStream(ctx, request, send)
   246  		return false, innerErr
   247  	})
   248  }
   249  
   250  func (ws *wrappedService) VStreamRows(ctx context.Context, request *binlogdatapb.VStreamRowsRequest, send func(*binlogdatapb.VStreamRowsResponse) error) error {
   251  	return ws.wrapper(ctx, request.Target, ws.impl, "VStreamRows", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   252  		innerErr := conn.VStreamRows(ctx, request, send)
   253  		return false, innerErr
   254  	})
   255  }
   256  
   257  func (ws *wrappedService) VStreamResults(ctx context.Context, target *querypb.Target, query string, send func(*binlogdatapb.VStreamResultsResponse) error) error {
   258  	return ws.wrapper(ctx, target, ws.impl, "VStreamResults", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   259  		innerErr := conn.VStreamResults(ctx, target, query, send)
   260  		return false, innerErr
   261  	})
   262  }
   263  
   264  func (ws *wrappedService) StreamHealth(ctx context.Context, callback func(*querypb.StreamHealthResponse) error) error {
   265  	return ws.wrapper(ctx, nil, ws.impl, "StreamHealth", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   266  		innerErr := conn.StreamHealth(ctx, callback)
   267  		return canRetry(ctx, innerErr), innerErr
   268  	})
   269  }
   270  
   271  func (ws *wrappedService) HandlePanic(err *error) {
   272  	// No-op. Wrappers must call HandlePanic.
   273  }
   274  
   275  // ReserveBeginExecute implements the QueryService interface
   276  func (ws *wrappedService) ReserveBeginExecute(ctx context.Context, target *querypb.Target, preQueries []string, postBeginQueries []string, sql string, bindVariables map[string]*querypb.BindVariable, options *querypb.ExecuteOptions) (state ReservedTransactionState, res *sqltypes.Result, err error) {
   277  	err = ws.wrapper(ctx, target, ws.impl, "ReserveBeginExecute", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   278  		var err error
   279  		state, res, err = conn.ReserveBeginExecute(ctx, target, preQueries, postBeginQueries, sql, bindVariables, options)
   280  		return canRetry(ctx, err), err
   281  	})
   282  
   283  	return state, res, err
   284  }
   285  
   286  // ReserveBeginStreamExecute implements the QueryService interface
   287  func (ws *wrappedService) ReserveBeginStreamExecute(ctx context.Context, target *querypb.Target, preQueries []string, postBeginQueries []string, sql string, bindVariables map[string]*querypb.BindVariable, options *querypb.ExecuteOptions, callback func(*sqltypes.Result) error) (state ReservedTransactionState, err error) {
   288  	err = ws.wrapper(ctx, target, ws.impl, "ReserveBeginStreamExecute", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   289  		var innerErr error
   290  		state, innerErr = conn.ReserveBeginStreamExecute(ctx, target, preQueries, postBeginQueries, sql, bindVariables, options, callback)
   291  		return canRetry(ctx, innerErr), innerErr
   292  	})
   293  	return state, err
   294  }
   295  
   296  // ReserveExecute implements the QueryService interface
   297  func (ws *wrappedService) ReserveExecute(ctx context.Context, target *querypb.Target, preQueries []string, sql string, bindVariables map[string]*querypb.BindVariable, transactionID int64, options *querypb.ExecuteOptions) (state ReservedState, res *sqltypes.Result, err error) {
   298  	inDedicatedConn := transactionID != 0
   299  	err = ws.wrapper(ctx, target, ws.impl, "ReserveExecute", inDedicatedConn, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   300  		var err error
   301  		state, res, err = conn.ReserveExecute(ctx, target, preQueries, sql, bindVariables, transactionID, options)
   302  		return canRetry(ctx, err) && !inDedicatedConn, err
   303  	})
   304  
   305  	return state, res, err
   306  }
   307  
   308  // ReserveStreamExecute implements the QueryService interface
   309  func (ws *wrappedService) ReserveStreamExecute(ctx context.Context, target *querypb.Target, preQueries []string, sql string, bindVariables map[string]*querypb.BindVariable, transactionID int64, options *querypb.ExecuteOptions, callback func(*sqltypes.Result) error) (state ReservedState, err error) {
   310  	inDedicatedConn := transactionID != 0
   311  	err = ws.wrapper(ctx, target, ws.impl, "ReserveStreamExecute", inDedicatedConn, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   312  		var innerErr error
   313  		state, innerErr = conn.ReserveStreamExecute(ctx, target, preQueries, sql, bindVariables, transactionID, options, callback)
   314  		return canRetry(ctx, innerErr) && !inDedicatedConn, innerErr
   315  	})
   316  	return state, err
   317  }
   318  
   319  func (ws *wrappedService) Release(ctx context.Context, target *querypb.Target, transactionID, reservedID int64) error {
   320  	inDedicatedConn := transactionID != 0 || reservedID != 0
   321  	return ws.wrapper(ctx, target, ws.impl, "Release", inDedicatedConn, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   322  		// No point retrying Release.
   323  		return false, conn.Release(ctx, target, transactionID, reservedID)
   324  	})
   325  }
   326  
   327  func (ws *wrappedService) GetSchema(ctx context.Context, target *querypb.Target, tableType querypb.SchemaTableType, tableNames []string, callback func(schemaRes *querypb.GetSchemaResponse) error) (err error) {
   328  	err = ws.wrapper(ctx, target, ws.impl, "GetSchema", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   329  		innerErr := conn.GetSchema(ctx, target, tableType, tableNames, callback)
   330  		return canRetry(ctx, innerErr), innerErr
   331  	})
   332  	return err
   333  }
   334  
   335  func (ws *wrappedService) Close(ctx context.Context) error {
   336  	return ws.wrapper(ctx, nil, ws.impl, "Close", false, func(ctx context.Context, target *querypb.Target, conn QueryService) (bool, error) {
   337  		// No point retrying Close.
   338  		return false, conn.Close(ctx)
   339  	})
   340  }