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 }