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  }