func (s *suite) TestSetStateServers(c *gc.C) { changes, err := s.State.EnsureAvailability(5, constraints.Value{}, "precise") c.Assert(err, gc.IsNil) c.Assert(changes.Added, gc.HasLen, 5) session := s.State.MongoSession() db := session.DB("juju") runner := txn.NewRunner(db.C("txns")) err = setStateServers0([]string{"0", "1", "4"}, s.State, db, runner) c.Assert(err, gc.IsNil) }
func (s *S) SetUpTest(c *C) { txn.SetChaos(txn.Chaos{}) txn.SetLogger(c) txn.SetDebug(true) s.MgoSuite.SetUpTest(c) s.db = s.session.DB("test") s.tc = s.db.C("tc") s.sc = s.db.C("tc.stash") s.accounts = s.db.C("accounts") s.runner = txn.NewRunner(s.tc) }
func openState() (*state.State, *mgo.Database, *txn.Runner, error) { stInfo, err := stateInfo() if err != nil { return nil, nil, nil, fmt.Errorf("cannot get state info: %v", err) } st, err := state.Open(stInfo, mongo.DialOpts{ Timeout: 20 * time.Second, }, nil) if err != nil { return nil, nil, nil, fmt.Errorf("cannot open state: %v", err) } db := st.MongoSession().DB("juju") runner := txn.NewRunner(db.C("txns")) return st, db, runner, nil }
func main() { log.SetFlags(log.Llongfile) session, err := mgo.Dial("localhost") if err != nil { panic(err) } defer session.Close() // Optional. Switch the session to a monotonic behavior. session.SetMode(mgo.Monotonic, true) tc := session.DB("txn_test").C("tc") runner := txn.NewRunner(tc) accounts := session.DB("txn_test").C("accounts") if err := accounts.Insert(bson.M{"_id": 0, "balance": 300}); err != nil { log.Fatalln(err) } if err := accounts.Insert(bson.M{"_id": 1, "balance": 100}); err != nil { log.Fatalln(err) } if err := accounts.Insert(bson.M{"_id": 2, "balance": 200}); err != nil { log.Fatalln(err) } if err := accounts.Insert(bson.M{"_id": 3, "balance": 400}); err != nil { log.Fatalln(err) } ops := []txn.Op{ { C: "accounts", Id: 0, Assert: bson.D{{"$or", []bson.D{{{"balance", 100}}, {{"balance", 300}}}}}, Update: bson.D{{"$inc", bson.D{{"balance", 100}}}}, }, { C: "accounts", Id: 1, Assert: bson.D{{"balance", 100}}, Update: bson.D{{"$inc", bson.D{{"balance", 150}}}}, }, } if err := runner.Run(ops, "", nil); err != nil { log.Fatalln(err) } }
func (s *watcherSuite) SetUpTest(c *gc.C) { s.BaseSuite.SetUpTest(c) s.MgoSuite.SetUpTest(c) db := s.MgoSuite.Session.DB("juju") s.log = db.C("txnlog") s.log.Create(&mgo.CollectionInfo{ Capped: true, MaxBytes: 1000000, }) s.stash = db.C("txn.stash") s.runner = txn.NewRunner(db.C("txn")) s.runner.ChangeLog(s.log) s.w = watcher.New(s.log) s.ch = make(chan watcher.Change) }
// FIXME: add clean for txn data func (self *Registry) txnRunner(sess *mgo.Session) *txn.Runner { // bson.M{"_id": bson.M{"$lt": bson.NewObjectIdWithTime(minus24h)}} repo := sess.DB(self.DbName).C("txn_collection") // remove txn older then 2 * 60 mins if self.lastCleanTxn.IsZero() { self.lastCleanTxn = time.Now() } if time.Now().Sub(self.lastCleanTxn) > 30*time.Minute { repo.RemoveAll(bson.M{ "_id": bson.M{ "$lt": bson.NewObjectIdWithTime(time.Now().Add(-60 * 2 * time.Minute)), }, }) self.lastCleanTxn = time.Now() } return txn.NewRunner(repo) }
func oneByOneClientTrans(session *mgo.Session) error { db := session.DB("test") txnCollection := db.C("transactions") var retries int = 0 for i := 0; i < *Count; i++ { val := *Start + i // Reset retries per document for retries = 0; retries < *MaxRetry; retries++ { if err := begin(db); err != nil { if shouldRetry(err, session) { RetryCount += 1 continue } return err } runner := txn.NewRunner(txnCollection) op := oneDocOp(val) txnId := bson.NewObjectId() if err := runner.Run([]txn.Op{op}, txnId, nil); err != nil { fmt.Printf("failed to insert %d\n%s\n", val, err) // ignore an error from rollback? if err2 := rollback(db); err2 != nil { // For now, don't print anything, because we're already in an error state. // fmt.Printf("failed to rollback while failing: %s\n", err2) } if shouldRetry(err, session) { RetryCount += 1 continue } return err } if err := commit(db); err != nil { if shouldRetry(err, session) { RetryCount += 1 continue } return err } else { break } } } return nil }
func (s *S) TestTxnQueueStashStressTest(c *C) { txn.SetChaos(txn.Chaos{ SlowdownChance: 0.3, Slowdown: 50 * time.Millisecond, }) defer txn.SetChaos(txn.Chaos{}) // So we can run more iterations of the test in less time. txn.SetDebug(false) const runners = 10 const inserts = 10 const repeat = 100 for r := 0; r < repeat; r++ { var wg sync.WaitGroup wg.Add(runners) for i := 0; i < runners; i++ { go func(i, r int) { defer wg.Done() session := s.session.New() defer session.Close() runner := txn.NewRunner(s.tc.With(session)) for j := 0; j < inserts; j++ { ops := []txn.Op{{ C: "accounts", Id: fmt.Sprintf("insert-%d-%d", r, j), Insert: bson.M{ "added-by": i, }, }} err := runner.Run(ops, "", nil) if err != txn.ErrAborted { c.Check(err, IsNil) } } }(i, r) } wg.Wait() } }
func oneByOneClientTrans(db *mgo.Database) error { txnCollection := db.C("transactions") var retries int = 0 if err := maybeBegin(db, *OneTrans, &retries); err != nil { return err } for i := 0; i < *Count; i++ { val := *Start + i // Reset retries per document for retries = 0; retries < *MaxRetry; retries++ { if err := maybeBegin(db, *EachTrans, &retries); err != nil { return err } runner := txn.NewRunner(txnCollection) op := oneDocOp(val) txnId := bson.NewObjectId() if err := runner.Run([]txn.Op{op}, txnId, nil); err != nil { fmt.Printf("could not insert %d\n%s\n", val, err) // ignore the error because we are returning anyway maybeRollback(db, *OneTrans || *EachTrans) if shouldRetry(err) { RetryCount += 1 continue } return err } if err := maybeCommit(db, *EachTrans); err != nil { if shouldRetry(err) { RetryCount += 1 continue } return err } else { break } } } // ignore the error because we are returning anyway return maybeCommit(db, *OneTrans) }
func bulkClientTrans(db *mgo.Database) error { txnCollection := db.C("transactions") var retries int = 0 ops := make([]txn.Op, 0, *Count) for i := 0; i < *Count; i++ { ops = append(ops, oneDocOp(*Start+i)) } for ; retries < *MaxRetry; retries++ { if err := maybeBegin(db, *OneTrans || *EachTrans, &retries); err != nil { return err } runner := txn.NewRunner(txnCollection) txnId := bson.NewObjectId() if err := runner.Run(ops, txnId, nil); err != nil { fmt.Printf("could not insert all\n%s\n", err) // ignore the error because we are returning anyway maybeRollback(db, *OneTrans || *EachTrans) if shouldRetry(err) { RetryCount += 1 continue } return err } // ignore the error because we are returning anyway if err := maybeCommit(db, *OneTrans || *EachTrans); err != nil { if shouldRetry(err) { RetryCount += 1 continue } return err } else { break } } return nil }
func simulate(c *C, server *dbtest.DBServer, params params) { seed := *seed if seed == 0 { seed = time.Now().UnixNano() } rand.Seed(seed) c.Logf("Seed: %v", seed) txn.SetChaos(txn.Chaos{ KillChance: params.killChance, SlowdownChance: params.slowdownChance, Slowdown: params.slowdown, }) defer txn.SetChaos(txn.Chaos{}) session := server.Session() defer session.Close() db := session.DB("test") tc := db.C("tc") runner := txn.NewRunner(tc) tclog := db.C("tc.log") if params.changelog { info := mgo.CollectionInfo{ Capped: true, MaxBytes: 1000000, } err := tclog.Create(&info) c.Assert(err, IsNil) runner.ChangeLog(tclog) } accounts := db.C("accounts") for i := 0; i < params.accounts; i++ { err := accounts.Insert(M{"_id": i, "balance": 300}) c.Assert(err, IsNil) } var stop time.Time if params.changes <= 0 { stop = time.Now().Add(*duration) } max := params.accounts if params.reinsertCopy || params.reinsertZeroed { max = int(float64(params.accounts) * 1.5) } changes := make(chan balanceChange, 1024) //session.SetMode(mgo.Eventual, true) for i := 0; i < params.workers; i++ { go func() { n := 0 for { if n > 0 && n == params.changes { break } if !stop.IsZero() && time.Now().After(stop) { break } change := balanceChange{ id: bson.NewObjectId(), origin: rand.Intn(max), target: rand.Intn(max), amount: 100, } var old Account var oldExists bool if params.reinsertCopy || params.reinsertZeroed { if err := accounts.FindId(change.origin).One(&old); err != mgo.ErrNotFound { c.Check(err, IsNil) change.amount = old.Balance oldExists = true } } var ops []txn.Op switch { case params.reinsertCopy && oldExists: ops = []txn.Op{{ C: "accounts", Id: change.origin, Assert: M{"balance": change.amount}, Remove: true, }, { C: "accounts", Id: change.target, Assert: txn.DocMissing, Insert: M{"balance": change.amount}, }} case params.reinsertZeroed && oldExists: ops = []txn.Op{{ C: "accounts", Id: change.target, Assert: txn.DocMissing, Insert: M{"balance": 0}, }, { C: "accounts", Id: change.origin, Assert: M{"balance": change.amount}, Remove: true, }, { C: "accounts", Id: change.target, Assert: txn.DocExists, Update: M{"$inc": M{"balance": change.amount}}, }} case params.changeHalf: ops = []txn.Op{{ C: "accounts", Id: change.origin, Assert: M{"balance": M{"$gte": change.amount}}, Update: M{"$inc": M{"balance": -change.amount / 2}}, }, { C: "accounts", Id: change.target, Assert: txn.DocExists, Update: M{"$inc": M{"balance": change.amount / 2}}, }, { C: "accounts", Id: change.origin, Update: M{"$inc": M{"balance": -change.amount / 2}}, }, { C: "accounts", Id: change.target, Update: M{"$inc": M{"balance": change.amount / 2}}, }} default: ops = []txn.Op{{ C: "accounts", Id: change.origin, Assert: M{"balance": M{"$gte": change.amount}}, Update: M{"$inc": M{"balance": -change.amount}}, }, { C: "accounts", Id: change.target, Assert: txn.DocExists, Update: M{"$inc": M{"balance": change.amount}}, }} } err := runner.Run(ops, change.id, nil) if err != nil && err != txn.ErrAborted && err != txn.ErrChaos { c.Check(err, IsNil) } n++ changes <- change } changes <- balanceChange{} }() } alive := params.workers changeLog := make([]balanceChange, 0, 1024) for alive > 0 { change := <-changes if change.id == "" { alive-- } else { changeLog = append(changeLog, change) } } c.Check(len(changeLog), Not(Equals), 0, Commentf("No operations were even attempted.")) txn.SetChaos(txn.Chaos{}) err := runner.ResumeAll() c.Assert(err, IsNil) n, err := accounts.Count() c.Check(err, IsNil) c.Check(n, Equals, params.accounts, Commentf("Number of accounts has changed.")) n, err = accounts.Find(M{"balance": M{"$lt": 0}}).Count() c.Check(err, IsNil) c.Check(n, Equals, 0, Commentf("There are %d accounts with negative balance.", n)) globalBalance := 0 iter := accounts.Find(nil).Iter() account := Account{} for iter.Next(&account) { globalBalance += account.Balance } c.Check(iter.Close(), IsNil) c.Check(globalBalance, Equals, params.accounts*300, Commentf("Total amount of money should be constant.")) // Compute and verify the exact final state of all accounts. balance := make(map[int]int) for i := 0; i < params.accounts; i++ { balance[i] += 300 } var applied, aborted int for _, change := range changeLog { err := runner.Resume(change.id) if err == txn.ErrAborted { aborted++ continue } else if err != nil { c.Fatalf("resuming %s failed: %v", change.id, err) } balance[change.origin] -= change.amount balance[change.target] += change.amount applied++ } iter = accounts.Find(nil).Iter() for iter.Next(&account) { c.Assert(account.Balance, Equals, balance[account.Id]) } c.Check(iter.Close(), IsNil) c.Logf("Total transactions: %d (%d applied, %d aborted)", len(changeLog), applied, aborted) if params.changelog { n, err := tclog.Count() c.Assert(err, IsNil) // Check if the capped collection is full. dummy := make([]byte, 1024) tclog.Insert(M{"_id": bson.NewObjectId(), "dummy": dummy}) m, err := tclog.Count() c.Assert(err, IsNil) if m == n+1 { // Wasn't full, so it must have seen it all. c.Assert(err, IsNil) c.Assert(n, Equals, applied) } } }
// PurgeTxn purges missing transation from restoreInfoC collection. // These can be caused because this collection is heavy use while backing // up and mongo 3.2 does not like this. func (info *RestoreInfo) PurgeTxn() error { restoreInfo, closer := info.st.getRawCollection(restoreInfoC) defer closer() r := txn.NewRunner(restoreInfo) return r.PurgeMissing(restoreInfoC) }