go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/span/client.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package span 16 17 import ( 18 "context" 19 "sync" 20 "time" 21 22 "cloud.google.com/go/spanner" 23 sppb "cloud.google.com/go/spanner/apiv1/spannerpb" 24 "google.golang.org/grpc/codes" 25 26 "go.chromium.org/luci/common/errors" 27 ) 28 29 // Transaction is a common interface of spanner.ReadOnlyTransaction and 30 // spanner.ReadWriteTransaction. 31 type Transaction interface { 32 // ReadRow reads a single row from the database. 33 ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error) 34 35 // ReadRowWithOptions reads a single row from the database, and allows customizing options. 36 ReadRowWithOptions(ctx context.Context, table string, key spanner.Key, columns []string, opts *spanner.ReadOptions) (*spanner.Row, error) 37 38 // Read reads multiple rows from the database. 39 Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator 40 41 // ReadWithOptions reads multiple rows from the database, and allows 42 // customizing options. 43 ReadWithOptions(ctx context.Context, table string, keys spanner.KeySet, columns []string, opts *spanner.ReadOptions) *spanner.RowIterator 44 45 // Query reads multiple rows returned by SQL statement. 46 Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator 47 48 // QueryWithOptions reads multiple rows returned by SQL statement, and allows 49 // customizing options. 50 QueryWithOptions(ctx context.Context, statement spanner.Statement, opts spanner.QueryOptions) *spanner.RowIterator 51 } 52 53 // UseClient installs a Spanner client into the context. 54 // 55 // Primarily used by the module initialization code. May be useful in tests as 56 // well. 57 func UseClient(ctx context.Context, client *spanner.Client) context.Context { 58 return context.WithValue(ctx, &clientContextKey, client) 59 } 60 61 // Apply applies a list of mutations atomically to the database. 62 // 63 // Panics if called from inside a read-write transaction. Use BufferWrite to 64 // apply mutations there. 65 func Apply(ctx context.Context, ms []*spanner.Mutation, opts ...spanner.ApplyOption) (commitTimestamp time.Time, err error) { 66 panicOnNestedRW(ctx) 67 if ctxOpts, ok := ctx.Value(&requestOptionsContextKey).(*RequestOptions); ok { 68 opts = append([]spanner.ApplyOption{spanner.Priority(ctxOpts.Priority)}, opts...) 69 } 70 return client(ctx).Apply(ctx, ms, opts...) 71 } 72 73 // PartitionedUpdate executes a DML statement in parallel across the database, 74 // using separate, internal transactions that commit independently. 75 // 76 // Panics if called from inside a read-write transaction. 77 func PartitionedUpdate(ctx context.Context, st spanner.Statement) (count int64, err error) { 78 panicOnNestedRW(ctx) 79 return client(ctx).PartitionedUpdateWithOptions(ctx, st, queryOptionsFromContext(ctx)) 80 } 81 82 // Single returns a derived context with a single-use read-only transaction. 83 // 84 // It provides a read-only snapshot transaction optimized for the case where 85 // only a single read or query is needed. This is more efficient than using 86 // ReadOnlyTransaction() for a single read or query. 87 // 88 // The transaction object can be obtained through RO(ctx) or Txn(ctx). It is 89 // also transparently used by ReadRow, Read, Query, etc. wrappers. 90 // 91 // Panics if `ctx` already holds a transaction (either read-write or read-only). 92 func Single(ctx context.Context) context.Context { 93 panicOnNestedRO(ctx) 94 return setTxnState(ctx, &txnState{ro: client(ctx).Single()}) 95 } 96 97 // ReadOnlyTransaction returns a derived context with a read-only transaction. 98 // 99 // It can be used for multiple reads from the database. To avoid leaking 100 // resources on the server this context *must* be canceled as soon as all reads 101 // are done. 102 // 103 // The transaction object can be obtained through RO(ctx) or Txn(ctx). It is 104 // also transparently used by ReadRow, Read, Query, etc. wrappers. 105 // 106 // Panics if `ctx` already holds a transaction (either read-write or read-only). 107 func ReadOnlyTransaction(ctx context.Context) (context.Context, context.CancelFunc) { 108 panicOnNestedRO(ctx) 109 txn := client(ctx).ReadOnlyTransaction() 110 ctx, cancel := context.WithCancel(setTxnState(ctx, &txnState{ro: txn})) 111 return ctx, func() { cancel(); txn.Close() } 112 } 113 114 // ReadWriteTransaction executes a read-write transaction, with retries as 115 // necessary. 116 // 117 // The callback may be called multiple times if Spanner client decides to retry 118 // the transaction. In particular this happens if the callback returns (perhaps 119 // wrapped) ABORTED error. This error is returned by Spanner client methods if 120 // they encounter a stale transaction. 121 // 122 // See https://godoc.org/cloud.google.com/go/spanner#ReadWriteTransaction for 123 // more details. 124 // 125 // The callback can access the transaction object via RW(ctx) or Txn(ctx). It is 126 // also transparently used by ReadRow, Read, Query, BufferWrite, etc. wrappers. 127 // 128 // Panics if `ctx` already holds a read-write transaction. Starting a read-write 129 // transaction from a read-only transaction is OK though, but beware that they 130 // are completely separate unrelated transactions. 131 func ReadWriteTransaction(ctx context.Context, f func(ctx context.Context) error) (commitTimestamp time.Time, err error) { 132 panicOnNestedRW(ctx) 133 134 var state *txnState 135 136 cts, err := client(ctx).ReadWriteTransaction(ctx, func(ctx context.Context, rw *spanner.ReadWriteTransaction) error { 137 state = &txnState{rw: rw} 138 err := f(setTxnState(ctx, state)) 139 if unwrapped := errors.Unwrap(err); spanner.ErrCode(unwrapped) == codes.Aborted { 140 err = unwrapped 141 } 142 return err 143 }) 144 145 if err == nil { 146 state.execCBs(ctx) 147 } 148 149 return cts, err 150 } 151 152 // RO returns the current read-only transaction in the context or nil if it's 153 // not a read-only transactional context. 154 func RO(ctx context.Context) *spanner.ReadOnlyTransaction { 155 if s := getTxnState(ctx); s != nil { 156 return s.ro 157 } 158 return nil 159 } 160 161 // MustRO is like RO except it panics if `ctx` is not read-only transactional. 162 func MustRO(ctx context.Context) *spanner.ReadOnlyTransaction { 163 if ro := RO(ctx); ro != nil { 164 return ro 165 } 166 panic("not a read-only Spanner transactional context") 167 } 168 169 // RW returns the current read-write transaction in the context or nil if it's 170 // not a read-write transactional context. 171 func RW(ctx context.Context) *spanner.ReadWriteTransaction { 172 if s := getTxnState(ctx); s != nil { 173 return s.rw 174 } 175 return nil 176 } 177 178 // MustRW is like RW except it panics if `ctx` is not read-write transactional. 179 func MustRW(ctx context.Context) *spanner.ReadWriteTransaction { 180 if rw := RW(ctx); rw != nil { 181 return rw 182 } 183 panic("not a read-write Spanner transactional context") 184 } 185 186 // Txn returns an interface that can be used to read data in the current 187 // read-only or read-write transaction. 188 // 189 // Returns nil if `ctx` is not a transactional context. 190 func Txn(ctx context.Context) Transaction { 191 switch s := getTxnState(ctx); { 192 case s == nil: 193 return nil 194 case s.ro != nil: 195 return s.ro 196 default: 197 return s.rw 198 } 199 } 200 201 // MustTxn is like Txn except it panics if `ctx` is not transactional. 202 func MustTxn(ctx context.Context) Transaction { 203 if txn := Txn(ctx); txn != nil { 204 return txn 205 } 206 panic("not a transactional Spanner context") 207 } 208 209 // WithoutTxn returns a copy of the context without the transaction in it. 210 // 211 // This can be used to spawn separate independent transactions from within 212 // a transaction. 213 func WithoutTxn(ctx context.Context) context.Context { 214 if getTxnState(ctx) == nil { 215 return ctx 216 } 217 return setTxnState(ctx, nil) 218 } 219 220 // Defer schedules `cb` for execution when the current read-write transaction 221 // successfully lands. 222 // 223 // Intended for a best-effort non-transactional follow up to a successful 224 // transaction. Note that in presence of failures there's no guarantee the 225 // callback will be called. For example, the callback won't ever be called if 226 // the process crashes right after landing the transaction. Or if the 227 // transaction really landed, but ReadWriteTransaction finished with 228 // "deadline exceeded" (or some similar) error. 229 // 230 // Callbacks are executed sequentially in the reverse order they were deferred. 231 // They receive the non-transactional version of the context initially passed to 232 // ReadWriteTransaction. 233 // 234 // Panics if the given context is not transactional. 235 func Defer(ctx context.Context, cb func(context.Context)) { 236 state := getTxnState(ctx) 237 if state == nil || state.rw == nil { 238 panic("not a read-write Spanner transactional context") 239 } 240 state.deferCB(cb) 241 } 242 243 // ReadRow reads a single row from the database. 244 // 245 // It is a shortcut for MustTxn(ctx).ReadRow(ctx, ...). Panics if the context 246 // is not transactional. 247 func ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error) { 248 return MustTxn(ctx).ReadRowWithOptions(ctx, table, key, columns, readOptionsFromContext(ctx)) 249 } 250 251 // ReadRowWithOptions reads a single row from the database, and allows customizing 252 // options. 253 // 254 // It is a shortcut for MustTxn(ctx).ReadRowWithOptions(ctx, ...). Panics if 255 // the context is not transactional. 256 // 257 // It does not use the default RequestOptions from the ctx. Use opts to pass 258 // these explicitly. 259 func ReadRowWithOptions(ctx context.Context, table string, key spanner.Key, columns []string, opts *spanner.ReadOptions) (*spanner.Row, error) { 260 return MustTxn(ctx).ReadRowWithOptions(ctx, table, key, columns, opts) 261 } 262 263 // Read reads multiple rows from the database. 264 // 265 // It is a shortcut for MustTxn(ctx).Read(ctx, ...). Panics if the context 266 // is not transactional. 267 func Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator { 268 return MustTxn(ctx).ReadWithOptions(ctx, table, keys, columns, readOptionsFromContext(ctx)) 269 } 270 271 // ReadWithOptions reads multiple rows from the database, and allows customizing 272 // options. 273 // 274 // It is a shortcut for MustTxn(ctx).ReadWithOptions(ctx, ...). Panics if the 275 // context is not transactional. 276 // 277 // It does not use the default RequestOptions from the ctx. Use opts to pass 278 // these explicitly. 279 func ReadWithOptions(ctx context.Context, table string, keys spanner.KeySet, columns []string, opts *spanner.ReadOptions) *spanner.RowIterator { 280 return MustTxn(ctx).ReadWithOptions(ctx, table, keys, columns, opts) 281 } 282 283 // Query reads multiple rows returned by SQL statement. 284 // 285 // It is a shortcut for MustTxn(ctx).Query(ctx, ...). Panics if the context is 286 // not transactional. 287 func Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator { 288 return MustTxn(ctx).QueryWithOptions(ctx, statement, queryOptionsFromContext(ctx)) 289 } 290 291 // BufferWrite adds a list of mutations to the set of updates that will be 292 // applied when the transaction is committed. 293 // 294 // It does not actually apply the write until the transaction is committed, so 295 // the operation does not block. The effects of the write won't be visible to 296 // any reads (including reads done in the same transaction) until the 297 // transaction commits. 298 // 299 // It is a wrapper over MustRW(ctx).BufferWrite(...). Panics if the context is 300 // not read-write transactional. 301 func BufferWrite(ctx context.Context, ms ...*spanner.Mutation) { 302 // BufferWrite just appends mutation to an internal buffer. It fails only if 303 // the transaction has already landed. We are OK to panic in this case: 304 // calling BufferWrite outside of a transaction is a programming error. 305 if err := MustRW(ctx).BufferWrite(ms); err != nil { 306 panic(err) 307 } 308 } 309 310 // Update executes a DML statement against the database. It returns the number 311 // of affected rows. Update returns an error if the statement is a query. 312 // However, the query is executed, and any data read will be validated upon 313 // commit. 314 // 315 // It is a shortcut for MustRW(ctx).Update(...). Panics if the context is not 316 // read-write transactional. 317 func Update(ctx context.Context, stmt spanner.Statement) (rowCount int64, err error) { 318 return MustRW(ctx).UpdateWithOptions(ctx, stmt, queryOptionsFromContext(ctx)) 319 } 320 321 // UpdateWithOptions executes a DML statement against the database. It returns 322 // the number of affected rows. The sql query execution will be optimized based 323 // on the given query options. 324 // 325 // It is a shortcut for MustRW(ctx).Update(...). Panics if the context is not 326 // read-write transactional. 327 // 328 // It does not use the default RequestOptions from the ctx. Use opts to pass 329 // these explicitly. 330 func UpdateWithOptions(ctx context.Context, stmt spanner.Statement, opts spanner.QueryOptions) (rowCount int64, err error) { 331 return MustRW(ctx).UpdateWithOptions(ctx, stmt, opts) 332 } 333 334 //////////////////////////////////////////////////////////////////////////////// 335 336 var ( 337 clientContextKey = "go.chromium.org/luci/server/span:client" 338 txnContextKey = "go.chromium.org/luci/server/span:txn" 339 requestOptionsContextKey = "go.chromium.org/luci/server/span:requestOptions" 340 ) 341 342 // client returns a Spanner client installed in the context. 343 // 344 // Panics if it is not there. 345 // 346 // Intentionally private to force all callers to go through package's functions 347 // like ReadWriteTransaction, ReadOnlyTransaction, Single, etc. since they 348 // generally add additional functionality on top of the raw Spanner client that 349 // other LUCI packages assume to be present. Using the Spanner client directly 350 // may violate such assumptions leading to undefined behavior when multiple 351 // packages are used together. 352 func client(ctx context.Context) *spanner.Client { 353 cl, _ := ctx.Value(&clientContextKey).(*spanner.Client) 354 if cl == nil { 355 panic("no Spanner client in the context") 356 } 357 return cl 358 } 359 360 type txnState struct { 361 ro *spanner.ReadOnlyTransaction 362 rw *spanner.ReadWriteTransaction 363 364 m sync.Mutex 365 cbs []func(context.Context) 366 } 367 368 func (s *txnState) deferCB(cb func(context.Context)) { 369 s.m.Lock() 370 s.cbs = append(s.cbs, cb) 371 s.m.Unlock() 372 } 373 374 func (s *txnState) execCBs(ctx context.Context) { 375 // Note: execCBs happens after ReadWriteTransaction has finished. If it 376 // spawned any goroutines, they must have been finished already too (calling 377 // Defer from a goroutine that outlives a transaction is rightfully a race). 378 // Thus all writes to `s.cbs` are finished already and we also passed some 379 // synchronization barrier that waited for the goroutines to join. It's fine 380 // to avoid locking s.m in this case saving 200ns on hot code path. 381 for i := len(s.cbs) - 1; i >= 0; i-- { 382 s.cbs[i](ctx) 383 } 384 } 385 386 func setTxnState(ctx context.Context, s *txnState) context.Context { 387 return context.WithValue(ctx, &txnContextKey, s) 388 } 389 390 func getTxnState(ctx context.Context) *txnState { 391 s, _ := ctx.Value(&txnContextKey).(*txnState) 392 return s 393 } 394 395 func panicOnNestedRW(ctx context.Context) { 396 if RW(ctx) != nil { 397 panic("nested Spanner write transactions are not allowed") 398 } 399 } 400 401 func panicOnNestedRO(ctx context.Context) { 402 if getTxnState(ctx) != nil { 403 panic("nested Spanner read transactions are not allowed") 404 } 405 } 406 407 // RequestOptions holds options common to many Spanner requests. 408 // 409 // Used for setting default request options in the context via 410 // ModifyRequestOptions. See also spanner.ReadOptions also 411 // spanner.QueryOptions. 412 type RequestOptions struct { 413 Tag string 414 Priority sppb.RequestOptions_Priority 415 } 416 417 // ModifyRequestOptions returns a new Context that carries default Spanner 418 // request options. 419 // 420 // These request options will be used by all Spanner operations in this module 421 // if the underlying operation supports it, except the *WithOptions functions 422 // (where the caller provides options explicitly). 423 // 424 // The cb function will be called with the current defaults from the context, 425 // to allow for incremental updates. 426 func ModifyRequestOptions(ctx context.Context, cb func(*RequestOptions)) context.Context { 427 var next RequestOptions 428 if cur, ok := ctx.Value(&requestOptionsContextKey).(*RequestOptions); ok { 429 next = *cur 430 } 431 cb(&next) 432 return context.WithValue(ctx, &requestOptionsContextKey, &next) 433 } 434 435 func queryOptionsFromContext(ctx context.Context) spanner.QueryOptions { 436 opts, ok := ctx.Value(&requestOptionsContextKey).(*RequestOptions) 437 if !ok { 438 return spanner.QueryOptions{} 439 } 440 return spanner.QueryOptions{ 441 Priority: opts.Priority, 442 RequestTag: opts.Tag, 443 } 444 } 445 446 func readOptionsFromContext(ctx context.Context) *spanner.ReadOptions { 447 opts, ok := ctx.Value(&requestOptionsContextKey).(*RequestOptions) 448 if !ok { 449 return &spanner.ReadOptions{} 450 } 451 return &spanner.ReadOptions{ 452 Priority: opts.Priority, 453 RequestTag: opts.Tag, 454 } 455 }