github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/renter/proto/contractset.go (about) 1 package proto 2 3 import ( 4 "errors" 5 "os" 6 "path/filepath" 7 "sync" 8 9 "github.com/Synthesix/Sia/build" 10 "github.com/Synthesix/Sia/modules" 11 "github.com/Synthesix/Sia/types" 12 "github.com/NebulousLabs/ratelimit" 13 14 "github.com/NebulousLabs/writeaheadlog" 15 ) 16 17 // A ContractSet provides safe concurrent access to a set of contracts. Its 18 // purpose is to serialize modifications to individual contracts, as well as 19 // to provide operations on the set as a whole. 20 type ContractSet struct { 21 contracts map[types.FileContractID]*SafeContract 22 deps modules.Dependencies 23 dir string 24 mu sync.Mutex 25 rl *ratelimit.RateLimit 26 wal *writeaheadlog.WAL 27 } 28 29 // Acquire looks up the contract with the specified FileContractID and locks 30 // it before returning it. If the contract is not present in the set, Acquire 31 // returns false and a zero-valued RenterContract. 32 func (cs *ContractSet) Acquire(id types.FileContractID) (*SafeContract, bool) { 33 cs.mu.Lock() 34 safeContract, ok := cs.contracts[id] 35 cs.mu.Unlock() 36 if !ok { 37 return nil, false 38 } 39 safeContract.mu.Lock() 40 // We need to check if the contract is still in the map or if it has been 41 // deleted in the meantime. 42 cs.mu.Lock() 43 _, ok = cs.contracts[id] 44 cs.mu.Unlock() 45 if !ok { 46 safeContract.mu.Unlock() 47 return nil, false 48 } 49 return safeContract, true 50 } 51 52 // Delete removes a contract from the set. The contract must have been 53 // previously acquired by Acquire. If the contract is not present in the set, 54 // Delete is a no-op. 55 func (cs *ContractSet) Delete(c *SafeContract) { 56 cs.mu.Lock() 57 safeContract, ok := cs.contracts[c.header.ID()] 58 if !ok { 59 cs.mu.Unlock() 60 build.Critical("Delete called on already deleted contract") 61 return 62 } 63 delete(cs.contracts, c.header.ID()) 64 cs.mu.Unlock() 65 safeContract.mu.Unlock() 66 // delete contract file 67 os.Remove(filepath.Join(cs.dir, c.header.ID().String()+contractExtension)) 68 } 69 70 // IDs returns the FileContractID of each contract in the set. The contracts 71 // are not locked. 72 func (cs *ContractSet) IDs() []types.FileContractID { 73 cs.mu.Lock() 74 defer cs.mu.Unlock() 75 ids := make([]types.FileContractID, 0, len(cs.contracts)) 76 for id := range cs.contracts { 77 ids = append(ids, id) 78 } 79 return ids 80 } 81 82 // Len returns the number of contracts in the set. 83 func (cs *ContractSet) Len() int { 84 cs.mu.Lock() 85 defer cs.mu.Unlock() 86 return len(cs.contracts) 87 } 88 89 // Return returns a locked contract to the set and unlocks it. The contract 90 // must have been previously acquired by Acquire. If the contract is not 91 // present in the set, Return panics. 92 func (cs *ContractSet) Return(c *SafeContract) { 93 cs.mu.Lock() 94 safeContract, ok := cs.contracts[c.header.ID()] 95 if !ok { 96 cs.mu.Unlock() 97 build.Critical("no contract with that id") 98 } 99 cs.mu.Unlock() 100 safeContract.mu.Unlock() 101 } 102 103 // SetRateLimits sets the bandwidth limits for connections created by the 104 // contractSet. 105 func (cs *ContractSet) SetRateLimits(readBPS, writeBPS int64, packetSize uint64) { 106 cs.rl.SetLimits(readBPS, writeBPS, packetSize) 107 } 108 109 // View returns a copy of the contract with the specified ID. The contracts is 110 // not locked. Certain fields, including the MerkleRoots, are set to nil for 111 // safety reasons. If the contract is not present in the set, View 112 // returns false and a zero-valued RenterContract. 113 func (cs *ContractSet) View(id types.FileContractID) (modules.RenterContract, bool) { 114 cs.mu.Lock() 115 defer cs.mu.Unlock() 116 safeContract, ok := cs.contracts[id] 117 if !ok { 118 return modules.RenterContract{}, false 119 } 120 return safeContract.Metadata(), true 121 } 122 123 // ViewAll returns the metadata of each contract in the set. The contracts are 124 // not locked. 125 func (cs *ContractSet) ViewAll() []modules.RenterContract { 126 cs.mu.Lock() 127 defer cs.mu.Unlock() 128 contracts := make([]modules.RenterContract, 0, len(cs.contracts)) 129 for _, safeContract := range cs.contracts { 130 contracts = append(contracts, safeContract.Metadata()) 131 } 132 return contracts 133 } 134 135 // Close closes all contracts in a contract set, this means rendering it unusable for I/O 136 func (cs *ContractSet) Close() error { 137 for _, c := range cs.contracts { 138 c.f.Close() 139 } 140 _, err := cs.wal.CloseIncomplete() 141 return err 142 } 143 144 // NewContractSet returns a ContractSet storing its contracts in the specified 145 // dir. 146 func NewContractSet(dir string, deps modules.Dependencies) (*ContractSet, error) { 147 if err := os.MkdirAll(dir, 0700); err != nil { 148 return nil, err 149 } 150 d, err := os.Open(dir) 151 if err != nil { 152 return nil, err 153 } else if stat, err := d.Stat(); err != nil { 154 return nil, err 155 } else if !stat.IsDir() { 156 return nil, errors.New("not a directory") 157 } 158 defer d.Close() 159 160 // Load the WAL. Any recovered updates will be applied after loading 161 // contracts. 162 // COMPATv1.3.1RC2 Rename old wals to have the 'wal' extension if new file 163 // doesn't exist. 164 if err := v131RC2RenameWAL(dir); err != nil { 165 return nil, err 166 } 167 walTxns, wal, err := writeaheadlog.New(filepath.Join(dir, "contractset.wal")) 168 if err != nil { 169 return nil, err 170 } 171 172 cs := &ContractSet{ 173 contracts: make(map[types.FileContractID]*SafeContract), 174 deps: deps, 175 dir: dir, 176 wal: wal, 177 } 178 // Set the initial rate limit to 'unlimited' bandwidth with 4kib packets. 179 cs.rl = ratelimit.NewRateLimit(0, 0, 0) 180 181 // Load the contract files. 182 dirNames, err := d.Readdirnames(-1) 183 if err != nil { 184 return nil, err 185 } 186 187 for _, filename := range dirNames { 188 if filepath.Ext(filename) != contractExtension { 189 continue 190 } 191 path := filepath.Join(dir, filename) 192 if err := cs.loadSafeContract(path, walTxns); err != nil { 193 return nil, err 194 } 195 } 196 197 return cs, nil 198 } 199 200 // v131RC2RenameWAL renames an existing old wal file from contractset.log to 201 // contractset.wal 202 func v131RC2RenameWAL(dir string) error { 203 oldPath := filepath.Join(dir, "contractset.log") 204 newPath := filepath.Join(dir, "contractset.wal") 205 _, errOld := os.Stat(oldPath) 206 _, errNew := os.Stat(newPath) 207 if !os.IsNotExist(errOld) && os.IsNotExist(errNew) { 208 return build.ExtendErr("failed to rename contractset.log to contractset.wal", 209 os.Rename(oldPath, newPath)) 210 } 211 return nil 212 }