| // Copyright 2011 Google Inc. All rights reserved. |
| // Use of this source code is governed by the Apache 2.0 |
| // license that can be found in the LICENSE file. |
| |
| package datastore |
| |
| import ( |
| "errors" |
| |
| "golang.org/x/net/context" |
| |
| "google.golang.org/appengine/internal" |
| pb "google.golang.org/appengine/internal/datastore" |
| ) |
| |
| func init() { |
| internal.RegisterTransactionSetter(func(x *pb.Query, t *pb.Transaction) { |
| x.Transaction = t |
| }) |
| internal.RegisterTransactionSetter(func(x *pb.GetRequest, t *pb.Transaction) { |
| x.Transaction = t |
| }) |
| internal.RegisterTransactionSetter(func(x *pb.PutRequest, t *pb.Transaction) { |
| x.Transaction = t |
| }) |
| internal.RegisterTransactionSetter(func(x *pb.DeleteRequest, t *pb.Transaction) { |
| x.Transaction = t |
| }) |
| } |
| |
| // ErrConcurrentTransaction is returned when a transaction is rolled back due |
| // to a conflict with a concurrent transaction. |
| var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction") |
| |
| // RunInTransaction runs f in a transaction. It calls f with a transaction |
| // context tc that f should use for all App Engine operations. |
| // |
| // If f returns nil, RunInTransaction attempts to commit the transaction, |
| // returning nil if it succeeds. If the commit fails due to a conflicting |
| // transaction, RunInTransaction retries f, each time with a new transaction |
| // context. It gives up and returns ErrConcurrentTransaction after three |
| // failed attempts. The number of attempts can be configured by specifying |
| // TransactionOptions.Attempts. |
| // |
| // If f returns non-nil, then any datastore changes will not be applied and |
| // RunInTransaction returns that same error. The function f is not retried. |
| // |
| // Note that when f returns, the transaction is not yet committed. Calling code |
| // must be careful not to assume that any of f's changes have been committed |
| // until RunInTransaction returns nil. |
| // |
| // Since f may be called multiple times, f should usually be idempotent. |
| // datastore.Get is not idempotent when unmarshaling slice fields. |
| // |
| // Nested transactions are not supported; c may not be a transaction context. |
| func RunInTransaction(c context.Context, f func(tc context.Context) error, opts *TransactionOptions) error { |
| xg := false |
| if opts != nil { |
| xg = opts.XG |
| } |
| readOnly := false |
| if opts != nil { |
| readOnly = opts.ReadOnly |
| } |
| attempts := 3 |
| if opts != nil && opts.Attempts > 0 { |
| attempts = opts.Attempts |
| } |
| var t *pb.Transaction |
| var err error |
| for i := 0; i < attempts; i++ { |
| if t, err = internal.RunTransactionOnce(c, f, xg, readOnly, t); err != internal.ErrConcurrentTransaction { |
| return err |
| } |
| } |
| return ErrConcurrentTransaction |
| } |
| |
| // TransactionOptions are the options for running a transaction. |
| type TransactionOptions struct { |
| // XG is whether the transaction can cross multiple entity groups. In |
| // comparison, a single group transaction is one where all datastore keys |
| // used have the same root key. Note that cross group transactions do not |
| // have the same behavior as single group transactions. In particular, it |
| // is much more likely to see partially applied transactions in different |
| // entity groups, in global queries. |
| // It is valid to set XG to true even if the transaction is within a |
| // single entity group. |
| XG bool |
| // Attempts controls the number of retries to perform when commits fail |
| // due to a conflicting transaction. If omitted, it defaults to 3. |
| Attempts int |
| // ReadOnly controls whether the transaction is a read only transaction. |
| // Read only transactions are potentially more efficient. |
| ReadOnly bool |
| } |