github.com/lianghucheng/zrddz@v0.0.0-20200923083010-c71f680932e2/src/gopkg.in/mgo.v2/txn/sim_test.go (about)

     1  package txn_test
     2  
     3  import (
     4  	"flag"
     5  	. "gopkg.in/check.v1"
     6  	"gopkg.in/mgo.v2"
     7  	"gopkg.in/mgo.v2/bson"
     8  	"gopkg.in/mgo.v2/dbtest"
     9  	"gopkg.in/mgo.v2/txn"
    10  	"math/rand"
    11  	"time"
    12  )
    13  
    14  var (
    15  	duration = flag.Duration("duration", 200*time.Millisecond, "duration for each simulation")
    16  	seed     = flag.Int64("seed", 0, "seed for rand")
    17  )
    18  
    19  type params struct {
    20  	killChance     float64
    21  	slowdownChance float64
    22  	slowdown       time.Duration
    23  
    24  	unsafe         bool
    25  	workers        int
    26  	accounts       int
    27  	changeHalf     bool
    28  	reinsertCopy   bool
    29  	reinsertZeroed bool
    30  	changelog      bool
    31  
    32  	changes int
    33  }
    34  
    35  func (s *S) TestSim1Worker(c *C) {
    36  	simulate(c, &s.server, params{
    37  		workers:        1,
    38  		accounts:       4,
    39  		killChance:     0.01,
    40  		slowdownChance: 0.3,
    41  		slowdown:       100 * time.Millisecond,
    42  	})
    43  }
    44  
    45  func (s *S) TestSim4WorkersDense(c *C) {
    46  	simulate(c, &s.server, params{
    47  		workers:        4,
    48  		accounts:       2,
    49  		killChance:     0.01,
    50  		slowdownChance: 0.3,
    51  		slowdown:       100 * time.Millisecond,
    52  	})
    53  }
    54  
    55  func (s *S) TestSim4WorkersSparse(c *C) {
    56  	simulate(c, &s.server, params{
    57  		workers:        4,
    58  		accounts:       10,
    59  		killChance:     0.01,
    60  		slowdownChance: 0.3,
    61  		slowdown:       100 * time.Millisecond,
    62  	})
    63  }
    64  
    65  func (s *S) TestSimHalf1Worker(c *C) {
    66  	simulate(c, &s.server, params{
    67  		workers:        1,
    68  		accounts:       4,
    69  		changeHalf:     true,
    70  		killChance:     0.01,
    71  		slowdownChance: 0.3,
    72  		slowdown:       100 * time.Millisecond,
    73  	})
    74  }
    75  
    76  func (s *S) TestSimHalf4WorkersDense(c *C) {
    77  	simulate(c, &s.server, params{
    78  		workers:        4,
    79  		accounts:       2,
    80  		changeHalf:     true,
    81  		killChance:     0.01,
    82  		slowdownChance: 0.3,
    83  		slowdown:       100 * time.Millisecond,
    84  	})
    85  }
    86  
    87  func (s *S) TestSimHalf4WorkersSparse(c *C) {
    88  	simulate(c, &s.server, params{
    89  		workers:        4,
    90  		accounts:       10,
    91  		changeHalf:     true,
    92  		killChance:     0.01,
    93  		slowdownChance: 0.3,
    94  		slowdown:       100 * time.Millisecond,
    95  	})
    96  }
    97  
    98  func (s *S) TestSimReinsertCopy1Worker(c *C) {
    99  	simulate(c, &s.server, params{
   100  		workers:        1,
   101  		accounts:       10,
   102  		reinsertCopy:   true,
   103  		killChance:     0.01,
   104  		slowdownChance: 0.3,
   105  		slowdown:       100 * time.Millisecond,
   106  	})
   107  }
   108  
   109  func (s *S) TestSimReinsertCopy4Workers(c *C) {
   110  	simulate(c, &s.server, params{
   111  		workers:        4,
   112  		accounts:       10,
   113  		reinsertCopy:   true,
   114  		killChance:     0.01,
   115  		slowdownChance: 0.3,
   116  		slowdown:       100 * time.Millisecond,
   117  	})
   118  }
   119  
   120  func (s *S) TestSimReinsertZeroed1Worker(c *C) {
   121  	simulate(c, &s.server, params{
   122  		workers:        1,
   123  		accounts:       10,
   124  		reinsertZeroed: true,
   125  		killChance:     0.01,
   126  		slowdownChance: 0.3,
   127  		slowdown:       100 * time.Millisecond,
   128  	})
   129  }
   130  
   131  func (s *S) TestSimReinsertZeroed4Workers(c *C) {
   132  	simulate(c, &s.server, params{
   133  		workers:        4,
   134  		accounts:       10,
   135  		reinsertZeroed: true,
   136  		killChance:     0.01,
   137  		slowdownChance: 0.3,
   138  		slowdown:       100 * time.Millisecond,
   139  	})
   140  }
   141  
   142  func (s *S) TestSimChangeLog(c *C) {
   143  	simulate(c, &s.server, params{
   144  		workers:        4,
   145  		accounts:       10,
   146  		killChance:     0.01,
   147  		slowdownChance: 0.3,
   148  		slowdown:       100 * time.Millisecond,
   149  		changelog:      true,
   150  	})
   151  }
   152  
   153  type balanceChange struct {
   154  	id     bson.ObjectId
   155  	origin int
   156  	target int
   157  	amount int
   158  }
   159  
   160  func simulate(c *C, server *dbtest.DBServer, params params) {
   161  	seed := *seed
   162  	if seed == 0 {
   163  		seed = time.Now().UnixNano()
   164  	}
   165  	rand.Seed(seed)
   166  	c.Logf("Seed: %v", seed)
   167  
   168  	txn.SetChaos(txn.Chaos{
   169  		KillChance:     params.killChance,
   170  		SlowdownChance: params.slowdownChance,
   171  		Slowdown:       params.slowdown,
   172  	})
   173  	defer txn.SetChaos(txn.Chaos{})
   174  
   175  	session := server.Session()
   176  	defer session.Close()
   177  
   178  	db := session.DB("test")
   179  	tc := db.C("tc")
   180  
   181  	runner := txn.NewRunner(tc)
   182  
   183  	tclog := db.C("tc.log")
   184  	if params.changelog {
   185  		info := mgo.CollectionInfo{
   186  			Capped:   true,
   187  			MaxBytes: 1000000,
   188  		}
   189  		err := tclog.Create(&info)
   190  		c.Assert(err, IsNil)
   191  		runner.ChangeLog(tclog)
   192  	}
   193  
   194  	accounts := db.C("accounts")
   195  	for i := 0; i < params.accounts; i++ {
   196  		err := accounts.Insert(M{"_id": i, "balance": 300})
   197  		c.Assert(err, IsNil)
   198  	}
   199  	var stop time.Time
   200  	if params.changes <= 0 {
   201  		stop = time.Now().Add(*duration)
   202  	}
   203  
   204  	max := params.accounts
   205  	if params.reinsertCopy || params.reinsertZeroed {
   206  		max = int(float64(params.accounts) * 1.5)
   207  	}
   208  
   209  	changes := make(chan balanceChange, 1024)
   210  
   211  	//session.SetMode(mgo.Eventual, true)
   212  	for i := 0; i < params.workers; i++ {
   213  		go func() {
   214  			n := 0
   215  			for {
   216  				if n > 0 && n == params.changes {
   217  					break
   218  				}
   219  				if !stop.IsZero() && time.Now().After(stop) {
   220  					break
   221  				}
   222  
   223  				change := balanceChange{
   224  					id:     bson.NewObjectId(),
   225  					origin: rand.Intn(max),
   226  					target: rand.Intn(max),
   227  					amount: 100,
   228  				}
   229  
   230  				var old Account
   231  				var oldExists bool
   232  				if params.reinsertCopy || params.reinsertZeroed {
   233  					if err := accounts.FindId(change.origin).One(&old); err != mgo.ErrNotFound {
   234  						c.Check(err, IsNil)
   235  						change.amount = old.Balance
   236  						oldExists = true
   237  					}
   238  				}
   239  
   240  				var ops []txn.Op
   241  				switch {
   242  				case params.reinsertCopy && oldExists:
   243  					ops = []txn.Op{{
   244  						C:      "accounts",
   245  						Id:     change.origin,
   246  						Assert: M{"balance": change.amount},
   247  						Remove: true,
   248  					}, {
   249  						C:      "accounts",
   250  						Id:     change.target,
   251  						Assert: txn.DocMissing,
   252  						Insert: M{"balance": change.amount},
   253  					}}
   254  				case params.reinsertZeroed && oldExists:
   255  					ops = []txn.Op{{
   256  						C:      "accounts",
   257  						Id:     change.target,
   258  						Assert: txn.DocMissing,
   259  						Insert: M{"balance": 0},
   260  					}, {
   261  						C:      "accounts",
   262  						Id:     change.origin,
   263  						Assert: M{"balance": change.amount},
   264  						Remove: true,
   265  					}, {
   266  						C:      "accounts",
   267  						Id:     change.target,
   268  						Assert: txn.DocExists,
   269  						Update: M{"$inc": M{"balance": change.amount}},
   270  					}}
   271  				case params.changeHalf:
   272  					ops = []txn.Op{{
   273  						C:      "accounts",
   274  						Id:     change.origin,
   275  						Assert: M{"balance": M{"$gte": change.amount}},
   276  						Update: M{"$inc": M{"balance": -change.amount / 2}},
   277  					}, {
   278  						C:      "accounts",
   279  						Id:     change.target,
   280  						Assert: txn.DocExists,
   281  						Update: M{"$inc": M{"balance": change.amount / 2}},
   282  					}, {
   283  						C:      "accounts",
   284  						Id:     change.origin,
   285  						Update: M{"$inc": M{"balance": -change.amount / 2}},
   286  					}, {
   287  						C:      "accounts",
   288  						Id:     change.target,
   289  						Update: M{"$inc": M{"balance": change.amount / 2}},
   290  					}}
   291  				default:
   292  					ops = []txn.Op{{
   293  						C:      "accounts",
   294  						Id:     change.origin,
   295  						Assert: M{"balance": M{"$gte": change.amount}},
   296  						Update: M{"$inc": M{"balance": -change.amount}},
   297  					}, {
   298  						C:      "accounts",
   299  						Id:     change.target,
   300  						Assert: txn.DocExists,
   301  						Update: M{"$inc": M{"balance": change.amount}},
   302  					}}
   303  				}
   304  
   305  				err := runner.Run(ops, change.id, nil)
   306  				if err != nil && err != txn.ErrAborted && err != txn.ErrChaos {
   307  					c.Check(err, IsNil)
   308  				}
   309  				n++
   310  				changes <- change
   311  			}
   312  			changes <- balanceChange{}
   313  		}()
   314  	}
   315  
   316  	alive := params.workers
   317  	changeLog := make([]balanceChange, 0, 1024)
   318  	for alive > 0 {
   319  		change := <-changes
   320  		if change.id == "" {
   321  			alive--
   322  		} else {
   323  			changeLog = append(changeLog, change)
   324  		}
   325  	}
   326  	c.Check(len(changeLog), Not(Equals), 0, Commentf("No operations were even attempted."))
   327  
   328  	txn.SetChaos(txn.Chaos{})
   329  	err := runner.ResumeAll()
   330  	c.Assert(err, IsNil)
   331  
   332  	n, err := accounts.Count()
   333  	c.Check(err, IsNil)
   334  	c.Check(n, Equals, params.accounts, Commentf("Number of accounts has changed."))
   335  
   336  	n, err = accounts.Find(M{"balance": M{"$lt": 0}}).Count()
   337  	c.Check(err, IsNil)
   338  	c.Check(n, Equals, 0, Commentf("There are %d accounts with negative balance.", n))
   339  
   340  	globalBalance := 0
   341  	iter := accounts.Find(nil).Iter()
   342  	account := Account{}
   343  	for iter.Next(&account) {
   344  		globalBalance += account.Balance
   345  	}
   346  	c.Check(iter.Close(), IsNil)
   347  	c.Check(globalBalance, Equals, params.accounts*300, Commentf("Total amount of money should be constant."))
   348  
   349  	// Compute and verify the exact final state of all accounts.
   350  	balance := make(map[int]int)
   351  	for i := 0; i < params.accounts; i++ {
   352  		balance[i] += 300
   353  	}
   354  	var applied, aborted int
   355  	for _, change := range changeLog {
   356  		err := runner.Resume(change.id)
   357  		if err == txn.ErrAborted {
   358  			aborted++
   359  			continue
   360  		} else if err != nil {
   361  			c.Fatalf("resuming %s failed: %v", change.id, err)
   362  		}
   363  		balance[change.origin] -= change.amount
   364  		balance[change.target] += change.amount
   365  		applied++
   366  	}
   367  	iter = accounts.Find(nil).Iter()
   368  	for iter.Next(&account) {
   369  		c.Assert(account.Balance, Equals, balance[account.Id])
   370  	}
   371  	c.Check(iter.Close(), IsNil)
   372  	c.Logf("Total transactions: %d (%d applied, %d aborted)", len(changeLog), applied, aborted)
   373  
   374  	if params.changelog {
   375  		n, err := tclog.Count()
   376  		c.Assert(err, IsNil)
   377  		// Check if the capped collection is full.
   378  		dummy := make([]byte, 1024)
   379  		tclog.Insert(M{"_id": bson.NewObjectId(), "dummy": dummy})
   380  		m, err := tclog.Count()
   381  		c.Assert(err, IsNil)
   382  		if m == n+1 {
   383  			// Wasn't full, so it must have seen it all.
   384  			c.Assert(err, IsNil)
   385  			c.Assert(n, Equals, applied)
   386  		}
   387  	}
   388  }