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 }