github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/renter/proto/contractset_test.go (about) 1 package proto 2 3 import ( 4 "sync" 5 "testing" 6 "time" 7 8 "github.com/Synthesix/Sia/types" 9 10 "github.com/NebulousLabs/fastrand" 11 ) 12 13 // mustAcquire is a convenience function for acquiring contracts that are 14 // known to be in the set. 15 func (cs *ContractSet) mustAcquire(t *testing.T, id types.FileContractID) *SafeContract { 16 t.Helper() 17 c, ok := cs.Acquire(id) 18 if !ok { 19 t.Fatal("no contract with that id") 20 } 21 return c 22 } 23 24 // TestContractSet tests that the ContractSet type is safe for concurrent use. 25 func TestContractSet(t *testing.T) { 26 // create contract set 27 c1 := &SafeContract{header: contractHeader{Transaction: types.Transaction{ 28 FileContractRevisions: []types.FileContractRevision{{ 29 ParentID: types.FileContractID{1}, 30 NewValidProofOutputs: []types.SiacoinOutput{{}, {}}, 31 UnlockConditions: types.UnlockConditions{ 32 PublicKeys: []types.SiaPublicKey{{}, {}}, 33 }, 34 }}, 35 }}} 36 id1 := c1.header.ID() 37 c2 := &SafeContract{header: contractHeader{Transaction: types.Transaction{ 38 FileContractRevisions: []types.FileContractRevision{{ 39 ParentID: types.FileContractID{2}, 40 NewValidProofOutputs: []types.SiacoinOutput{{}, {}}, 41 UnlockConditions: types.UnlockConditions{ 42 PublicKeys: []types.SiaPublicKey{{}, {}}, 43 }, 44 }}, 45 }}} 46 id2 := c2.header.ID() 47 cs := &ContractSet{ 48 contracts: map[types.FileContractID]*SafeContract{ 49 id1: c1, 50 id2: c2, 51 }, 52 } 53 54 // uncontested acquire/release 55 c1 = cs.mustAcquire(t, id1) 56 cs.Return(c1) 57 58 // 100 concurrent serialized mutations 59 var wg sync.WaitGroup 60 for i := 0; i < 100; i++ { 61 wg.Add(1) 62 go func() { 63 defer wg.Done() 64 c1 := cs.mustAcquire(t, id1) 65 c1.header.Transaction.FileContractRevisions[0].NewRevisionNumber++ 66 time.Sleep(time.Duration(fastrand.Intn(100))) 67 cs.Return(c1) 68 }() 69 } 70 wg.Wait() 71 c1 = cs.mustAcquire(t, id1) 72 cs.Return(c1) 73 if c1.header.LastRevision().NewRevisionNumber != 100 { 74 t.Fatal("expected exactly 100 increments, got", c1.header.LastRevision().NewRevisionNumber) 75 } 76 77 // a blocked acquire shouldn't prevent a return 78 c1 = cs.mustAcquire(t, id1) 79 go func() { 80 time.Sleep(time.Millisecond) 81 cs.Return(c1) 82 }() 83 c1 = cs.mustAcquire(t, id1) 84 cs.Return(c1) 85 86 // delete and reinsert id2 87 c2 = cs.mustAcquire(t, id2) 88 cs.Delete(c2) 89 cs.mu.Lock() 90 cs.contracts[id2] = c2 91 cs.mu.Unlock() 92 93 // call all the methods in parallel haphazardly 94 funcs := []func(){ 95 func() { cs.Len() }, 96 func() { cs.IDs() }, 97 func() { cs.View(id1); cs.View(id2) }, 98 func() { cs.ViewAll() }, 99 func() { cs.Return(cs.mustAcquire(t, id1)) }, 100 func() { cs.Return(cs.mustAcquire(t, id2)) }, 101 func() { 102 c3 := &SafeContract{header: contractHeader{ 103 Transaction: types.Transaction{ 104 FileContractRevisions: []types.FileContractRevision{{ 105 ParentID: types.FileContractID{3}, 106 NewValidProofOutputs: []types.SiacoinOutput{{}, {}}, 107 UnlockConditions: types.UnlockConditions{ 108 PublicKeys: []types.SiaPublicKey{{}, {}}, 109 }, 110 }}, 111 }, 112 }} 113 id3 := c3.header.ID() 114 cs.mu.Lock() 115 cs.contracts[id3] = c3 116 cs.mu.Unlock() 117 cs.mustAcquire(t, id3) 118 cs.Delete(c3) 119 }, 120 } 121 wg = sync.WaitGroup{} 122 for _, fn := range funcs { 123 wg.Add(1) 124 go func(fn func()) { 125 defer wg.Done() 126 for i := 0; i < 100; i++ { 127 time.Sleep(time.Duration(fastrand.Intn(100))) 128 fn() 129 } 130 }(fn) 131 } 132 wg.Wait() 133 }