1// Copyright 2011 Google Inc. All rights reserved. 2// Use of this source code is governed by the Apache 2.0 3// license that can be found in the LICENSE file. 4 5package datastore 6 7import ( 8 "errors" 9 10 "golang.org/x/net/context" 11 12 "google.golang.org/appengine/internal" 13 pb "google.golang.org/appengine/internal/datastore" 14) 15 16func init() { 17 internal.RegisterTransactionSetter(func(x *pb.Query, t *pb.Transaction) { 18 x.Transaction = t 19 }) 20 internal.RegisterTransactionSetter(func(x *pb.GetRequest, t *pb.Transaction) { 21 x.Transaction = t 22 }) 23 internal.RegisterTransactionSetter(func(x *pb.PutRequest, t *pb.Transaction) { 24 x.Transaction = t 25 }) 26 internal.RegisterTransactionSetter(func(x *pb.DeleteRequest, t *pb.Transaction) { 27 x.Transaction = t 28 }) 29} 30 31// ErrConcurrentTransaction is returned when a transaction is rolled back due 32// to a conflict with a concurrent transaction. 33var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction") 34 35// RunInTransaction runs f in a transaction. It calls f with a transaction 36// context tc that f should use for all App Engine operations. 37// 38// If f returns nil, RunInTransaction attempts to commit the transaction, 39// returning nil if it succeeds. If the commit fails due to a conflicting 40// transaction, RunInTransaction retries f, each time with a new transaction 41// context. It gives up and returns ErrConcurrentTransaction after three 42// failed attempts. The number of attempts can be configured by specifying 43// TransactionOptions.Attempts. 44// 45// If f returns non-nil, then any datastore changes will not be applied and 46// RunInTransaction returns that same error. The function f is not retried. 47// 48// Note that when f returns, the transaction is not yet committed. Calling code 49// must be careful not to assume that any of f's changes have been committed 50// until RunInTransaction returns nil. 51// 52// Since f may be called multiple times, f should usually be idempotent. 53// datastore.Get is not idempotent when unmarshaling slice fields. 54// 55// Nested transactions are not supported; c may not be a transaction context. 56func RunInTransaction(c context.Context, f func(tc context.Context) error, opts *TransactionOptions) error { 57 xg := false 58 if opts != nil { 59 xg = opts.XG 60 } 61 attempts := 3 62 if opts != nil && opts.Attempts > 0 { 63 attempts = opts.Attempts 64 } 65 for i := 0; i < attempts; i++ { 66 if err := internal.RunTransactionOnce(c, f, xg); err != internal.ErrConcurrentTransaction { 67 return err 68 } 69 } 70 return ErrConcurrentTransaction 71} 72 73// TransactionOptions are the options for running a transaction. 74type TransactionOptions struct { 75 // XG is whether the transaction can cross multiple entity groups. In 76 // comparison, a single group transaction is one where all datastore keys 77 // used have the same root key. Note that cross group transactions do not 78 // have the same behavior as single group transactions. In particular, it 79 // is much more likely to see partially applied transactions in different 80 // entity groups, in global queries. 81 // It is valid to set XG to true even if the transaction is within a 82 // single entity group. 83 XG bool 84 // Attempts controls the number of retries to perform when commits fail 85 // due to a conflicting transaction. If omitted, it defaults to 3. 86 Attempts int 87} 88