• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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